Skip to content

Latest commit

 

History

History
392 lines (306 loc) · 16.2 KB

04-erc721x.md

File metadata and controls

392 lines (306 loc) · 16.2 KB
title actions requireLogin material
발행하기
checkAnswer
hints
true
editor
language startingCode answer
sol
ZombieCard.sol ERC721XToken.sol Ownable.sol
pragma solidity ^0.4.25; import "./ERC721XToken.sol"; import "./Ownable.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]; } // 여기서 시작하게 }
// 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; /** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address private _owner; event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ constructor() internal { _owner = msg.sender; emit OwnershipTransferred(address(0), _owner); } /** * @return the address of the owner. */ function owner() public view returns(address) { return _owner; } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { require(isOwner()); _; } /** * @return true if `msg.sender` is the owner of the contract. */ function isOwner() public view returns(bool) { return msg.sender == _owner; } /** * @dev Allows the current owner to relinquish control of the contract. * @notice Renouncing to ownership will leave the contract without an owner. * It will not be possible to call the functions with the `onlyOwner` * modifier anymore. */ function renounceOwnership() public onlyOwner { emit OwnershipTransferred(_owner, address(0)); _owner = address(0); } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { _transferOwnership(newOwner); } /** * @dev Transfers control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function _transferOwnership(address newOwner) internal { require(newOwner != address(0)); emit OwnershipTransferred(_owner, newOwner); _owner = newOwner; } }
pragma solidity ^0.4.25; import "./ERC721XToken.sol"; import "./Ownable.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]; } 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; } }

훌륭하군! 이제 우리의 첫 카드들을 발행할 시간이네.

카드 발행을 위해서 우리는 mintToken이라는 함수를 구현할 것이네. 새로운 카드 템플릿들을 실제로 만들어내기 위해 컨트랙트 소유자가 호출할 수 있는 함수이지.

생성 시점에, 각 카드 템플릿에는 식별자로서 고유한 _tokenId를 전달해야 하고, _supply도 전달해야 하네 - 만들어지는 카드의 개수이지.

우리는 컨트랙트의 소유자만 mintToken 함수를 호출할 수 있기를 원하기 때문에, Ownable.sol을 포함시키고 우리 컨트랙트에 import 구문을 추가하였네(우리가 레슨3 챕터2에서 다뤘듯이 말이지). 이제 자네는 onlyOwner 제어자를 우리가 만들려 하는 mintToken 함수에 사용할 수 있네.

우리가 토큰을 발행할 때 그 뒤에서 어떤 일들이 일어나는지 알아보도록 하지.

_mint

우리가 상속하는 ERC721XToken 컨트랙트에는 _mint라는 이름의 함수가 있네.

function _mint(uint256 _tokenId, address _to, uint256 _supply) internal;

이는 컨트랙트에서 새로운 카드 템플릿을 정의할 때 쓰이는 internal 함수이지.

이제 우리의 게임 서버(또는 사이드체인 - 자네가 Loom을 통해 완전한 블록체인 기반 게임을 만든다면 말이지)에서 새로운 토큰들을 만들기 위해 호출할 수 있는 public 함수를 만들도록 하겠네.

직접 해보기

  1. mintToken이라는 이름의 함수를 만들게. 이 함수는 2개의 인자로 uint _tokenIduint _supply를 받아야 하네. 또 public이어야 하고, onlyOwner 제어자를 가져야 하네.

  2. 희귀한 한정판 카드라고 말하려면 그 양이 확실히 제한되어 있어야 하는 것이지. 이는 우리가 한 토큰을 한 번만 발행할 수 있어야 하고, 그 공급량을 다시 늘릴 수는 없어야 하는 것이네.

우리는 이를 require 구문을 사용하여 보장할 수 있지. 해당 토큰이 이미 존재한다면 에러를 내며 종료되도록 해서 말이네. 이렇게 하려면 우리가 ERC721XToken.sol에서 정의한 함수를 쓰기 전에 다음 한 줄의 코드를 쓰면 되네: require(!exists(_tokenId), "Error: Tried to mint duplicate token id");

이 코드를 우리 함수의 첫 번째 줄에 두게.

  1. 다음으로, 위의 예시에서처럼 internal 함수인 _mint를 호출할 것이네. 이 함수에는 인자로 _tokenId, msg.sender, _supply 3개를 전달할 것이네.

  2. 마지막으로, 우리의 mapping tokenIdToIndividualSupply를 이 토큰의 공급량을 저장하도록 업데이트해야 하네. 이 매핑의 _tokenId에 해당하는 값을 _supply로 설정하도록 업데이트하게.

이게 끝이네! 이제 우리는 컨트랙트(우리 게임)의 소유자가 새 카드를 만들 때 호출할 수 있는 퍼블릭 mintToken 함수를 가지게 되었네.