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 https://eips.ethereum.org/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 zero
    function 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 NFT
    function 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 transfer
    function 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 transfer
    function 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 approve
    function 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 approval
    function 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 none
    function 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 otherwise
    function 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` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

contract ERC165 is IERC165{
    //常量 - ERC165接囗ID
    bytes4 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: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (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 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return 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拥有多少个token
    mapping(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接囗ID
    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;

    //常量 - IERC721TokenReceiver接囗ID
    bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

    //构造函数 - 初始化ERC721接囗已实现
    constructor() public {
        registerInterface(_INTERFACE_ID_ERC721);
    }

    modifier ownerIsNotZeroAddress(address _owner) {
        require(address(0) != _owner, "ERC721:地址不能为零地址");
        _;
    }

    /// 获取所有者拥有多少个Token
    function 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转给_to
    function _transferFrom(address _from, address _to, uint256 _tokenId) internal virtual {    
        //判断tokenId的当前所有者是否为_from
        require(_ownerOf(_tokenId) == _from, "当前所有者不是转账所有者");
        //判断接收者地址为不零地址
        require(address(0) != _to, "接收者地址不能为零地址");  
        //判断用户是否有权转移token
        require(_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;
    }

    //判断转账方是否有权转出token
    function _isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) {
        //判断tokenId的所有者是否存在
        require(_isExistTokenId(_tokenId), "tokenId不存在");
        //查询出tokenId的所有者
        address _owner = _ownerOf(_tokenId);
        //所有者可能是调用合约的地址spender,可能是tokenId被授权的地址spender,还有可能是所有者的token都被授权给地址spender
        return (_owner == _spender || _getApproved(_tokenId) == _spender || _isApprovedForAll(_owner, _spender));
    }

    //判断是否为合约地址
    function _isContract(address addr) internal view returns (bool) {
        uint256 _size;

        //若为外部账户_size = 0,若为合约账户 _size > 0
        assembly { _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,包含data
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external override payable {
        _safeTransferFrom(_from, _to, _tokenId, data);
    }

    /// 安装转移token
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external override payable {
        _safeTransferFrom(_from, _to, _tokenId, "");
    }

    /// 转移token
    function transferFrom(address _from, address _to, uint256 _tokenId) external override payable {
        //转移token
        _transferFrom(_from, _to, _tokenId);
    }

    /// 授权token
    function _approve(address _approved, uint256 _tokenId)  internal  {
        require(_approved != msg.sender, "不能授权给自己");
        _tokenApproval[_tokenId] = _approved;
    }

    /// 授权token
    function 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全部授权给_operator
    function _isApprovedForAll(address _owner, address _operator) internal view returns (bool) {
        return _operatorApprovals[_owner][_operator];
    }

    /// 是否全部授权,即_owner将自己所有的tokenId全部授权给_operator
    function 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);
    }

    // 生成tokenId
    function 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接囗");
    }

}

 部署并测试

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值