DCIPs/assets/eip-5606/contracts/MultiverseNFT.sol

422 lines
14 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract ERC721Full is ERC721Enumerable, ERC721URIStorage {
/// @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
/// @param name is a non-empty string
/// @param symbol is a non-empty string
constructor(string memory name, string memory symbol)
ERC721(name, symbol)
{}
/// @dev Hook that is called before any token transfer. This includes minting and burning. `from`'s `tokenId` will be transferred to `to`
/// @param from is an non-zero address
/// @param to is an non-zero address
/// @param tokenId is an uint256 which determine token transferred from `from` to `to`
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override(ERC721Enumerable, ERC721) {
ERC721Enumerable._beforeTokenTransfer(from, to, tokenId);
}
/// @notice Interface of the ERC165 standard
/// @param interfaceId is a byte4 which determine interface used
/// @return true if this contract implements the interface defined by `interfaceId`
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(ERC721Enumerable, ERC721)
returns (bool)
{
return
ERC721.supportsInterface(interfaceId) ||
ERC721Enumerable.supportsInterface(interfaceId);
}
/// @notice the Uniform Resource Identifier (URI) for `tokenId` token
/// @param tokenId is unit256
/// @return string of (URI) for `tokenId` token
function tokenURI(uint256 tokenId)
public
view
virtual
override(ERC721URIStorage, ERC721)
returns (string memory)
{
return ERC721URIStorage.tokenURI(tokenId);
}
function _burn(uint256 tokenId)
internal
override(ERC721, ERC721URIStorage)
{}
}
/**
* @dev Interface of the Multiverse NFT standard as defined in the EIP.
*/
interface IMultiverseNFT {
/**
* @dev struct to store delegate token details
*
*/
struct DelegateData {
address contractAddress;
uint256 tokenId;
uint256 quantity;
}
/**
* @dev Emitted when one or more new delegate NFTs are added to a Multiverse NFT
*
*/
event Bundled(
uint256 multiverseTokenID,
DelegateData[] delegateData,
address ownerAddress
);
/**
* @dev Emitted when one or more delegate NFTs are removed from a Multiverse NFT
*/
event Unbundled(uint256 multiverseTokenID, DelegateData[] delegateData);
/**
* @dev Accepts the tokenId of the Multiverse NFT and returns an array of delegate token data
*/
function delegateTokens(uint256 multiverseTokenID)
external
view
returns (DelegateData[] memory);
/**
* @dev Removes one or more delegate NFTs from a Multiverse NFT
* This function accepts the delegate NFT details, and transfer those NFTs out of the Multiverse NFT contract to the owner's wallet
*/
function unbundle(
DelegateData[] memory delegateData,
uint256 multiverseTokenID
) external;
/**
* @dev Adds one or more delegate NFTs to a Multiverse NFT
* This function accepts the delegate NFT details, and transfers those NFTs to the Multiverse NFT contract
* Need to ensure that approval is given to this Multiverse NFT contract for the delegate NFTs so that they can be transferred programmatically
*/
function bundle(
DelegateData[] memory delegateData,
uint256 multiverseTokenID
) external;
/**
* @dev Initializes a new bundle, mints a Multiverse NFT and assigns it to msg.sender
* Returns the token ID of a new Multiverse NFT
* Note - When a new Multiverse NFT is initialized, it is empty, it does not contain any delegate NFTs
*/
function initBundle(DelegateData[] memory delegateData) external;
}
abstract contract MultiverseNFT is
IMultiverseNFT,
Ownable,
ERC721Full,
IERC1155Receiver,
AccessControl
{
using SafeMath for uint256;
bytes32 public constant BUNDLER_ROLE = keccak256("BUNDLER_ROLE");
uint256 currentMultiverseTokenID;
mapping(uint256 => DelegateData[]) public multiverseNFTDelegateData;
mapping(uint256 => mapping(address => mapping(uint256 => uint256)))
public tokenBalances;
constructor(address bundlerAddress) {
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
_setupRole(BUNDLER_ROLE, msg.sender);
_setRoleAdmin(BUNDLER_ROLE, DEFAULT_ADMIN_ROLE);
_setupRole(BUNDLER_ROLE, bundlerAddress);
}
function delegateTokens(uint256 multiverseTokenID)
external
view
returns (DelegateData[] memory)
{
return multiverseNFTDelegateData[multiverseTokenID];
}
function initBundle(DelegateData[] memory delegateData) external {
uint256 tokenId = currentMultiverseTokenID.add(1);
for (uint256 i = 0; i < delegateData.length; i = i.add(1)) {
bool isERC721 = _isERC721(delegateData[i].contractAddress);
if (isERC721) {
require(
delegateData[i].quantity == 1,
"ERC721 quantity must be 1"
);
}
multiverseNFTDelegateData[tokenId].push(delegateData[i]);
}
_incrementMultiverseTokenID();
_safeMint(msg.sender, tokenId);
}
function bundle(
DelegateData[] memory delegateData,
uint256 multiverseTokenID
) external {
require(
hasRole(BUNDLER_ROLE, msg.sender) ||
ownerOf(multiverseTokenID) == msg.sender,
"msg.sender neither have bundler role nor multiversetoken owner"
);
_bundle(delegateData, multiverseTokenID);
}
function unbundle(
DelegateData[] memory delegateData,
uint256 multiverseTokenID
) external {
require(
ownerOf(multiverseTokenID) == msg.sender,
"msg.sender is not a multiversetoken owner"
);
for (uint256 i = 0; i < delegateData.length; i = i.add(1)) {
require(
_ensureDelegateBelongsToMultiverseNFT(
delegateData[i],
multiverseTokenID
),
"delegate not assigned to multiverse token"
);
uint256 balance = tokenBalances[multiverseTokenID][
delegateData[i].contractAddress
][delegateData[i].tokenId];
require(
delegateData[i].quantity <= balance,
"quantity exceeds balance"
);
require(
_ensureMultiverseContractOwnsDelegate(delegateData[i]),
"delegate not owned by contract"
);
address contractAddress = delegateData[i].contractAddress;
uint256 tokenId = delegateData[i].tokenId;
uint256 quantity = delegateData[i].quantity;
_updateDelegateBalances(delegateData[i], multiverseTokenID);
if (_isERC721(contractAddress)) {
ERC721Full erc721Instance = ERC721Full(contractAddress);
erc721Instance.transferFrom(address(this), msg.sender, tokenId);
} else if (_isERC1155(contractAddress)) {
ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress);
erc1155Instance.safeTransferFrom(
address(this),
msg.sender,
tokenId,
quantity,
""
);
}
}
emit Unbundled(multiverseTokenID, delegateData);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(AccessControl, ERC721Full, IERC165)
returns (bool)
{
return
AccessControl.supportsInterface(interfaceId) ||
ERC721Full.supportsInterface(interfaceId);
}
function _bundle(
DelegateData[] memory delegateData,
uint256 multiverseTokenID
) internal {
for (uint256 i = 0; i < delegateData.length; i = i.add(1)) {
require(
_ensureDelegateBelongsToMultiverseNFT(
delegateData[i],
multiverseTokenID
),
"delegate not assigned to multiversetoken"
);
require(
_ensureDelegateQuantityLimitForMMultiverseNFT(
delegateData[i],
multiverseTokenID
),
"delegate quantity assigned to multiversetoken exceeds"
);
address contractAddress = delegateData[i].contractAddress;
uint256 tokenId = delegateData[i].tokenId;
uint256 quantity = delegateData[i].quantity;
tokenBalances[multiverseTokenID][contractAddress][
tokenId
] = tokenBalances[multiverseTokenID][contractAddress][tokenId].add(
quantity
);
if (_isERC721(contractAddress)) {
require(
quantity == 1,
"ERC721 cannot have quantity more than 1"
);
ERC721Full erc721Instance = ERC721Full(contractAddress);
erc721Instance.transferFrom(msg.sender, address(this), tokenId);
} else if (_isERC1155(contractAddress)) {
ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress);
erc1155Instance.safeTransferFrom(
msg.sender,
address(this),
tokenId,
quantity,
""
);
}
}
emit Bundled(
multiverseTokenID,
delegateData,
ownerOf(multiverseTokenID)
);
}
function _ensureDelegateBelongsToMultiverseNFT(
DelegateData memory delegateData,
uint256 multiverseTokenID
) internal view returns (bool) {
DelegateData[] memory storedData = multiverseNFTDelegateData[
multiverseTokenID
];
for (uint256 i = 0; i < storedData.length; i = i.add(1)) {
if (
delegateData.contractAddress == storedData[i].contractAddress &&
delegateData.tokenId == storedData[i].tokenId
) {
return true;
}
}
return false;
}
function _ensureMultiverseContractOwnsDelegate(
DelegateData memory delegateData
) internal view returns (bool) {
if (_isERC721(delegateData.contractAddress)) {
ERC721Full erc721Instance = ERC721Full(
delegateData.contractAddress
);
if (address(this) == erc721Instance.ownerOf(delegateData.tokenId)) {
return true;
}
} else if (_isERC1155(delegateData.contractAddress)) {
ERC1155Supply erc1155Instance = ERC1155Supply(
delegateData.contractAddress
);
if (
erc1155Instance.balanceOf(
address(this),
delegateData.tokenId
) >= delegateData.quantity
) {
return true;
}
}
return false;
}
function _ensureDelegateQuantityLimitForMMultiverseNFT(
DelegateData memory delegateData,
uint256 multiverseTokenID
) internal view returns (bool) {
DelegateData[] memory storedData = multiverseNFTDelegateData[
multiverseTokenID
];
for (uint256 i = 0; i < storedData.length; i = i.add(1)) {
if (
delegateData.contractAddress == storedData[i].contractAddress &&
delegateData.tokenId == storedData[i].tokenId
) {
uint256 balance = tokenBalances[multiverseTokenID][
delegateData.contractAddress
][delegateData.tokenId];
if (
balance.add(delegateData.quantity) <= storedData[i].quantity
) {
return true;
}
return false;
}
}
}
function _updateDelegateBalances(
DelegateData memory delegateData,
uint256 multiverseTokenID
) internal returns (uint256) {
address contractAddress = delegateData.contractAddress;
uint256 tokenId = delegateData.tokenId;
tokenBalances[multiverseTokenID][contractAddress][
tokenId
] = tokenBalances[multiverseTokenID][contractAddress][tokenId].sub(
delegateData.quantity
);
return tokenBalances[multiverseTokenID][contractAddress][tokenId];
}
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external pure override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external pure override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
function _isERC1155(address contractAddress) internal view returns (bool) {
return IERC1155(contractAddress).supportsInterface(0xd9b67a26);
}
function _isERC721(address contractAddress) internal view returns (bool) {
return IERC721(contractAddress).supportsInterface(0x80ac58cd);
}
function _incrementMultiverseTokenID() internal {
currentMultiverseTokenID = currentMultiverseTokenID.add(1);
}
}