TRC-721 合约示例

包含元数据扩展和枚举扩展的 TRC-721 标准完整参考实现。

📘

前置阅读

本页包含一个单文件完整 TRC-721 参考实现。使用时,在最终合约中自定义 Token 名称和符号,然后部署即可。

合约按层次组织——工具在顶部,TRC-721 核心居中,最终可部署合约在底部。

文件内容说明

层次合约与库用途
工具库SafeMathAddressCountersRolesContext安全算术、地址类型辅助函数、计数器跟踪以及访问角色管理。
访问控制MinterRole将铸造权限限制为授权地址白名单。
接口ITRC165ITRC721ITRC721MetadataITRC721EnumerableITRC721Receiver与 TRC-721 协议匹配的抽象接口。
核心TRC165TRC721必须实现的 TRC-721 和 TRC-165 实现。
扩展TRC721MetadataTRC721Enumerable可选的元数据和枚举扩展。
可铸造复合合约TRC721MintableTRC721MetadataMintable将核心与 MinterRole 组合以启用铸造功能。
可部署合约TRC721Token最终合约——继承完整的合约栈;在其构造函数中修改名称和符号。

如何定制

  1. 在文件底部的构造函数(TRC721Token)中修改 Token 名称和符号。
  2. 决定系列是否需要枚举扩展。如不需要,仅继承 TRC721MetadataMintable 并去掉 TRC721Enumerable,可节省部署和运行时能量。
  3. 如果需要让非铸造角色地址也能铸造(例如白名单铸造),可通过 MinterRole 覆盖 _isMinter 行为;不要完全删除访问控制,否则任何人都能铸造。
  4. 部署时使用 Solidity 编译器 0.5.10(合约固定使用 pragma solidity 0.5.10)。
🚧

生产环境准备

本文件是用于学习的参考实现。在向主网部署包含真实资产的合约之前,请对合约进行审计、使用 linter 检查,并先在 Shasta 测试网部署测试。参见安全最佳实践

完整参考实现

完整合约如下。为便于复制,特意保持单文件形式。

pragma solidity 0.5.10;

/**
 * @dev 对 Solidity 算术运算的封装,增加了溢出检查。
 *
 * Solidity 中的算术运算在溢出时会发生回绕。这很容易导致 bug,
 * 因为程序员通常假设溢出会报错,这是高级编程语言的标准行为。
 * `SafeMath` 通过在运算溢出时 revert 交易来恢复这种直觉。
 *
 * 建议始终使用此库,以消除一整类 bug。
 */
library SafeMath {
    /**
     * @dev 返回两个无符号整数之和,溢出时 revert。
     *
     * Solidity `+` 运算符的对应函数。
     *
     * 要求:
     * - 加法不得溢出。
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev 返回两个无符号整数之差,结果为负时 revert(溢出)。
     *
     * Solidity `-` 运算符的对应函数。
     *
     * 要求:
     * - 减法不得溢出。
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev 返回两个无符号整数之差,溢出时带自定义错误信息 revert(结果为负)。
     *
     * Solidity `-` 运算符的对应函数。
     *
     * 要求:
     * - 减法不得溢出。
     *
     * _从 v2.4.0 起可用。_
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev 返回两个无符号整数之积,溢出时 revert。
     *
     * Solidity `*` 运算符的对应函数。
     *
     * 要求:
     * - 乘法不得溢出。
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas 优化:比要求 'a' 不为零更便宜,但若 'b' 也为零则效果消失。
        // 参见:https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev 返回两个无符号整数的整数除法结果,除以零时 revert。结果向零取整。
     *
     * Solidity `/` 运算符的对应函数。注意:该函数使用 `revert` opcode
     *(保留剩余 gas),而 Solidity 使用无效 opcode(消耗所有剩余 gas)。
     *
     * 要求:
     * - 除数不得为零。
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev 返回两个无符号整数的整数除法结果,除以零时带自定义错误信息 revert。结果向零取整。
     *
     * Solidity `/` 运算符的对应函数。注意:该函数使用 `revert` opcode
     *(保留剩余 gas),而 Solidity 使用无效 opcode(消耗所有剩余 gas)。
     *
     * 要求:
     * - 除数不得为零。
     *
     * _从 v2.4.0 起可用。_
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity 仅在除以 0 时自动断言
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // 不存在不成立的情况

        return c;
    }

    /**
     * @dev 返回两个无符号整数相除的余数(无符号整数取模),
     * 除以零时 revert。
     *
     * Solidity `%` 运算符的对应函数。该函数使用 `revert` opcode
     *(保留剩余 gas),而 Solidity 使用无效 opcode(消耗所有剩余 gas)。
     *
     * 要求:
     * - 除数不得为零。
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev 返回两个无符号整数相除的余数(无符号整数取模),
     * 除以零时带自定义错误信息 revert。
     *
     * Solidity `%` 运算符的对应函数。该函数使用 `revert` opcode
     *(保留剩余 gas),而 Solidity 使用无效 opcode(消耗所有剩余 gas)。
     *
     * 要求:
     * - 除数不得为零。
     *
     * _从 v2.4.0 起可用。_
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

/*
 * @dev 提供关于当前执行上下文的信息,包括交易的发送方及其数据。
 * 虽然这些信息通常可通过 msg.sender 和 msg.data 获取,
 * 但不应以如此直接的方式访问,因为在处理 GSN 元交易时,
 * 发送和支付执行的账户可能不是实际发送方(从应用程序角度来看)。
 *
 * 该合约仅供中间层、类库合约使用。
 */
contract Context {
    // 空的内部构造函数,防止人们误将该合约直接部署为实例,
    // 该合约应通过继承使用。
    constructor () internal { }
    // solhint-disable-previous-line no-empty-blocks

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // 消除状态可变性警告而不生成字节码——见 https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

/**
 * @title Roles
 * @dev 用于管理分配给角色的地址的库。
 */
library Roles {
    struct Role {
        mapping (address => bool) bearer;
    }

    /**
     * @dev 授予账户对该角色的访问权限。
     */
    function add(Role storage role, address account) internal {
        require(!has(role, account), "Roles: account already has role");
        role.bearer[account] = true;
    }

    /**
     * @dev 移除账户对该角色的访问权限。
     */
    function remove(Role storage role, address account) internal {
        require(has(role, account), "Roles: account does not have role");
        role.bearer[account] = false;
    }

    /**
     * @dev 检查账户是否具有该角色。
     * @return bool
     */
    function has(Role storage role, address account) internal view returns (bool) {
        require(account != address(0), "Roles: account is the zero address");
        return role.bearer[account];
    }
}

/**
 * @dev 与地址类型相关的函数集合
 */
library Address {
    /**
     * @dev 如果 `account` 是合约,则返回 true。
     *
     * 需要注意的是,合约在构造函数执行期间会返回 false,
     * 因为合约代码只会在构造函数结束后才存储到链上。
     *
     * _从 v2.4.0 起可用。_
     */
    function isContract(address account) internal view returns (bool) {
        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }

    /**
     * @dev 将 `address` 转换为 `address payable`。注意这只是类型转换:
     * 底层实际值不会改变。
     *
     * _从 v2.4.0 起可用。_
     */
    function toPayable(address account) internal pure returns (address payable) {
        return address(uint160(account));
    }

    /**
     * @dev Solidity `transfer` 的替代品:向 `recipient` 发送 `amount` wei,
     * 转发所有可用 gas 并在出错时 revert。
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[了解更多]。
     *
     * 重要提示:由于控制权转移给 `recipient`,必须注意避免产生重入漏洞。
     * 考虑使用 {ReentrancyGuard} 或
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[检查-效果-交互模式]。
     *
     * _从 v2.4.0 起可用。_
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        // solhint-disable-next-line avoid-call-value
        (bool success, ) = recipient.call.value(amount)("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }
}


/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev 提供只能以 1 为步长递增或递减的计数器。
 * 可用于跟踪 mapping 中的元素数量、发行 TRC721 ID 或统计请求 ID 等场景。
 *
 * 使用方式:`using Counters for Counters.Counter;`
 * 由于 256 位整数以 1 为步长递增不可能溢出,`increment` 可跳过 {SafeMath}
 * 溢出检查,从而节省能量。但这假设使用正确,即底层 `_value` 永远不会被直接访问。
 */
library Counters {
    using SafeMath for uint256;

    struct Counter {
        // 该变量不应被库的使用者直接访问:交互必须限制在库的函数内。
        // 截至 Solidity v0.5.2,这无法强制执行,但已有相关改进提议:
        // 见 https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // 默认:0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        // 此处可跳过 {SafeMath} 溢出检查,见顶部注释
        counter._value += 1;
    }

    function decrement(Counter storage counter) internal {
        counter._value = counter._value.sub(1);
    }
}


contract MinterRole is Context {
    using Roles for Roles.Role;

    event MinterAdded(address indexed account);
    event MinterRemoved(address indexed account);

    Roles.Role private _minters;

    constructor () internal {
        _addMinter(_msgSender());
    }

    modifier onlyMinter() {
        require(isMinter(_msgSender()), "MinterRole: caller does not have the Minter role");
        _;
    }

    function isMinter(address account) public view returns (bool) {
        return _minters.has(account);
    }

    function addMinter(address account) public onlyMinter {
        _addMinter(account);
    }

    function renounceMinter() public {
        _removeMinter(_msgSender());
    }

    function _addMinter(address account) internal {
        _minters.add(account);
        emit MinterAdded(account);
    }

    function _removeMinter(address account) internal {
        _minters.remove(account);
        emit MinterRemoved(account);
    }
}

/**
 * @dev TRC165 标准接口。
 *
 * 实现者可以声明对合约接口的支持,然后其他人({TRC165Checker})可以查询。
 *
 * 具体实现请参见 {TRC165}。
 */
interface ITRC165 {
    /**
     * @dev 如果本合约实现了 `interfaceId` 定义的接口,则返回 true。
     *
     * 该函数调用消耗 gas 必须少于 30,000。
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}


/**
 * @dev TRC721 合规合约的必要接口。
 */
contract ITRC721 is ITRC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev 返回 `owner` 账户中的 NFT 数量。
     */
    function balanceOf(address owner) public view returns (uint256 balance);

    /**
     * @dev 返回 `tokenId` 指定的 NFT 的所有者。
     */
    function ownerOf(uint256 tokenId) public view returns (address owner);

    /**
     * @dev 将特定 NFT(`tokenId`)从一个账户(`from`)转移到另一个账户(`to`)。
     *
     * 要求:
     * - `from`、`to` 不能为零地址。
     * - `tokenId` 必须由 `from` 持有。
     * - 若调用方不是 `from`,则必须通过 {approve} 或 {setApprovalForAll} 获得授权。
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public;
    /**
     * @dev 将特定 NFT(`tokenId`)从一个账户(`from`)转移到另一个账户(`to`)。
     *
     * 要求:
     * - 若调用方不是 `from`,则必须通过 {approve} 或 {setApprovalForAll} 获得授权。
     */
    function transferFrom(address from, address to, uint256 tokenId) public;
    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 safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
}


/**
 * @title TRC-721 非同质化Token标准,可选元数据扩展
 */
contract ITRC721Metadata is ITRC721 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

/**
 * @title TRC721 Token接收者接口
 * @dev 任何希望支持来自 TRC721 资产合约的安全转账的合约均需实现此接口。
 */
contract ITRC721Receiver {
    /**
     * @notice 处理 NFT 的接收
     * @dev 在 {ITRC721-safeTransferFrom} 之后,TRC721 智能合约在接收方调用此函数。
     * 该函数必须返回函数选择器,否则调用方将 revert 交易。
     * 返回的选择器可通过 `this.onTRC721Received.selector` 获取。
     * 该函数可以抛出异常以 revert 并拒绝转账。
     * 注意:TRC721 合约地址始终是消息发送方。
     * @param operator 调用 `safeTransferFrom` 函数的地址
     * @param from 先前持有Token的地址
     * @param tokenId 正在转移的 NFT 标识符
     * @param data 无指定格式的附加数据
     * @return bytes4 `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`
     */
    function onTRC721Received(address operator, address from, uint256 tokenId, bytes memory data)
    public returns (bytes4);
}


/**
 * @dev {ITRC165} 接口的实现。
 *
 * 合约可继承此合约并调用 {_registerInterface} 来声明对某接口的支持。
 */
contract TRC165 is ITRC165 {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    bytes4 private constant _INTERFACE_ID_TRC165 = 0x01ffc9a7;

    /**
     * @dev 接口 id 到是否受支持的映射。
     */
    mapping(bytes4 => bool) private _supportedInterfaces;

    constructor () internal {
        // 派生合约只需注册对自身接口的支持,
        // 此处注册对 TRC165 本身的支持
        _registerInterface(_INTERFACE_ID_TRC165);
    }

    /**
     * @dev 参见 {ITRC165-supportsInterface}。
     *
     * 时间复杂度 O(1),保证始终消耗少于 30,000 gas。
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool) {
        return _supportedInterfaces[interfaceId];
    }

    /**
     * @dev 将合约注册为 `interfaceId` 定义接口的实现者。
     * 对 TRC165 接口本身的支持是自动的,无需注册其接口 id。
     *
     * 参见 {ITRC165-supportsInterface}。
     *
     * 要求:
     *
     * - `interfaceId` 不能是 TRC165 的无效接口(`0xffffffff`)。
     */
    function _registerInterface(bytes4 interfaceId) internal {
        require(interfaceId != 0xffffffff, "TRC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }
}


/**
 * @title TRC721 非同质化Token标准基础实现
 */
contract TRC721 is Context, TRC165, ITRC721 {
    using SafeMath for uint256;
    using Address for address;
    using Counters for Counters.Counter;

    // 等于 `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`
    // 也可通过 `ITRC721Receiver(0).onTRC721Received.selector` 获得
    //
    // 注意:ERC721 使用 0x150b7a02,TRC721 使用 0x5175f878。
    bytes4 private constant _TRC721_RECEIVED = 0x5175f878;

    // token ID 到所有者的映射
    mapping (uint256 => address) private _tokenOwner;

    // token ID 到被授权地址的映射
    mapping (uint256 => address) private _tokenApprovals;

    // 所有者到持有Token数量的映射
    mapping (address => Counters.Counter) private _ownedTokensCount;

    // 所有者到操作员授权的映射
    mapping (address => mapping (address => bool)) private _operatorApprovals;

    /*
     *     bytes4(keccak256('balanceOf(address)')) == 0x70a08231
     *     bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
     *     bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
     *     bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
     *     bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
     *     bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
     *     bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
     *     bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
     *     bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
     *
     *     => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
     *        0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
     */
    bytes4 private constant _INTERFACE_ID_TRC721 = 0x80ac58cd;

    constructor () public {
        // 注册受支持的接口以通过 TRC165 符合 TRC721
        _registerInterface(_INTERFACE_ID_TRC721);
    }

    /**
     * @dev 获取指定地址的余额。
     * @param owner 要查询余额的地址
     * @return uint256 表示传入地址持有数量的值
     */
    function balanceOf(address owner) public view returns (uint256) {
        require(owner != address(0), "TRC721: balance query for the zero address");

        return _ownedTokensCount[owner].current();
    }

    /**
     * @dev 获取指定 token ID 的所有者。
     * @param tokenId 要查询所有者的 token 的 uint256 ID
     * @return 当前标记为给定 token ID 所有者的地址
     */
    function ownerOf(uint256 tokenId) public view returns (address) {
        address owner = _tokenOwner[tokenId];
        require(owner != address(0), "TRC721: owner query for nonexistent token");

        return owner;
    }

    /**
     * @dev 授权另一个地址转移给定的 token ID。
     * 零地址表示没有被授权的地址。
     * 给定时间内每个 token 只能有一个被授权地址。
     * 只能由 token 所有者或被授权的操作员调用。
     * @param to 要被授权的地址(用于给定 token ID)
     * @param tokenId 要被授权的 token 的 uint256 ID
     */
    function approve(address to, uint256 tokenId) public {
        address owner = ownerOf(tokenId);
        require(to != owner, "TRC721: approval to current owner");

        require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "TRC721: approve caller is not owner nor approved for all"
        );

        _tokenApprovals[tokenId] = to;
        emit Approval(owner, to, tokenId);
    }

    /**
     * @dev 获取 token ID 的被授权地址,若未设置则为零地址。
     * 若 token ID 不存在则 revert。
     * @param tokenId 要查询授权的 token 的 uint256 ID
     * @return 当前被授权的给定 token ID 的地址
     */
    function getApproved(uint256 tokenId) public view returns (address) {
        require(_exists(tokenId), "TRC721: approved query for nonexistent token");

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev 设置或取消给定操作员的授权。
     * 操作员被允许代表发送方转移其所有Token。
     * @param to 要设置授权的操作员地址
     * @param approved 要设置的授权状态
     */
    function setApprovalForAll(address to, bool approved) public {
        require(to != _msgSender(), "TRC721: approve to caller");

        _operatorApprovals[_msgSender()][to] = approved;
        emit ApprovalForAll(_msgSender(), to, approved);
    }

    /**
     * @dev 查询操作员是否被给定所有者授权。
     * @param owner 要查询授权的所有者地址
     * @param operator 要查询授权的操作员地址
     * @return bool 给定操作员是否被给定所有者授权
     */
    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev 将给定 token ID 的所有权转移到另一个地址。
     * 不建议直接使用此方法,尽可能使用 {safeTransferFrom}。
     * 要求 msg.sender 是所有者、被授权方或操作员。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     */
    function transferFrom(address from, address to, uint256 tokenId) public {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "TRC721: transfer caller is not owner nor approved");

        _transferFrom(from, to, tokenId);
    }

    /**
     * @dev 安全地将给定 token ID 的所有权转移到另一个地址。
     * 若目标地址是合约,则必须实现 {ITRC721Receiver-onTRC721Received},
     * 该函数在安全转账时被调用,并返回魔法值
     * `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`;否则转账将 revert。
     * 要求 msg.sender 是所有者、被授权方或操作员。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev 安全地将给定 token ID 的所有权转移到另一个地址。
     * 若目标地址是合约,则必须实现 {ITRC721Receiver-onTRC721Received},
     * 该函数在安全转账时被调用,并返回魔法值
     * `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`;否则转账将 revert。
     * 要求 _msgSender() 是所有者、被授权方或操作员。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     * @param _data 要随安全转账检查一起发送的字节数据
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "TRC721: transfer caller is not owner nor approved");
        _safeTransferFrom(from, to, tokenId, _data);
    }

    /**
     * @dev 安全地将给定 token ID 的所有权转移到另一个地址。
     * 若目标地址是合约,则必须实现 `onTRC721Received`,
     * 该函数在安全转账时被调用,并返回魔法值
     * `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`;否则转账将 revert。
     * 要求 msg.sender 是所有者、被授权方或操作员。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     * @param _data 要随安全转账检查一起发送的字节数据
     */
    function _safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) internal {
        _transferFrom(from, to, tokenId);
        require(_checkOnTRC721Received(from, to, tokenId, _data), "TRC721: transfer to non TRC721Receiver implementer");
    }

    /**
     * @dev 返回指定 token 是否存在。
     * @param tokenId 要查询是否存在的 token 的 uint256 ID
     * @return bool token 是否存在
     */
    function _exists(uint256 tokenId) internal view returns (bool) {
        address owner = _tokenOwner[tokenId];
        return owner != address(0);
    }

    /**
     * @dev 返回给定支出方是否可以转移给定 token ID。
     * @param spender 要查询的支出方地址
     * @param tokenId 要转移的 token 的 uint256 ID
     * @return bool msg.sender 是否被授权操作给定 token ID,
     * 是否是所有者的操作员,或者是否是 token 的所有者
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        require(_exists(tokenId), "TRC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev 安全铸造新 token 的内部函数。
     * 若给定 token ID 已存在则 revert。
     * 若目标地址是合约,则必须实现 `onTRC721Received`,
     * 该函数在安全转账时被调用,并返回魔法值
     * `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`;否则转账将 revert。
     * @param to 将拥有铸造 token 的地址
     * @param tokenId 要铸造的 token 的 uint256 ID
     */
    function _safeMint(address to, uint256 tokenId) internal {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev 安全铸造新 token 的内部函数。
     * 若给定 token ID 已存在则 revert。
     * 若目标地址是合约,则必须实现 `onTRC721Received`,
     * 该函数在安全转账时被调用,并返回魔法值
     * `bytes4(keccak256("onTRC721Received(address,address,uint256,bytes)"))`;否则转账将 revert。
     * @param to 将拥有铸造 token 的地址
     * @param tokenId 要铸造的 token 的 uint256 ID
     * @param _data 要随安全转账检查一起发送的字节数据
     */
    function _safeMint(address to, uint256 tokenId, bytes memory _data) internal {
        _mint(to, tokenId);
        require(_checkOnTRC721Received(address(0), to, tokenId, _data), "TRC721: transfer to non TRC721Receiver implementer");
    }

    /**
     * @dev 铸造新 token 的内部函数。
     * 若给定 token ID 已存在则 revert。
     * @param to 将拥有铸造 token 的地址
     * @param tokenId 要铸造的 token 的 uint256 ID
     */
    function _mint(address to, uint256 tokenId) internal {
        require(to != address(0), "TRC721: mint to the zero address");
        require(!_exists(tokenId), "TRC721: token already minted");

        _tokenOwner[tokenId] = to;
        _ownedTokensCount[to].increment();

        emit Transfer(address(0), to, tokenId);
    }

    /**
     * @dev 燃烧特定 token 的内部函数。
     * 若 token 不存在则 revert。
     * 已废弃,请改用 {_burn}。
     * @param owner 要燃烧 token 的所有者
     * @param tokenId 正在被燃烧的 token 的 uint256 ID
     */
    function _burn(address owner, uint256 tokenId) internal {
        require(ownerOf(tokenId) == owner, "TRC721: burn of token that is not own");

        _clearApproval(tokenId);

        _ownedTokensCount[owner].decrement();
        _tokenOwner[tokenId] = address(0);

        emit Transfer(owner, address(0), tokenId);
    }

    /**
     * @dev 燃烧特定 token 的内部函数。
     * 若 token 不存在则 revert。
     * @param tokenId 正在被燃烧的 token 的 uint256 ID
     */
    function _burn(uint256 tokenId) internal {
        _burn(ownerOf(tokenId), tokenId);
    }

    /**
     * @dev 将给定 token ID 的所有权转移到另一个地址的内部函数。
     * 与 {transferFrom} 不同,此函数不对 msg.sender 施加限制。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     */
    function _transferFrom(address from, address to, uint256 tokenId) internal {
        require(ownerOf(tokenId) == from, "TRC721: transfer of token that is not own");
        require(to != address(0), "TRC721: transfer to the zero address");

        _clearApproval(tokenId);

        _ownedTokensCount[from].decrement();
        _ownedTokensCount[to].increment();

        _tokenOwner[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }

    /**
     * @dev 在目标地址上调用 {ITRC721Receiver-onTRC721Received} 的内部函数。
     * 若目标地址不是合约则不执行调用。
     *
     * 这是 `TRC721` 合约的内部实现细节,其使用已废弃。
     * @param from 表示给定 token ID 先前所有者的地址
     * @param to 将接收Token的目标地址
     * @param tokenId 正在转移的 token 的 uint256 ID
     * @param _data 可选的随调用一起发送的数据
     * @return bool 调用是否正确返回了预期的魔法值
     */
    function _checkOnTRC721Received(address from, address to, uint256 tokenId, bytes memory _data)
        internal returns (bool)
    {
        if (!to.isContract()) {
            return true;
        }
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = to.call(abi.encodeWithSelector(
            ITRC721Receiver(to).onTRC721Received.selector,
            _msgSender(),
            from,
            tokenId,
            _data
        ));
        if (!success) {
            if (returndata.length > 0) {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert("TRC721: transfer to non TRC721Receiver implementer");
            }
        } else {
            bytes4 retval = abi.decode(returndata, (bytes4));
            return (retval == _TRC721_RECEIVED);
        }
    }

    /**
     * @dev 清除给定 token ID 当前授权的私有函数。
     * @param tokenId 要转移的 token 的 uint256 ID
     */
    function _clearApproval(uint256 tokenId) private {
        if (_tokenApprovals[tokenId] != address(0)) {
            _tokenApprovals[tokenId] = address(0);
        }
    }
}


contract TRC721Metadata is Context, TRC165, TRC721, ITRC721Metadata {
    // Token名称
    string private _name;

    // Token符号
    string private _symbol;

    // 基础 URI
    string private _baseURI;

    // token URI 的可选映射
    mapping(uint256 => string) private _tokenURIs;

    /*
     *     bytes4(keccak256('name()')) == 0x06fdde03
     *     bytes4(keccak256('symbol()')) == 0x95d89b41
     *     bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
     *
     *     => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
     */
    bytes4 private constant _INTERFACE_ID_TRC721_METADATA = 0x5b5e139f;

    /**
     * @dev 构造函数
     */
    constructor (string memory name, string memory symbol) public {
        _name = name;
        _symbol = symbol;

        // 注册受支持的接口以通过 TRC165 符合 TRC721
        _registerInterface(_INTERFACE_ID_TRC721_METADATA);
    }

    /**
     * @dev 获取Token名称。
     * @return 表示Token名称的字符串
     */
    function name() external view returns (string memory) {
        return _name;
    }

    /**
     * @dev 获取Token符号。
     * @return 表示Token符号的字符串
     */
    function symbol() external view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev 返回给定 token ID 的 URI。若为空字符串则可能返回空串。
     *
     * 若 token 的 URI 非空且已设置基础 URI(通过 {_setBaseURI}),
     * 则将其作为前缀添加到 token ID 的 URI 前。
     *
     * 若 token ID 不存在则 revert。
     */
    function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(_exists(tokenId), "TRC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];

        // 即使存在基础 URI,也仅在非空的 token 特定 URI 前追加
        if (bytes(_tokenURI).length == 0) {
            return "";
        } else {
            // 使用 abi.encodePacked 拼接字符串
            return string(abi.encodePacked(_baseURI, _tokenURI));
        }
    }

    /**
     * @dev 为给定 token 设置 token URI 的内部函数。
     *
     * 若 token ID 不存在则 revert。
     *
     * 提示:若所有 token ID 共享同一前缀(例如 URI 形如
     * `http://api.myproject.com/token/<id>`),可使用 {_setBaseURI} 存储,
     * 以节省 gas。
     */
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal {
        require(_exists(tokenId), "TRC721Metadata: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }

    /**
     * @dev 为所有 token ID 设置基础 URI 的内部函数。
     * 将自动作为前缀添加到 {tokenURI} 返回的值前。
     *
     * _从 v2.5.0 起可用。_
     */
    function _setBaseURI(string memory baseURI) internal {
        _baseURI = baseURI;
    }

    /**
    * @dev 返回通过 {_setBaseURI} 设置的基础 URI。
    * 将自动作为前缀添加到 {tokenURI} 中每个 token 的 URI 前(当 URI 非空时)。
    *
    * _从 v2.5.0 起可用。_
    */
    function baseURI() external view returns (string memory) {
        return _baseURI;
    }

    /**
     * @dev 燃烧特定 token 的内部函数。
     * 若 token 不存在则 revert。
     * 已废弃,请改用 _burn(uint256)。
     * @param owner 要燃烧 token 的所有者
     * @param tokenId 正在被 msg.sender 燃烧的 token 的 uint256 ID
     */
    function _burn(address owner, uint256 tokenId) internal {
        super._burn(owner, tokenId);

        // 清除元数据(若有)
        if (bytes(_tokenURIs[tokenId]).length != 0) {
            delete _tokenURIs[tokenId];
        }
    }
}



/**
 * @title TRC721MetadataMintable
 * @dev 带元数据的 TRC721 铸造逻辑。
 */
contract TRC721MetadataMintable is TRC721, TRC721Metadata, MinterRole {
    /**
     * @dev 铸造Token的函数。
     * @param to 接收铸造Token的地址。
     * @param tokenId 要铸造的 token id。
     * @param tokenURI 铸造Token的 token URI。
     * @return 布尔值,指示操作是否成功。
     */
    function mintWithTokenURI(address to, uint256 tokenId, string memory tokenURI) public onlyMinter returns (bool) {
        _mint(to, tokenId);
        _setTokenURI(tokenId, tokenURI);
        return true;
    }
}


/**
 * @title TRC721Mintable
 * @dev TRC721 铸造逻辑。
 */
contract TRC721Mintable is TRC721, MinterRole {
    /**
     * @dev 铸造Token的函数。
     * @param to 接收铸造Token的地址。
     * @param tokenId 要铸造的 token id。
     * @return 布尔值,指示操作是否成功。
     */
    function mint(address to, uint256 tokenId) public onlyMinter returns (bool) {
        _mint(to, tokenId);
        return true;
    }

    /**
     * @dev 安全铸造Token的函数。
     * @param to 接收铸造Token的地址。
     * @param tokenId 要铸造的 token id。
     * @return 布尔值,指示操作是否成功。
     */
    function safeMint(address to, uint256 tokenId) public onlyMinter returns (bool) {
        _safeMint(to, tokenId);
        return true;
    }

    /**
     * @dev 安全铸造Token的函数。
     * @param to 接收铸造Token的地址。
     * @param tokenId 要铸造的 token id。
     * @param _data 要随安全转账检查一起发送的字节数据。
     * @return 布尔值,指示操作是否成功。
     */
    function safeMint(address to, uint256 tokenId, bytes memory _data) public onlyMinter returns (bool) {
        _safeMint(to, tokenId, _data);
        return true;
    }
}

/**
 * @title TRC-721 非同质化Token标准,可选枚举扩展
 */
contract ITRC721Enumerable is ITRC721 {
    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 带可选枚举扩展逻辑的 TRC-721 非同质化Token
 */
contract TRC721Enumerable is Context, TRC165, TRC721, ITRC721Enumerable {
    // 所有者到持有 token ID 列表的映射
    mapping(address => uint256[]) private _ownedTokens;

    // token ID 到所有者Token列表中索引的映射
    mapping(uint256 => uint256) private _ownedTokensIndex;

    // 包含所有 token id 的数组,用于枚举
    uint256[] private _allTokens;

    // token id 到 allTokens 数组中位置的映射
    mapping(uint256 => uint256) private _allTokensIndex;

    /*
     *     bytes4(keccak256('totalSupply()')) == 0x18160ddd
     *     bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
     *     bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
     *
     *     => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
     */
    bytes4 private constant _INTERFACE_ID_TRC721_ENUMERABLE = 0x780e9d63;

    /**
     * @dev 构造函数。
     */
    constructor () public {
        // 注册受支持的接口以通过 TRC165 符合 TRC721Enumerable
        _registerInterface(_INTERFACE_ID_TRC721_ENUMERABLE);
    }

    /**
     * @dev 获取请求所有者Token列表中给定索引处的 token ID。
     * @param owner 拥有要访问的Token列表的地址
     * @param index 表示要访问的请求Token列表索引的 uint256
     * @return uint256 请求地址所拥有的Token列表中给定索引处的 token ID
     */
    function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
        require(index < balanceOf(owner), "TRC721Enumerable: owner index out of bounds");
        return _ownedTokens[owner][index];
    }

    /**
     * @dev 获取合约存储的Token总量。
     * @return uint256 表示Token总量的值
     */
    function totalSupply() public view returns (uint256) {
        return _allTokens.length;
    }

    /**
     * @dev 获取此合约中所有Token中给定索引处的 token ID。
     * 若索引大于或等于Token总数则 revert。
     * @param index 表示要访问的Token列表索引的 uint256
     * @return uint256 Token列表中给定索引处的 token ID
     */
    function tokenByIndex(uint256 index) public view returns (uint256) {
        require(index < totalSupply(), "TRC721Enumerable: global index out of bounds");
        return _allTokens[index];
    }

    /**
     * @dev 将给定 token ID 的所有权转移到另一个地址的内部函数。
     * 与 transferFrom 不同,此函数不对 msg.sender 施加限制。
     * @param from token 的当前所有者
     * @param to 接收给定 token ID 所有权的地址
     * @param tokenId 要转移的 token 的 uint256 ID
     */
    function _transferFrom(address from, address to, uint256 tokenId) internal {
        super._transferFrom(from, to, tokenId);

        _removeTokenFromOwnerEnumeration(from, tokenId);

        _addTokenToOwnerEnumeration(to, tokenId);
    }

    /**
     * @dev 铸造新 token 的内部函数。
     * 若给定 token ID 已存在则 revert。
     * @param to 将拥有铸造 token 的受益人地址
     * @param tokenId 要铸造的 token 的 uint256 ID
     */
    function _mint(address to, uint256 tokenId) internal {
        super._mint(to, tokenId);

        _addTokenToOwnerEnumeration(to, tokenId);

        _addTokenToAllTokensEnumeration(tokenId);
    }

    /**
     * @dev 燃烧特定 token 的内部函数。
     * 若 token 不存在则 revert。
     * 已废弃,请改用 {TRC721-_burn}。
     * @param owner 要燃烧 token 的所有者
     * @param tokenId 正在被燃烧的 token 的 uint256 ID
     */
    function _burn(address owner, uint256 tokenId) internal {
        super._burn(owner, tokenId);

        _removeTokenFromOwnerEnumeration(owner, tokenId);
        // 由于 tokenId 将被删除,可以清除其在 _ownedTokensIndex 中的槽位以触发 gas 退款
        _ownedTokensIndex[tokenId] = 0;

        _removeTokenFromAllTokensEnumeration(tokenId);
    }

    /**
     * @dev 获取请求所有者的 token ID 列表。
     * @param owner 拥有Token的地址
     * @return uint256[] 请求地址所拥有的 token ID 列表
     */
    function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
        return _ownedTokens[owner];
    }

    /**
     * @dev 将 token 添加到此扩展所有权跟踪数据结构的私有函数。
     * @param to 表示给定 token ID 新所有者的地址
     * @param tokenId 要添加到给定地址Token列表的 token 的 uint256 ID
     */
    function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
        _ownedTokensIndex[tokenId] = _ownedTokens[to].length;
        _ownedTokens[to].push(tokenId);
    }

    /**
     * @dev 将 token 添加到此扩展 token 跟踪数据结构的私有函数。
     * @param tokenId 要添加到Token列表的 token 的 uint256 ID
     */
    function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
        _allTokensIndex[tokenId] = _allTokens.length;
        _allTokens.push(tokenId);
    }

    /**
     * @dev 从此扩展所有权跟踪数据结构中删除 token 的私有函数。注意
     * 当 token 未分配新所有者时,`_ownedTokensIndex` 映射不会更新:
     * 这允许在执行转账操作时进行 gas 优化(避免双重写入)。
     * 时间复杂度为 O(1),但会改变 _ownedTokens 数组的顺序。
     * @param from 表示给定 token ID 先前所有者的地址
     * @param tokenId 要从给定地址Token列表中删除的 token 的 uint256 ID
     */
    function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
        // 为防止 from 的Token数组出现间隙,将最后一个 token 存储在要删除的 token 的索引处,
        // 然后删除最后一个槽位(交换并弹出)。

        uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
        uint256 tokenIndex = _ownedTokensIndex[tokenId];

        // 当要删除的 token 是最后一个 token 时,交换操作是不必要的
        if (tokenIndex != lastTokenIndex) {
            uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];

            _ownedTokens[from][tokenIndex] = lastTokenId; // 将最后一个 token 移到要删除 token 的槽位
            _ownedTokensIndex[lastTokenId] = tokenIndex; // 更新被移动 token 的索引
        }

        // 这也会删除数组最后位置的内容
        _ownedTokens[from].length--;

        // 注意 _ownedTokensIndex[tokenId] 未被清除:它仍然指向旧槽位
        //(现在由 lastTokenId 占据,或者如果 token 是最后一个则超出数组末尾)。
    }

    /**
     * @dev 从此扩展 token 跟踪数据结构中删除 token 的私有函数。
     * 时间复杂度为 O(1),但会改变 _allTokens 数组的顺序。
     * @param tokenId 要从Token列表中删除的 token 的 uint256 ID
     */
    function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
        // 为防止 tokens 数组出现间隙,将最后一个 token 存储在要删除的 token 的索引处,
        // 然后删除最后一个槽位(交换并弹出)。

        uint256 lastTokenIndex = _allTokens.length.sub(1);
        uint256 tokenIndex = _allTokensIndex[tokenId];

        // 当要删除的 token 是最后一个 token 时,交换操作是不必要的。
        // 但由于这种情况发生得很少(当最后铸造的 token 被燃烧时),
        // 我们仍然在这里进行交换,以避免添加 'if' 语句的 gas 成本
        //(与 _removeTokenFromOwnerEnumeration 不同)
        uint256 lastTokenId = _allTokens[lastTokenIndex];

        _allTokens[tokenIndex] = lastTokenId; // 将最后一个 token 移到要删除 token 的槽位
        _allTokensIndex[lastTokenId] = tokenIndex; // 更新被移动 token 的索引

        // 这也会删除数组最后位置的内容
        _allTokens.length--;
        _allTokensIndex[tokenId] = 0;
    }
}

contract TRC721Token is TRC721, TRC721Enumerable, TRC721MetadataMintable {
    constructor() public TRC721Metadata("Your Token Name", "YTN") {
       
    }
}

相关资源