ERC721 官方简介是:A standard interface for non-fungible tokens, also known as deeds.也叫非同质代币,或者不可置换代币(NFTs)。提到ERC721,一个好理解的例子就是CryptoKitties 迷恋猫,每一只猫都是独一无二的拥有不一样基因,有收藏价值属性。ERC721对于虚拟资产收藏品领域会有很好的应用价值和市场需求。javascript
它和我写的上一篇《OpenZeppelin ERC20源码分析》介绍的ERC20有所不一样,ERC721最小的单位为1没法再分割,表明独一无二的,针对不可置换的Token的智能合约标准接口。从 ERC721标准草案中能够看到,兼容ERC20的方法有4个:name,symbol,totalSupply,balanceOf 添加的新方法:ownerOf,takeOwnership ERC721还重写了approve和transfer。java
分析OpenZeppelin ERC721源码前一样我画了一个继承和调用关系的思惟导图,能够帮助更容易地看源码。git
pragma solidity ^0.4.23; /** * @title ERC721 标准的基本接口 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Basic { event Transfer( address indexed _from, address indexed _to, uint256 _tokenId ); event Approval( address indexed _owner, address indexed _approved, uint256 _tokenId ); event ApprovalForAll( address indexed _owner, address indexed _operator, bool _approved ); function balanceOf(address _owner) public view returns (uint256 _balance); function ownerOf(uint256 _tokenId) public view returns (address _owner); function exists(uint256 _tokenId) public view returns (bool _exists); function approve(address _to, uint256 _tokenId) public; function getApproved(uint256 _tokenId) public view returns (address _operator); function setApprovalForAll(address _operator, bool _approved) public; function isApprovedForAll(address _owner, address _operator) public view returns (bool); function transferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom(address _from, address _to, uint256 _tokenId) public; function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public; }
ERC721Basic 合约定义了基本的接口方法:github
同时还定义了Transfer
Approval
ApprovalForAll
在后面的ERC721实现的代码中再来看事件的触发。数组
pragma solidity ^0.4.23; import "./ERC721Basic.sol"; /** * @title ERC-721 标准的基本接口, 可选的枚举扩展 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Enumerable is ERC721Basic { function totalSupply() public view returns (uint256); function tokenOfOwnerByIndex( address _owner, uint256 _index ) public view returns (uint256 _tokenId); function tokenByIndex(uint256 _index) public view returns (uint256); } /** * @title ERC-721 ERC-721 标准的基本接口, 可选的元数据扩展 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Metadata is ERC721Basic { function name() public view returns (string _name); function symbol() public view returns (string _symbol); function tokenURI(uint256 _tokenId) public view returns (string); } /** * @title ERC-721 标准的基本接口,完整实现接口 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { }
ERC721 合约继承了 ERC721Basic 的基础上,添加枚举和元数据的扩展。安全
ERC721Enumerable枚举扩展可使代币更具备可访问性:app
ERC721Metadata元数据扩展哦用来描述合约元信息函数
pragma solidity ^0.4.23; import "./ERC721Basic.sol"; import "./ERC721Receiver.sol"; import "../../math/SafeMath.sol"; import "../../AddressUtils.sol"; /** * @title ERC721 标准基本实现 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721BasicToken is ERC721Basic { using SafeMath for uint256; using AddressUtils for address; // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 constant ERC721_RECEIVED = 0xf0b9e5ba; // token ID 到 持有人owner的映射 mapping (uint256 => address) internal tokenOwner; // token ID 到受权地址address的映射 mapping (uint256 => address) internal tokenApprovals; // 持有人到持有的token数量的映射 mapping (address => uint256) internal ownedTokensCount; // 持有人到操做人受权的映射 mapping (address => mapping (address => bool)) internal operatorApprovals; /** * @dev 确保msg.sender是tokenId的持有人 * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender */ modifier onlyOwnerOf(uint256 _tokenId) { require(ownerOf(_tokenId) == msg.sender); _; } /** * @dev 经过检查msg.sender是不是代币的持有人,被受权或者操做人来确保msg.sender能够交易一个token * @param _tokenId uint256 ID of the token to validate */ modifier canTransfer(uint256 _tokenId) { require(isApprovedOrOwner(msg.sender, _tokenId)); _; } /** * @dev 获取持有者的代币总数 * @param _owner address to query the balance of * @return uint256 representing the amount owned by the passed address */ function balanceOf(address _owner) public view returns (uint256) { require(_owner != address(0)); return ownedTokensCount[_owner]; } /** * @dev 根据token ID获取持有者 * @param _tokenId uint256 ID of the token to query the owner of * @return owner address currently marked as the owner of the given token ID */ function ownerOf(uint256 _tokenId) public view returns (address) { address owner = tokenOwner[_tokenId]; require(owner != address(0)); return owner; } /** * @dev 指定的token是否存在 * @param _tokenId uint256 ID of the token to query the existence of * @return whether the token exists */ function exists(uint256 _tokenId) public view returns (bool) { address owner = tokenOwner[_tokenId]; return owner != address(0); } /** * @dev 批准另外一我的address来交易指定的代币 * @dev 0 address 表示没有受权的地址 * @dev 给定的时间内,一个token只能有一个批准的地址 * @dev 只有token的持有者或者受权的操做人才能够调用 * @param _to address to be approved for the given token ID * @param _tokenId uint256 ID of the token to be approved */ function approve(address _to, uint256 _tokenId) public { address owner = ownerOf(_tokenId); require(_to != owner); require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); if (getApproved(_tokenId) != address(0) || _to != address(0)) { tokenApprovals[_tokenId] = _to; emit Approval(owner, _to, _tokenId); } } /** * @dev 获取token被受权的地址,若是没有设置地址则为0 * @param _tokenId uint256 ID of the token to query the approval of * @return address currently approved for the given token ID */ function getApproved(uint256 _tokenId) public view returns (address) { return tokenApprovals[_tokenId]; } /** * @dev 设置或者取消对操做人的受权 * @dev 一个操做人能够表明他们转让发送者的全部token * @param _to operator address to set the approval * @param _approved representing the status of the approval to be set */ function setApprovalForAll(address _to, bool _approved) public { require(_to != msg.sender); operatorApprovals[msg.sender][_to] = _approved; emit ApprovalForAll(msg.sender, _to, _approved); } /** * @dev 查询是否操做人被指定的持有者受权 * @param _owner 要查询的受权人地址 * @param _operator 要查询的受权操做人地址 * @return bool whether the given operator is approved by the given owner */ function isApprovedForAll( address _owner, address _operator ) public view returns (bool) { return operatorApprovals[_owner][_operator]; } /** * @dev 将指定的token全部权转移给另一个地址 * @dev 不鼓励使用这个方法,尽可能使用`safeTransferFrom` * @dev 要求 msg.sender 必须为全部者,已受权或者操做人 * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred */ function transferFrom( address _from, address _to, uint256 _tokenId ) public canTransfer(_tokenId) { require(_from != address(0)); require(_to != address(0)); clearApproval(_from, _tokenId); removeTokenFrom(_from, _tokenId); addTokenTo(_to, _tokenId); emit Transfer(_from, _to, _tokenId); } /** * @dev 更安全的方法,将指定的token全部权转移给另一个地址 * @dev 若是目标地址是一个合约,必须实现 `onERC721Received`,这个要求安全交易并返回值 `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; 不然交易被还原 * @dev 要求 msg.sender 必须为全部者,已受权或者操做人 * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred */ function safeTransferFrom( address _from, address _to, uint256 _tokenId ) public canTransfer(_tokenId) { safeTransferFrom(_from, _to, _tokenId, ""); } /** * @dev 更安全的方法,将指定的token全部权转移给另一个地址 * @dev 若是目标地址是一个合约,必须实现 `onERC721Received`,这个要求安全交易并返回值 `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`; 不然交易被还原 * @dev 要求 msg.sender 必须为全部者,已受权或者操做人 * @param _from current owner of the token * @param _to address to receive the ownership of the given token ID * @param _tokenId uint256 ID of the token to be transferred * @param _data bytes data to send along with a safe transfer check */ function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes _data ) public canTransfer(_tokenId) { transferFrom(_from, _to, _tokenId); require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); } /** * @dev 返回给定的spender是否能够交易一个给定的token * @param _spender address of the spender to query * @param _tokenId uint256 ID of the token to be transferred * @return bool whether the msg.sender is approved for the given token ID, * is an operator of the owner, or is the owner of the token */ function isApprovedOrOwner( address _spender, uint256 _tokenId ) internal view returns (bool) { address owner = ownerOf(_tokenId); return ( _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender) ); } /** * @dev 增发一个新token的内部方法 * @dev 若是增发的token已经存在则撤销 * @param _to The address that will own the minted token * @param _tokenId uint256 ID of the token to be minted by the msg.sender */ function _mint(address _to, uint256 _tokenId) internal { require(_to != address(0)); addTokenTo(_to, _tokenId); emit Transfer(address(0), _to, _tokenId); } /** * @dev 销毁一个token的内部方法 * @dev 若是token不存在则撤销 * @param _tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address _owner, uint256 _tokenId) internal { clearApproval(_owner, _tokenId); removeTokenFrom(_owner, _tokenId); emit Transfer(_owner, address(0), _tokenId); } /** * @dev 清除当前的给定token的受权,内部方法 * @dev 若是给定地址不是token的持有者则撤销 * @param _owner owner of the token * @param _tokenId uint256 ID of the token to be transferred */ function clearApproval(address _owner, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _owner); if (tokenApprovals[_tokenId] != address(0)) { tokenApprovals[_tokenId] = address(0); emit Approval(_owner, address(0), _tokenId); } } /** * @dev 内部方法,将给定的token添加到给定地址列表中 * @param _to address 指定token的新全部者 * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address */ function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; ownedTokensCount[_to] = ownedTokensCount[_to].add(1); } /** * @dev 内部方法,将给定的token从地址列表中移除 * @param _from address 给定token的以前持有中地址 * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address */ function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); tokenOwner[_tokenId] = address(0); } /** * @dev 内部函数,调用目标地址上的 `onERC721Received` * @dev 若是目标地址不是合同则不执行调用 * @param _from address representing the previous owner of the given token ID * @param _to target address that will receive the tokens * @param _tokenId uint256 ID of the token to be transferred * @param _data bytes optional data to send along with the call * @return whether the call correctly returned the expected magic value */ function checkAndCallSafeTransfer( address _from, address _to, uint256 _tokenId, bytes _data ) internal returns (bool) { if (!_to.isContract()) { return true; } bytes4 retval = ERC721Receiver(_to).onERC721Received( _from, _tokenId, _data); return (retval == ERC721_RECEIVED); } }
ERC721BasicToken 实现了ERC721Basic合约定义的接口方法,主要对token的持有人的一个添加和修改,以及受权和交易的管理,实现了基本的非同质化token的业务逻辑。具体方法实现并不难,就是对映射的公有变量的管理,可是对于权限和安全验证值得关注,好比函数修改器还有require。源码分析
pragma solidity ^0.4.23; import "./ERC721.sol"; import "./ERC721BasicToken.sol"; /** * @title 完整 ERC721 Token * 该实现包括全部ERC721标准必须的和可选的方法,此外还包括使用操做者批准全部功能 * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721Token is ERC721, ERC721BasicToken { // 代币名称 string internal name_; // 代币符号 string internal symbol_; // 全部者到全部者拥有的代币列表的映射 mapping(address => uint256[]) internal ownedTokens; // 全部者代币列表中代币ID到索引的映射 mapping(uint256 => uint256) internal ownedTokensIndex; // 保存全部代币ID的数组,用于枚举 uint256[] internal allTokens; // allTokens数组中代币ID到索引的映射 mapping(uint256 => uint256) internal allTokensIndex; // 可选的代币资源URIs映射 mapping(uint256 => string) internal tokenURIs; /** * @dev Constructor function */ constructor(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; } /** * @dev 获取代币名称 * @return string representing the token name */ function name() public view returns (string) { return name_; } /** * @dev 获取代币符号 * @return string representing the token symbol */ function symbol() public view returns (string) { return symbol_; } /** * @dev 根据_tokenId返回对应的资源URI * @dev 若是token不存在异常返回空字符串 * @param _tokenId uint256 ID of the token to query */ function tokenURI(uint256 _tokenId) public view returns (string) { require(exists(_tokenId)); return tokenURIs[_tokenId]; } /** * @dev 获取token id 经过给定的token列表中的索引 * @param _owner address owning the tokens list to be accessed * @param _index uint256 representing the index to be accessed of the requested tokens list * @return uint256 token ID at the given index of the tokens list owned by the requested address */ function tokenOfOwnerByIndex( address _owner, uint256 _index ) public view returns (uint256) { require(_index < balanceOf(_owner)); return ownedTokens[_owner][_index]; } /** * @dev 获取合约存储的token总数 * @return uint256 representing the total amount of tokens */ function totalSupply() public view returns (uint256) { return allTokens.length; } /** * @dev 根据token 索引值获取合约中token的 * @dev 若是索引大于等于token总数则撤销 * @param _index uint256 representing the index to be accessed of the tokens list * @return uint256 token ID at the given index of the tokens list */ function tokenByIndex(uint256 _index) public view returns (uint256) { require(_index < totalSupply()); return allTokens[_index]; } /** * @dev 内部方法,给存在token添加token URI * @dev Reverts if the token ID does not exist * @param _tokenId uint256 ID of the token to set its URI * @param _uri string URI to assign */ function _setTokenURI(uint256 _tokenId, string _uri) internal { require(exists(_tokenId)); tokenURIs[_tokenId] = _uri; } /** * @dev 内部方法,添加token ID 到给定的地址的列表中 * @param _to address 给定token ID的新的持有者 * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address */ function addTokenTo(address _to, uint256 _tokenId) internal { // 调用父合约的addTokenTo super.addTokenTo(_to, _tokenId); uint256 length = ownedTokens[_to].length; ownedTokens[_to].push(_tokenId); //当前的长度做为索引 ownedTokensIndex[_tokenId] = length; } /** * @dev 内部方法,从一个给定地址的列表中移除token * @param _from address 给定token ID的以前的持有者address * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address */ function removeTokenFrom(address _from, uint256 _tokenId) internal { // 调用父合约的移除方法 super.removeTokenFrom(_from, _tokenId); // 获取token的索引 uint256 tokenIndex = ownedTokensIndex[_tokenId]; // 获取持有人token的最后一个token索引 uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); // 获取最后一个token uint256 lastToken = ownedTokens[_from][lastTokenIndex]; //将最后一个token放到被删除的索引位置,lastTokenIndex置0 ownedTokens[_from][tokenIndex] = lastToken; ownedTokens[_from][lastTokenIndex] = 0; // 注意这里须要处理单元素数组,tokenIndex和lastTokenIndex都将置0.而后能够确保将ownedTokens列表中删除_tokenId,首先将lastToken换到第一个位置,而后删除列表最后位置的元素 ownedTokens[_from].length--; ownedTokensIndex[_tokenId] = 0; ownedTokensIndex[lastToken] = tokenIndex; } /** * @dev 内部方法,增发一个新的token * @dev 若是token已经存在了就撤销 * @param _to address the beneficiary that will own the minted token * @param _tokenId uint256 ID of the token to be minted by the msg.sender */ function _mint(address _to, uint256 _tokenId) internal { super._mint(_to, _tokenId); allTokensIndex[_tokenId] = allTokens.length; allTokens.push(_tokenId); } /** * @dev 内部方法,销毁一个指定的token * @dev token不存在则撤销 * @param _owner owner of the token to burn * @param _tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address _owner, uint256 _tokenId) internal { super._burn(_owner, _tokenId); // 清除资源URI if (bytes(tokenURIs[_tokenId]).length != 0) { delete tokenURIs[_tokenId]; } // 作全部的token数组后续处理 uint256 tokenIndex = allTokensIndex[_tokenId]; uint256 lastTokenIndex = allTokens.length.sub(1); uint256 lastToken = allTokens[lastTokenIndex]; // 能够参考增发removeTokenFrom allTokens[tokenIndex] = lastToken; allTokens[lastTokenIndex] = 0; allTokens.length--; allTokensIndex[_tokenId] = 0; allTokensIndex[lastToken] = tokenIndex; } }
ERC721Token实现了完整的ERC721标准,在继承了ERC721BasicToken的基础上增长了一些token的操做,主要在包括token的元数据,资源URI,增发销毁,还有就是token索引的映射关系。对于具体实现咱们根据实际状况经过继承ERC721BasicToken或者ERC721Token来添加本身的业务逻辑。ui
OpenZeppelin ERC721源码分析到这里就结束了。
转载请注明: 转载自Ryan是菜鸟 | LNMP技术栈笔记
若是以为本篇文章对您十分有益,何不 打赏一下
本文连接地址: OpenZeppelin ERC721源码分析