Skip to content

Latest commit

 

History

History
372 lines (295 loc) · 15.9 KB

10-erc721x.md

File metadata and controls

372 lines (295 loc) · 15.9 KB
title actions requireLogin material
Batch Minting Tokens
checkAnswer
hints
true
editor
language startingCode answer
sol
ZombieCard.sol ERC721XToken.sol
pragma solidity ^0.4.25; import "./ERC721XToken.sol"; import "./Ownable.sol"; contract ZombieCard is ERC721XToken { mapping(uint => uint) internal tokenIdToIndividualSupply; mapping(uint => uint) internal nftTokenIdToMouldId; uint nftTokenIdIndex = 1000000; event TokenAwarded(uint indexed tokenId, address claimer, uint amount); function name() external view returns (string) { return "ZombieCard"; } function symbol() external view returns (string) { return "ZCX"; } function individualSupply(uint _tokenId) public view returns (uint) { return tokenIdToIndividualSupply[_tokenId]; } // Start here function mintToken(uint _tokenId, uint _supply) public onlyOwner { require(!exists(_tokenId), "Error: Tried to mint duplicate token id"); _mint(_tokenId, msg.sender, _supply); tokenIdToIndividualSupply[_tokenId] = _supply; } function awardToken(uint _tokenId, address _to, uint _amount) public onlyOwner { require(exists(_tokenId), "TokenID has not been minted"); if (individualSupply(_tokenId) > 0) { require(_amount <= balanceOf(msg.sender, _tokenId), "Quantity greater than from balance"); _updateTokenBalance(msg.sender, _tokenId, _amount, ObjectLib.Operations.SUB); } _updateTokenBalance(_to, _tokenId, _amount, ObjectLib.Operations.ADD); emit TokenAwarded(_tokenId, _to, _amount); } function convertToNFT(uint _tokenId, uint _amount) public { require(tokenType[_tokenId] == FT); require(_amount <= balanceOf(msg.sender, _tokenId), "You do not own enough tokens"); _updateTokenBalance(msg.sender, _tokenId, _amount, ObjectLib.Operations.SUB); for (uint i = 0; i < _amount; i++) { _mint(nftTokenIdIndex, msg.sender); nftTokenIdToMouldId[nftTokenIdIndex] = _tokenId; nftTokenIdIndex++; } } function convertToFT(uint _tokenId) public { require(tokenType[_tokenId] == NFT); require(ownerOf(_tokenId) == msg.sender, "You do not own this token"); _updateTokenBalance(msg.sender, _tokenId, 0, ObjectLib.Operations.REPLACE); _updateTokenBalance(msg.sender, nftTokenIdToMouldId[_tokenId], 1, ObjectLib.Operations.ADD); emit TransferWithQuantity(address(this), msg.sender, nftTokenIdToMouldId[_tokenId], 1); } }
// Full implementation with all included files at https://github.com/loomnetwork/erc721x pragma solidity ^0.4.25; import "./../../Interfaces/ERC721X.sol"; import "./../../Interfaces/ERC721XReceiver.sol"; import "./ERC721XTokenNFT.sol"; import "openzeppelin-solidity/contracts/AddressUtils.sol"; import "./../../Libraries/ObjectsLib.sol"; // Additional features over NFT token that is compatible with batch transfers contract ERC721XToken is ERC721X, ERC721XTokenNFT { using ObjectLib for ObjectLib.Operations; using AddressUtils for address; bytes4 internal constant ERC721X_RECEIVED = 0x660b3370; bytes4 internal constant ERC721X_BATCH_RECEIVE_SIG = 0xe9e5be6a; event BatchTransfer(address from, address to, uint256[] tokenTypes, uint256[] amounts); modifier isOperatorOrOwner(address _from) { require((msg.sender == _from) || operators[_from][msg.sender], "msg.sender is neither _from nor operator"); _; } function implementsERC721X() public pure returns (bool) { return true; } /** * @dev transfer objects from different tokenIds to specified address * @param _from The address to BatchTransfer objects from. * @param _to The address to batchTransfer objects to. * @param _tokenIds Array of tokenIds to update balance of * @param _amounts Array of amount of object per type to be transferred. * Note: Arrays should be sorted so that all tokenIds in a same bin are adjacent (more efficient). */ function _batchTransferFrom(address _from, address _to, uint256[] _tokenIds, uint256[] _amounts) internal isOperatorOrOwner(_from) { // Requirements require(_tokenIds.length == _amounts.length, "Inconsistent array length between args"); require(_to != address(0), "Invalid recipient"); if (tokenType[_tokenIds[0]] == NFT) { tokenOwner[_tokenIds[0]] = _to; emit Transfer(_from, _to, _tokenIds[0]); } // Load first bin and index where the object balance exists (uint256 bin, uint256 index) = ObjectLib.getTokenBinIndex(_tokenIds[0]); // Balance for current bin in memory (initialized with first transfer) // Written with bad library syntax instead of as below to bypass stack limit error uint256 balFrom = ObjectLib.updateTokenBalance( packedTokenBalance[_from][bin], index, _amounts[0], ObjectLib.Operations.SUB ); uint256 balTo = ObjectLib.updateTokenBalance( packedTokenBalance[_to][bin], index, _amounts[0], ObjectLib.Operations.ADD ); // Number of transfers to execute uint256 nTransfer = _tokenIds.length; // Last bin updated uint256 lastBin = bin; for (uint256 i = 1; i < nTransfer; i++) { // If we're transferring an NFT we additionally should update the tokenOwner and emit the corresponding event if (tokenType[_tokenIds[i]] == NFT) { tokenOwner[_tokenIds[i]] = _to; emit Transfer(_from, _to, _tokenIds[i]); } (bin, index) = _tokenIds[i].getTokenBinIndex(); // If new bin if (bin != lastBin) { // Update storage balance of previous bin packedTokenBalance[_from][lastBin] = balFrom; packedTokenBalance[_to][lastBin] = balTo; // Load current bin balance in memory balFrom = packedTokenBalance[_from][bin]; balTo = packedTokenBalance[_to][bin]; // Bin will be the most recent bin lastBin = bin; } // Update memory balance balFrom = balFrom.updateTokenBalance(index, _amounts[i], ObjectLib.Operations.SUB); balTo = balTo.updateTokenBalance(index, _amounts[i], ObjectLib.Operations.ADD); } // Update storage of the last bin visited packedTokenBalance[_from][bin] = balFrom; packedTokenBalance[_to][bin] = balTo; // Emit batchTransfer event emit BatchTransfer(_from, _to, _tokenIds, _amounts); } function batchTransferFrom(address _from, address _to, uint256[] _tokenIds, uint256[] _amounts) public { // Batch Transfering _batchTransferFrom(_from, _to, _tokenIds, _amounts); } /** * @dev transfer objects from different tokenIds to specified address * @param _from The address to BatchTransfer objects from. * @param _to The address to batchTransfer objects to. * @param _tokenIds Array of tokenIds to update balance of * @param _amounts Array of amount of object per type to be transferred. * @param _data Data to pass to onERC721XReceived() function if recipient is contract * Note: Arrays should be sorted so that all tokenIds in a same bin are adjacent (more efficient). */ function safeBatchTransferFrom( address _from, address _to, uint256[] _tokenIds, uint256[] _amounts, bytes _data ) public { // Batch Transfering _batchTransferFrom(_from, _to, _tokenIds, _amounts); // Pass data if recipient is contract if (_to.isContract()) { bytes4 retval = ERC721XReceiver(_to).onERC721XBatchReceived( msg.sender, _from, _tokenIds, _amounts, _data ); require(retval == ERC721X_BATCH_RECEIVE_SIG); } } function transfer(address _to, uint256 _tokenId, uint256 _amount) public { _transferFrom(msg.sender, _to, _tokenId, _amount); } function transferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) public { _transferFrom(_from, _to, _tokenId, _amount); } function _transferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) internal isOperatorOrOwner(_from) { require(tokenType[_tokenId] == FT); require(_amount <= balanceOf(_from, _tokenId), "Quantity greater than from balance"); require(_to != address(0), "Invalid to address"); _updateTokenBalance(_from, _tokenId, _amount, ObjectLib.Operations.SUB); _updateTokenBalance(_to, _tokenId, _amount, ObjectLib.Operations.ADD); emit TransferWithQuantity(_from, _to, _tokenId, _amount); } function safeTransferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount) public { safeTransferFrom(_from, _to, _tokenId, _amount, ""); } function safeTransferFrom(address _from, address _to, uint256 _tokenId, uint256 _amount, bytes _data) public { _transferFrom(_from, _to, _tokenId, _amount); require( checkAndCallSafeTransfer(_from, _to, _tokenId, _amount, _data), "Sent to a contract which is not an ERC721X receiver" ); } function _mint(uint256 _tokenId, address _to, uint256 _supply) internal { // If the token doesn't exist, add it to the tokens array if (!exists(_tokenId)) { tokenType[_tokenId] = FT; allTokens.push(_tokenId); } else { // if the token exists, it must be a FT require(tokenType[_tokenId] == FT, "Not a FT"); } _updateTokenBalance(_to, _tokenId, _supply, ObjectLib.Operations.ADD); emit TransferWithQuantity(address(this), _to, _tokenId, _supply); } function checkAndCallSafeTransfer( address _from, address _to, uint256 _tokenId, uint256 _amount, bytes _data ) internal returns (bool) { if (!_to.isContract()) { return true; } bytes4 retval = ERC721XReceiver(_to).onERC721XReceived( msg.sender, _from, _tokenId, _amount, _data); return(retval == ERC721X_RECEIVED); } }
pragma solidity ^0.4.25; import "./ERC721XToken.sol"; import "./Ownable.sol"; contract ZombieCard is ERC721XToken { mapping(uint => uint) internal tokenIdToIndividualSupply; mapping(uint => uint) internal nftTokenIdToMouldId; uint nftTokenIdIndex = 1000000; event TokenAwarded(uint indexed tokenId, address claimer, uint amount); function name() external view returns (string) { return "ZombieCard"; } function symbol() external view returns (string) { return "ZCX"; } function individualSupply(uint _tokenId) public view returns (uint) { return tokenIdToIndividualSupply[_tokenId]; } function batchMintTokens (uint[] _tokenIds, uint[] _tokenSupplies) external onlyOwner { for(uint i = 0; i < _tokenIds.length; i++) { mintToken(_tokenIds[i], _tokenSupplies[i]); } } function mintToken(uint _tokenId, uint _supply) public onlyOwner { require(!exists(_tokenId), "Error: Tried to mint duplicate token id"); _mint(_tokenId, msg.sender, _supply); tokenIdToIndividualSupply[_tokenId] = _supply; } function awardToken(uint _tokenId, address _to, uint _amount) public onlyOwner { require(exists(_tokenId), "TokenID has not been minted"); if (individualSupply(_tokenId) > 0) { require(_amount <= balanceOf(msg.sender, _tokenId), "Quantity greater than from balance"); _updateTokenBalance(msg.sender, _tokenId, _amount, ObjectLib.Operations.SUB); } _updateTokenBalance(_to, _tokenId, _amount, ObjectLib.Operations.ADD); emit TokenAwarded(_tokenId, _to, _amount); } function convertToNFT(uint _tokenId, uint _amount) public { require(tokenType[_tokenId] == FT); require(_amount <= balanceOf(msg.sender, _tokenId), "You do not own enough tokens"); _updateTokenBalance(msg.sender, _tokenId, _amount, ObjectLib.Operations.SUB); for (uint i = 0; i < _amount; i++) { _mint(nftTokenIdIndex, msg.sender); nftTokenIdToMouldId[nftTokenIdIndex] = _tokenId; nftTokenIdIndex++; } } function convertToFT(uint _tokenId) public { require(tokenType[_tokenId] == NFT); require(ownerOf(_tokenId) == msg.sender, "You do not own this token"); _updateTokenBalance(msg.sender, _tokenId, 0, ObjectLib.Operations.REPLACE); _updateTokenBalance(msg.sender, nftTokenIdToMouldId[_tokenId], 1, ObjectLib.Operations.ADD); emit TransferWithQuantity(address(this), msg.sender, nftTokenIdToMouldId[_tokenId], 1); } }

We are almost done! Let's wrap up our contract with one more function, then do some review.

The last thing we're going to implement is batch minting tokens. This function will allow us to mint many tokens in a single function call — and is one of the reasons ERC721x allows us to save so much on gas fees on Ethereum.

With everything we've set up until now, the function is quite simple to implement.

Put It to the test

  1. Right above mintToken, declare a function called batchMintTokens. It will take 2 arguments, a uint[] array named _tokenIds, and a uint[] array named _tokenSupplies. It should be external and onlyOwner.

  2. The function should iterate through a for loop starting with uint i = 0, and continuing while i < _tokenIds.length.

  3. Inside the for loop, we'll simply call mintToken with the arguments _tokenIds[i] and _tokenSupplies[i]

And that's it! We now have a function that can be passed an array of tokenIds and supplies, and mint millions of tokens at once. Simple, right?