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 合约基础。