2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Solidity - 智能合约标准ERC-721

Solidity - 智能合约标准ERC-721

时间:2022-04-17 14:06:53

相关推荐

Solidity - 智能合约标准ERC-721

详情参见:EIP-721: Non-Fungible Token Standard

ERC721 定义了一种以太坊生态中不可分割的、具有唯一性的Token交互、流通的接囗规范。官网简要称为 Non-Fungible Token Standard(简称NFT标准规范),即非同质化Token(或不可替代的Token)。

ERC721合约标准提供了在实现ERC721 Token 时必须要遵守的协议,要求每个ERC721标准合约需要实现ERC721接囗及ERC165接囗,ERC165可参见:Solidity - 智能合约标准ERC-165_ling1998的博客-CSDN博客

NFT标准接囗规范具有如下特性:

1、在该合约内,tokenId唯一

2、tokenId只能被一个owner所拥有

3、一个owner可以拥有多个NFT,balance函数只能查询owner拥有多少个token

4、NFT可通过approve、transfer等接囗方法进行流通,即NFT所有权转移

以下实现ERC721,共包含4个.sol文件:

(1)IERC721.sol - 接囗

pragma solidity ^0.6.0;/// @title ERC-721 Non-Fungible Token Standard/// @dev See /EIPS/eip-721/// Note: the ERC-165 identifier for this interface is 0x80ac58cd.interface IERC721 /* is ERC165 */ {/// @dev This emits when ownership of any NFT changes by any mechanism./// This event emits when NFTs are created (`from` == 0) and destroyed/// (`to` == 0). Exception: during contract creation, any number of NFTs/// may be created and assigned without emitting Transfer. At the time of/// any transfer, the approved address for that NFT (if any) is reset to none.event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);/// @dev This emits when the approved address for an NFT is changed or/// reaffirmed. The zero address indicates there is no approved address./// When a Transfer event emits, this also indicates that the approved/// address for that NFT (if any) is reset to none.event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);/// @dev This emits when an operator is enabled or disabled for an owner./// The operator can manage all NFTs of the owner.event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);/// @notice Count all NFTs assigned to an owner/// @dev NFTs assigned to the zero address are considered invalid, and this/// function throws for queries about the zero address./// @param _owner An address for whom to query the balance/// @return The number of NFTs owned by `_owner`, possibly zerofunction balanceOf(address _owner) external view returns (uint256);/// @notice Find the owner of an NFT/// @dev NFTs assigned to zero address are considered invalid, and queries/// about them do throw./// @param _tokenId The identifier for an NFT/// @return The address of the owner of the NFTfunction ownerOf(uint256 _tokenId) external view returns (address);/// @notice Transfers the ownership of an NFT from one address to another address/// @dev Throws unless `msg.sender` is the current owner, an authorized/// operator, or the approved address for this NFT. Throws if `_from` is/// not the current owner. Throws if `_to` is the zero address. Throws if/// `_tokenId` is not a valid NFT. When transfer is complete, this function/// checks if `_to` is a smart contract (code size > 0). If so, it calls/// `onERC721Received` on `_to` and throws if the return value is not/// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`./// @param _from The current owner of the NFT/// @param _to The new owner/// @param _tokenId The NFT to transfer/// @param data Additional data with no specified format, sent in call to `_to`function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable;/// @notice Transfers the ownership of an NFT from one address to another address/// @dev This works identically to the other function with an extra data parameter,/// except this function just sets data to ""./// @param _from The current owner of the NFT/// @param _to The new owner/// @param _tokenId The NFT to transferfunction safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;/// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE/// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE/// THEY MAY BE PERMANENTLY LOST/// @dev Throws unless `msg.sender` is the current owner, an authorized/// operator, or the approved address for this NFT. Throws if `_from` is/// not the current owner. Throws if `_to` is the zero address. Throws if/// `_tokenId` is not a valid NFT./// @param _from The current owner of the NFT/// @param _to The new owner/// @param _tokenId The NFT to transferfunction transferFrom(address _from, address _to, uint256 _tokenId) external payable;/// @notice Change or reaffirm the approved address for an NFT/// @dev The zero address indicates there is no approved address./// Throws unless `msg.sender` is the current NFT owner, or an authorized/// operator of the current owner./// @param _approved The new approved NFT controller/// @param _tokenId The NFT to approvefunction approve(address _approved, uint256 _tokenId) external payable;/// @notice Enable or disable approval for a third party ("operator") to manage/// all of `msg.sender`'s assets/// @dev Emits the ApprovalForAll event. The contract MUST allow/// multiple operators per owner./// @param _operator Address to add to the set of authorized operators/// @param _approved True if the operator is approved, false to revoke approvalfunction setApprovalForAll(address _operator, bool _approved) external;/// @notice Get the approved address for a single NFT/// @dev Throws if `_tokenId` is not a valid NFT./// @param _tokenId The NFT to find the approved address for/// @return The approved address for this NFT, or the zero address if there is nonefunction getApproved(uint256 _tokenId) external view returns (address);/// @notice Query if an address is an authorized operator for another address/// @param _owner The address that owns the NFTs/// @param _operator The address that acts on behalf of the owner/// @return True if `_operator` is an approved operator for `_owner`, false otherwisefunction isApprovedForAll(address _owner, address _operator) external view returns (bool);}

(2)ERC165.sol - 接囗及实现

pragma solidity ^0.6.0;interface IERC165 {/// @notice Query if a contract implements an interface/// @param interfaceID The interface identifier, as specified in ERC-165/// @dev Interface identification is specified in ERC-165. This function/// uses less than 30,000 gas./// @return `true` if the contract implements `interfaceID` and/// `interfaceID` is not 0xffffffff, `false` otherwisefunction supportsInterface(bytes4 interfaceID) external view returns (bool);}contract ERC165 is IERC165{//常量 - ERC165接囗IDbytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;//状态变量 - 记录接囗ID是否已被实现mapping(bytes4=>bool) private _supportInterface;//构造函数 - 初始化ERC165已被实现constructor() public {registerInterface(_INTERFACE_ID_ERC165);}//实现ERC65接囗方法function supportsInterface(bytes4 interfaceID) external override view returns (bool){return _supportInterface[interfaceID];}//注册接囗,即标记接囗为已实现function registerInterface(bytes4 interfaceID) public {require(interfaceID != 0xffffffff, "ERC165:无效的接囗ID");_supportInterface[interfaceID] = true;}}

(3)SafeMath.sol - 安全运算库

pragma solidity ^0.6.0;/*** @title SafeMath* @dev Math operations with safety checks that revert on error*/library SafeMath {/*** @dev Multiplies two numbers, reverts on overflow.*/function mul(uint256 a, uint256 b) internal pure returns (uint256) {// Gas optimization: this is cheaper than requiring 'a' not being zero, but the// benefit is lost if 'b' is also tested.// See: /OpenZeppelin/openzeppelin-solidity/pull/522if (a == 0) {return 0;}uint256 c = a * b;require(c / a == b);return c;}/*** @dev Integer division of two numbers truncating the quotient, reverts on division by zero.*/function div(uint256 a, uint256 b) internal pure returns (uint256) {require(b > 0); // Solidity only automatically asserts when dividing by 0uint256 c = a / b;// assert(a == b * c + a % b); // There is no case in which this doesn't holdreturn c;}/*** @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).*/function sub(uint256 a, uint256 b) internal pure returns (uint256) {require(b <= a);uint256 c = a - b;return c;}/*** @dev Adds two numbers, reverts on overflow.*/function add(uint256 a, uint256 b) internal pure returns (uint256) {uint256 c = a + b;require(c >= a);return c;}/*** @dev Divides two numbers and returns the remainder (unsigned integer modulo),* reverts when dividing by zero.*/function mod(uint256 a, uint256 b) internal pure returns (uint256) {require(b != 0);return a % b;}}

(4)ERC721.sol - ERC721实现

pragma solidity ^0.6.0;import "./IERC721.sol";import "./ERC165.sol";import "./SafeMath.sol";interface IERC721TokenReceiver {function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4);}contract ERC721 is ERC165, IERC721 {//状态变量 - 记录owner拥有多少个tokenmapping(address => uint256) private _ownerTokensCount;//状态变量 - 记录tokenId的所有者mapping(uint256 => address) private _tokenOwner;//状态变量 - 记录tokenId授权给外部账户mapping(uint256 => address) private _tokenApproval;//状态变量 - 记录用户全部授权mapping(address => mapping(address => bool)) private _operatorApprovals;//使用库作用于uint256类型using SafeMath for uint256;//常量 - ERC721接囗IDbytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;//常量 - IERC721TokenReceiver接囗IDbytes4 private constant _ERC721_RECEIVED = 0x150b7a02;//构造函数 - 初始化ERC721接囗已实现constructor() public {registerInterface(_INTERFACE_ID_ERC721);}modifier ownerIsNotZeroAddress(address _owner) {require(address(0) != _owner, "ERC721:地址不能为零地址");_;}/// 获取所有者拥有多少个Tokenfunction balanceOf(address _owner) ownerIsNotZeroAddress(_owner) external override view returns (uint256) {return _ownerTokensCount[_owner];}/// 获取tokenId的所有者function ownerOf(uint256 _tokenId) external override view returns (address) {return _ownerOf(_tokenId);}function _ownerOf(uint256 _tokenId) internal view returns (address) {//查询出tokenId的所有者address _owner = _tokenOwner[_tokenId];//判断所有者是否为零地址require(address(0) != _owner, "ERC721:拥有者地址不能为零地址,即所有者不存在");return _owner;}//公用内部函数 - NFT转移,将tokenId由所有者_from转给_tofunction _transferFrom(address _from, address _to, uint256 _tokenId) internal virtual { //判断tokenId的当前所有者是否为_fromrequire(_ownerOf(_tokenId) == _from, "当前所有者不是转账所有者");//判断接收者地址为不零地址require(address(0) != _to, "接收者地址不能为零地址"); //判断用户是否有权转移tokenrequire(_isApprovedOrOwner(msg.sender, _tokenId), "用户无权转移token");//清除tokenId的授权者_approve(address(0), _tokenId);//原所有者拥有的token数量减一_ownerTokensCount[_from] = _ownerTokensCount[_from].sub(1);//新接收者拥有的token数量加一_ownerTokensCount[_to] = _ownerTokensCount[_to].add(1);//记录token所有者为新接收者_tokenOwner[_tokenId] = _to; //调用事件emit Transfer(_from, _to, _tokenId);}//判断tokenId是否存在,若获取不到用户的所有者,则视为不存在function _isExistTokenId(uint256 _tokenId) internal view returns (bool) {//查询出tokenId的所有者address owner = _tokenOwner[_tokenId];if (address(0) != owner) {return true;}return false;}//判断转账方是否有权转出tokenfunction _isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) {//判断tokenId的所有者是否存在require(_isExistTokenId(_tokenId), "tokenId不存在");//查询出tokenId的所有者address _owner = _ownerOf(_tokenId);//所有者可能是调用合约的地址spender,可能是tokenId被授权的地址spender,还有可能是所有者的token都被授权给地址spenderreturn (_owner == _spender || _getApproved(_tokenId) == _spender || _isApprovedForAll(_owner, _spender));}//判断是否为合约地址function _isContract(address addr) internal view returns (bool) {uint256 _size;//若为外部账户_size = 0,若为合约账户 _size > 0assembly { _size := extcodesize(addr) }return _size > 0;}//校验接收地址是否有效function _checkOnERC721Received(address _from, address _to, uint256 _tokenId, bytes memory _data) private returns (bool) {//判断是否为合约地址,若为外部账户直接返回true,若为合约账户则校验是否实现了ERC721Receiver接囗方法if (!_isContract(_to)) { //外部账户return true;}//合约账户:校验是否实现了IERC721Receiver接囗方法,只有实现了IERC721Receiver接囗,才能接收ERC-721标准的token(bool success, bytes memory returndata) = _to.call(abi.encodeWithSelector(IERC721TokenReceiver(_to).onERC721Received.selector,msg.sender,_from,_tokenId,_data));//判断返回结果if (!success) {revert("合约地址未实现IERC721TokenReceiver接囗");} else {bytes4 retval = abi.decode(returndata, (bytes4));return (retval == _ERC721_RECEIVED);}}// 安全转移token - 公用内部函数function _safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory _data) internal virtual {_transferFrom(_from, _to, _tokenId);require(_checkOnERC721Received(_from, _to, _tokenId, _data), "合约地址未实现IERC721TokenReceiver接囗!");}/// 安装转移token,包含datafunction safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external override payable {_safeTransferFrom(_from, _to, _tokenId, data);}/// 安装转移tokenfunction safeTransferFrom(address _from, address _to, uint256 _tokenId) external override payable {_safeTransferFrom(_from, _to, _tokenId, "");}/// 转移tokenfunction transferFrom(address _from, address _to, uint256 _tokenId) external override payable {//转移token_transferFrom(_from, _to, _tokenId);}/// 授权tokenfunction _approve(address _approved, uint256 _tokenId) internal {require(_approved != msg.sender, "不能授权给自己");_tokenApproval[_tokenId] = _approved;}/// 授权tokenfunction approve(address _approved, uint256 _tokenId) ownerIsNotZeroAddress(_approved) external override payable {_approve(_approved, _tokenId);}/// 全部授权function setApprovalForAll(address _operator, bool _approved) external override {require(_operator != msg.sender, "不能授权给自己");//修改状态变量 - 全部授权_operatorApprovals[msg.sender][_operator] = _approved;//事件emit ApprovalForAll(msg.sender, _operator, _approved);}/// 获取tokenId的被授权者function _getApproved(uint256 _tokenId) internal view returns (address) {//判断tokenId的所有者是否存在require(_isExistTokenId(_tokenId), "tokenId不存在");return _tokenApproval[_tokenId];}/// 获取tokenId的被授权者function getApproved(uint256 _tokenId) external override view returns (address) {return _getApproved(_tokenId);}/// 是否全部授权,即_owner将自己所有的tokenId全部授权给_operatorfunction _isApprovedForAll(address _owner, address _operator) internal view returns (bool) {return _operatorApprovals[_owner][_operator];}/// 是否全部授权,即_owner将自己所有的tokenId全部授权给_operatorfunction isApprovedForAll(address _owner, address _operator) external override view returns (bool) {return _isApprovedForAll(_owner, _operator);}//生成tokenId - 公用函数function _mint(address _to, uint256 _tokenId) ownerIsNotZeroAddress(_to) internal virtual {require(!_isExistTokenId(_tokenId), "token已存在");//设置token的所有者_tokenOwner[_tokenId] = _to;//所有者拥有的token数量累加_ownerTokensCount[_to] = _ownerTokensCount[_to].add(1);//事件emit Transfer(address(0), _to, _tokenId);}// 生成tokenIdfunction mint(address _to, uint256 _tokenId) ownerIsNotZeroAddress(_to) external {_mint(_to, _tokenId);}// 生成tokenId - (安全)function safeMint(address _to, uint256 _tokenId, bytes calldata _data) ownerIsNotZeroAddress(_to) external {_mint(_to, _tokenId);require(_checkOnERC721Received(address(0), _to, _tokenId, _data), "合约地址没有实现ERC721Received接囗");}}

部署并测试

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。