Skip to content

Latest commit

 

History

History
298 lines (225 loc) · 12.6 KB

03-erc721x.md

File metadata and controls

298 lines (225 loc) · 12.6 KB
title actions requireLogin material
多重同质化代币供应量
checkAnswer
hints
true
editor
language startingCode answer
sol
ZombieCard.sol ERC721XToken.sol
pragma solidity ^0.4.25; import "./ERC721XToken.sol"; contract ZombieCard is ERC721XToken { // 1. Create mapping here function name() external view returns (string) { return "ZombieCard"; } function symbol() external view returns (string) { return "ZCX"; } // 2. Define function 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(_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"; contract ZombieCard is ERC721XToken { mapping(uint => uint) internal tokenIdToIndividualSupply; 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); } }

对于普通的ERC20或ERC721代币,我们有**代币供应**的概念 —— 是指总共存在多少那样的代币。

对于ERC721x多重同质化代币,我们实际上可以在同一合约中定义多个代币 —— 每个代币都有自己的“供应量”(存在的代币数量)。

但是当谈到游戏物品时,对于某些物品,你会想要无限量供应。否则,如果你的游戏只能拥有1000把剑,你将受限只能拥有1000名玩家 —— 或者对于加入较晚的玩家来说游戏不会很好玩!

在“僵尸战场”中,我们有两种不同类型的卡牌 —— 固定供应量的卡牌和无限量供应的卡牌。

  • 标准版卡牌:就像典型游戏物品一样,它们无限量供应 —— 这样越来越多的玩家可以继续加入游戏,每个人都有机会获得它们并建立一个有竞争力的牌组。用金融术语来说,我们称这些卡牌为“通胀的”。

  • 限量版卡牌:它们是标准版卡牌的美工变体,有固定供应量。是看起来比标准版更酷的稀有收藏品。因为在所有用户中可能只存在几百张,这种稀缺性提升了它们的价值。

在本章中,我们将逐步向你介绍如何在你的代币合约中实现这两种类型的卡牌。

实现过程

如你所见,这两种卡牌的主要差别就是totalSupply —— 即存在的卡牌总量,

限量版卡牌将会有固定数量的totalSupply,即uint,而标准版卡牌有无限量的totalSupply

由于无限量不是一个我们可以设置供应量的数字,对于标准版卡牌,我们可以将其设置为0作为占位符。 然后在我们代码的其他地方,我们可以检查供应量是否等于0,如果是,我们就知道这是一个无上限供应量的标准版卡牌。

在下一章中,我们将创建一个函数用来在我们的合约中铸造新代币。 但首先,让我们设置一些函数来跟踪每张卡牌的totalSupply

实战演习

  1. 创建一个名为tokenIdToIndividualSupply映射 。 它会将一个uint 映射到uint,并且应该是内部的

  2. 创建一个名为individualSupply函数。 它应该是公共视图,将uint _tokenId作为其输入,并返回一个uint

  3. 对于函数体,它应该只返回tokenIdToIndividualSupply(_tokenId)