Skip to content

Latest commit

 

History

History
372 lines (295 loc) · 16.2 KB

10-erc721x.md

File metadata and controls

372 lines (295 loc) · 16.2 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"; 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 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); } }

거의 끝나가는군! 이제 우리의 컨트랙트에 함수 하나를 더 추가해서 마무리한 후 뒤에 복습해볼걸세.

우리가 마지막으로 구현할 것은 토큰 일괄 발행 이라네. 이 함수는 한 번의 함수 호출로 많은 토큰을 발행할 수 있게 해준다네 — 그리고 이것이 ERC721x가 이더리움의 가스 비용을 많이 절약할 수 있게 해주는 이유이기도 하지.

이 함수는 우리가 지금까지 짜둔 코드로 꽤 간단하게 구현할 수 있다네.

직접 해보기

  1. mintToken 바로 위에 batchMintTokens라는 이름의 함수를 선언하게. 이 함수는 2개의 인자를 가지는데, 하나는 uint[] 배열인 _tokenIds이고, 나머지는 uint[] 배열인 _tokenSupplies라네. 이 함수는 external이면서 onlyOwner이어야 하네.

  2. 이 함수에는 uint i = 0에서 시작해서 i < _tokenIds.length를 만족하는 동안 반복하는 for 반복문이 있어야 하네.

  3. for 반복문 안에서는 간단히 mintToken 함수를 _tokenIds[i]_tokenSupplies[i]를 인자로 전달하여 호출할 것이네.

이게 전부라네! 이제 tokenId 배열과 공급량을 전달받아서 한 번에 수백만 개의 토큰을 생성할 수 있는 함수가 생겼다네. 간단하지 않은가?