Skip to content

Latest commit

 

History

History
348 lines (267 loc) · 14.7 KB

06-erc721x.md

File metadata and controls

348 lines (267 loc) · 14.7 KB
title actions requireLogin material
Minting- continued
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; 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 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"); // Start here } }
// 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(msg.sender, _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; 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 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 remaining cards"); _updateTokenBalance(msg.sender, _tokenId, _amount, ObjectLib.Operations.SUB); } _updateTokenBalance(_to, _tokenId, _amount, ObjectLib.Operations.ADD); emit TokenAwarded(_tokenId, _to, _amount); } }

To understand what should happen in awardToken to transfer the token to a user, let's take a look at what happens in a normal transfer function in ERC721XToken.sol:

Transfering Fungible Tokens

The code for the transfer function for a Fungible Token (FT) in ERC721XToken.sol is as follows:

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);
}

As you can see, the bulk of this function is simply reducing the token balance of the sender by _amount, and increasing the token balance of the receiver by the same _amount.

But because we also have Standard Edition cards, our awardToken function logic will need to be slightly different.

Remember, we also have Standard Edition cards, where the supply is infinity. We're storing this as a supply of 0, and we don't want to subtract _amount from 0.

So our awardToken logic should check if this is a Limited Edition card or a Standard Edition card with a supply of 0 — and only subtract the token balance for Limited Edition cards.

Put It to the Test

Continuing our implementation...

  1. Create an if statement that checks if individualSupply(_tokenId) is greater than 0. If this condition is true, that means this token is a Limited Edition card with a fixed supply.

  2. If the if statement is true, we want to first make sure the game server has enough of this card left to send to the user and throw an error otherwise. We can check this with the line: require(_amount <= balanceOf(msg.sender, _tokenId), "Quantity greater than remaining cards");

  3. Still inside the if statement, now let's reduce the game server (msg.sender)'s balance of _tokenId by _amount. Reference the sample code above in _transferFrom() to see how to call _updateTokenBalance().

  4. After the if statement, now we want to update the token balance of the _to address to increase it by _amount. This goes outside the if statement, because we want to do this both for Limited Edition cards and Standard Edition cards.

You can reference the sample code above to see how to call this.

  1. Lastly, we'll want to fire the event we created, with emit TokenAwarded(_tokenId, _to, _amount);.

Woo! That's a lot of code. Hope you got through all that ok — but feel free to click "show answer" if you're having trouble getting it perfect.