Skip to content

ERC721 标准实现

1. 系统概述

ERC721 标准实现是一个基于 Solidity 实现的非同质化代币(NFT)合约,完全符合 ERC721 标准接口规范。该实现包含了代币的基本功能、元数据功能和必要的安全机制。

1.1 主要特点

  • 标准兼容:完全符合 ERC721 标准
  • 基础功能:铸造、转账、销毁等基本操作
  • 元数据功能:支持代币元数据管理
  • 事件追踪:完整的事件日志记录
  • 安全检查:完善的安全性验证
  • 授权管理:支持代币授权和操作授权

2. 合约实现

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
 * @title ERC721 Token
 * @dev ERC721 非同质化代币标准实现
 */
contract ERC721 is Context, ERC165 {
    using Address for address;
    using Strings for uint256;

    // 事件定义
    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);

    // 状态变量
    string private _name;
    string private _symbol;
    string private _baseURI;

    // 代币数据
    mapping(uint256 => address) private _owners;
    mapping(address => uint256) private _balances;
    mapping(uint256 => address) private _tokenApprovals;
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    mapping(uint256 => string) private _tokenURIs;

    /**
     * @dev 构造函数
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev 实现 ERC165 接口
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev 获取代币名称
     */
    function name() public view returns (string memory) {
        return _name;
    }

    /**
     * @dev 获取代币符号
     */
    function symbol() public view returns (string memory) {
        return _symbol;
    }

    /**
     * @dev 获取代币 URI
     */
    function tokenURI(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "ERC721: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];
        string memory base = baseURI();

        if (bytes(base).length == 0) {
            return _tokenURI;
        }
        if (bytes(_tokenURI).length > 0) {
            return string(abi.encodePacked(base, _tokenURI));
        }
        return string(abi.encodePacked(base, tokenId.toString()));
    }

    /**
     * @dev 获取基础 URI
     */
    function baseURI() public view returns (string memory) {
        return _baseURI;
    }

    /**
     * @dev 获取代币余额
     */
    function balanceOf(address owner) public view returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return _balances[owner];
    }

    /**
     * @dev 获取代币所有者
     */
    function ownerOf(uint256 tokenId) public view returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /**
     * @dev 转账代币
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public virtual {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _transfer(from, to, tokenId);
    }

    /**
     * @dev 安全转账代币
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public virtual {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
        _safeTransfer(from, to, tokenId, _data);
    }

    /**
     * @dev 授权代币
     */
    function approve(address to, uint256 tokenId) public virtual {
        address owner = ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");
        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not owner nor approved for all"
        );
        _approve(to, tokenId);
    }

    /**
     * @dev 获取代币授权地址
     */
    function getApproved(uint256 tokenId) public view returns (address) {
        require(_exists(tokenId), "ERC721: approved query for nonexistent token");
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev 设置操作授权
     */
    function setApprovalForAll(address operator, bool approved) public virtual {
        require(operator != _msgSender(), "ERC721: approve to caller");
        _operatorApprovals[_msgSender()][operator] = approved;
        emit ApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev 检查是否被授权
     */
    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev 内部安全转账函数
     */
    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) internal virtual {
        _transfer(from, to, tokenId);
        require(
            _checkOnERC721Received(from, to, tokenId, _data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev 内部转账函数
     */
    function _transfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {
        require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId);
    }

    /**
     * @dev 内部铸造函数
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId);

        _balances[to] += 1;
        _owners[tokenId] = to;

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

        _afterTokenTransfer(address(0), to, tokenId);
    }

    /**
     * @dev 内部销毁函数
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId);

        // Clear approvals
        _approve(address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];
        delete _tokenURIs[tokenId];

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

        _afterTokenTransfer(owner, address(0), tokenId);
    }

    /**
     * @dev 内部授权函数
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev 检查代币是否存在
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _owners[tokenId] != address(0);
    }

    /**
     * @dev 检查是否被授权或是所有者
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        require(_exists(tokenId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(tokenId);
        return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
    }

    /**
     * @dev 设置代币 URI
     */
    function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
    }

    /**
     * @dev 设置基础 URI
     */
    function _setBaseURI(string memory baseURI_) internal virtual {
        _baseURI = baseURI_;
    }

    /**
     * @dev 转账前钩子函数
     */
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev 转账后钩子函数
     */
    function _afterTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal virtual {}

    /**
     * @dev 检查接收者是否实现了 ERC721Receiver 接口
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }
}

3. 功能说明

3.1 基础功能

  • 代币信息查询:名称、符号、URI
  • 代币所有权:查询所有者、余额
  • 代币转账:普通转账和安全转账
  • 代币授权:单个授权和批量授权

3.2 元数据功能

  • 代币 URI:支持设置和查询代币 URI
  • 基础 URI:支持设置和查询基础 URI
  • 元数据扩展:支持自定义元数据格式

3.3 扩展功能

  • 铸造和销毁:内部函数支持代币的铸造和销毁
  • 钩子函数:支持转账前后的自定义操作
  • 接口检查:支持 ERC165 接口检查

4. 安全机制

4.1 地址验证

  • 零地址检查:禁止与零地址进行交互
  • 地址有效性验证:确保地址格式正确
  • 合约地址检查:验证接收者是否支持 ERC721

4.2 所有权检查

  • 所有权验证:确保操作者具有权限
  • 授权验证:验证授权状态
  • 代币存在性检查:确保代币已被铸造

4.3 状态管理

  • 原子性操作:确保状态更新的原子性
  • 事件记录:记录所有重要操作的事件
  • 授权管理:安全的授权管理机制

5. 使用示例

5.1 部署合约

javascript
const name = "My NFT";
const symbol = "MNFT";
const nft = await ERC721.deploy(name, symbol);
await nft.deployed();

5.2 铸造代币

javascript
const to = "0x1234...";
const tokenId = 1;
await nft.mint(to, tokenId);

5.3 转账代币

javascript
const from = "0x1234...";
const to = "0x5678...";
const tokenId = 1;
await nft.transferFrom(from, to, tokenId);

5.4 设置元数据

javascript
const tokenId = 1;
const tokenURI = "https://example.com/token/1";
await nft.setTokenURI(tokenId, tokenURI);

6. 总结

该 ERC721 标准实现提供了一个完整的非同质化代币合约,包括:

  • 完全符合 ERC721 标准的接口实现
  • 安全的代币转账和授权机制
  • 灵活的元数据管理功能
  • 完善的安全检查机制
  • 详细的事件记录系统

通过严格遵循 ERC721 标准并添加必要的安全检查,该实现为开发者提供了一个可靠、安全的 NFT 合约基础。

Released under the MIT License by Vogeb.