05-ERC标准详解
第一部分:ERC20代币标准
1.1 ERC20标准概述
ERC20是以太坊上最广泛使用的代币标准,定义了同质化代币(Fungible Token)的接口规范。
1.1.1 标准接口
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev ERC20标准接口
*/
interface IERC20 {
// 返回代币总供应量
function totalSupply() external view returns (uint256);
// 返回账户余额
function balanceOf(address account) external view returns (uint256);
// 转账
function transfer(address recipient, uint256 amount) external returns (bool);
// 返回授权额度
function allowance(address owner, address spender) external view returns (uint256);
// 授权
function approve(address spender, uint256 amount) external returns (bool);
// 授权转账
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
// 转账事件
event Transfer(address indexed from, address indexed to, uint256 value);
// 授权事件
event Approval(address indexed owner, address indexed spender, uint256 value);
}
1.2 完整的ERC20实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title ERC20
* @dev 完整的ERC20代币实现
*/
contract ERC20 is IERC20 {
// 状态变量
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string public name;
string public symbol;
uint8 public decimals;
/**
* @dev 构造函数
*/
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
decimals = 18;
}
/**
* @dev 返回代币总供应量
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
/**
* @dev 返回账户余额
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
/**
* @dev 转账
*/
function transfer(address recipient, uint256 amount) public override returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @dev 返回授权额度
*/
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev 授权
*/
function approve(address spender, uint256 amount) public override returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
/**
* @dev 授权转账
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][msg.sender];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, msg.sender, currentAllowance - amount);
}
return true;
}
/**
* @dev 增加授权额度
* 解决approve的竞态条件问题
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
return true;
}
/**
* @dev 减少授权额度
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
uint256 currentAllowance = _allowances[msg.sender][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(msg.sender, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev 内部转账函数
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
/**
* @dev 铸造代币
*/
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}
/**
* @dev 销毁代币
*/
function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from the zero address");
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
}
/**
* @dev 内部授权函数
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
}
1.3 扩展功能的ERC20
1.3.1 可铸造的代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC20Mintable is ERC20 {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
owner = msg.sender;
}
/**
* @dev 铸造代币
*/
function mint(address account, uint256 amount) public onlyOwner {
_mint(account, amount);
}
/**
* @dev 转移所有权
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "New owner is zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
1.3.2 可销毁的代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC20Burnable is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
/**
* @dev 销毁自己的代币
*/
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
/**
* @dev 销毁授权的代币
*/
function burnFrom(address account, uint256 amount) public {
uint256 currentAllowance = allowance(account, msg.sender);
require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
unchecked {
_approve(account, msg.sender, currentAllowance - amount);
}
_burn(account, amount);
}
}
1.3.3 可暂停的代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC20Pausable is ERC20 {
address public owner;
bool public paused;
event Paused(address account);
event Unpaused(address account);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier whenNotPaused() {
require(!paused, "Pausable: paused");
_;
}
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
owner = msg.sender;
}
/**
* @dev 暂停
*/
function pause() public onlyOwner {
paused = true;
emit Paused(msg.sender);
}
/**
* @dev 恢复
*/
function unpause() public onlyOwner {
paused = false;
emit Unpaused(msg.sender);
}
/**
* @dev 重写transfer,添加暂停检查
*/
function transfer(address recipient, uint256 amount)
public
override
whenNotPaused
returns (bool)
{
return super.transfer(recipient, amount);
}
/**
* @dev 重写transferFrom,添加暂停检查
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) public override whenNotPaused returns (bool) {
return super.transferFrom(sender, recipient, amount);
}
}
1.3.4 带快照功能的代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC20Snapshot is ERC20 {
address public owner;
// 快照ID
uint256 private _currentSnapshotId;
// 快照数据结构
struct Snapshots {
uint256[] ids;
uint256[] values;
}
// 账户余额快照
mapping(address => Snapshots) private _accountBalanceSnapshots;
// 总供应量快照
Snapshots private _totalSupplySnapshots;
event Snapshot(uint256 id);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
owner = msg.sender;
}
/**
* @dev 创建快照
*/
function snapshot() public onlyOwner returns (uint256) {
_currentSnapshotId++;
emit Snapshot(_currentSnapshotId);
return _currentSnapshotId;
}
/**
* @dev 获取快照时的余额
*/
function balanceOfAt(address account, uint256 snapshotId)
public
view
returns (uint256)
{
(bool snapshotted, uint256 value) = _valueAt(
snapshotId,
_accountBalanceSnapshots[account]
);
return snapshotted ? value : balanceOf(account);
}
/**
* @dev 获取快照时的总供应量
*/
function totalSupplyAt(uint256 snapshotId) public view returns (uint256) {
(bool snapshotted, uint256 value) = _valueAt(snapshotId, _totalSupplySnapshots);
return snapshotted ? value : totalSupply();
}
/**
* @dev 重写transfer,更新快照
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal override {
_updateAccountSnapshot(sender);
_updateAccountSnapshot(recipient);
super._transfer(sender, recipient, amount);
}
/**
* @dev 重写mint,更新快照
*/
function _mint(address account, uint256 amount) internal override {
_updateAccountSnapshot(account);
_updateTotalSupplySnapshot();
super._mint(account, amount);
}
/**
* @dev 重写burn,更新快照
*/
function _burn(address account, uint256 amount) internal override {
_updateAccountSnapshot(account);
_updateTotalSupplySnapshot();
super._burn(account, amount);
}
function _valueAt(uint256 snapshotId, Snapshots storage snapshots)
private
view
returns (bool, uint256)
{
require(snapshotId > 0, "Snapshot id is 0");
require(snapshotId <= _currentSnapshotId, "Nonexistent snapshot id");
uint256 index = _findIndex(snapshots.ids, snapshotId);
if (index == snapshots.ids.length) {
return (false, 0);
} else {
return (true, snapshots.values[index]);
}
}
function _updateAccountSnapshot(address account) private {
_updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
}
function _updateTotalSupplySnapshot() private {
_updateSnapshot(_totalSupplySnapshots, totalSupply());
}
function _updateSnapshot(Snapshots storage snapshots, uint256 currentValue) private {
uint256 currentId = _currentSnapshotId;
if (_lastSnapshotId(snapshots.ids) < currentId) {
snapshots.ids.push(currentId);
snapshots.values.push(currentValue);
}
}
function _lastSnapshotId(uint256[] storage ids) private view returns (uint256) {
if (ids.length == 0) {
return 0;
} else {
return ids[ids.length - 1];
}
}
function _findIndex(uint256[] storage ids, uint256 snapshotId)
private
view
returns (uint256)
{
uint256 low = 0;
uint256 high = ids.length;
while (low < high) {
uint256 mid = (low + high) / 2;
if (ids[mid] > snapshotId) {
high = mid;
} else {
low = mid + 1;
}
}
return high;
}
}
1.4 实际应用案例
1.4.1 治理代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title GovernanceToken
* @dev 用于DAO治理的代币
*/
contract GovernanceToken is ERC20Snapshot {
// 提案结构
struct Proposal {
string description;
uint256 forVotes;
uint256 againstVotes;
uint256 snapshotId;
uint256 deadline;
bool executed;
mapping(address => bool) hasVoted;
}
Proposal[] public proposals;
event ProposalCreated(uint256 indexed proposalId, string description, uint256 deadline);
event Voted(uint256 indexed proposalId, address indexed voter, bool support, uint256 weight);
event ProposalExecuted(uint256 indexed proposalId);
constructor() ERC20Snapshot("Governance Token", "GOV") {
_mint(msg.sender, 1000000 * 10**18);
}
/**
* @dev 创建提案
*/
function createProposal(string memory description, uint256 votingPeriod)
public
returns (uint256)
{
require(balanceOf(msg.sender) >= 1000 * 10**18, "Insufficient tokens to propose");
uint256 snapshotId = snapshot();
uint256 proposalId = proposals.length;
Proposal storage newProposal = proposals.push();
newProposal.description = description;
newProposal.snapshotId = snapshotId;
newProposal.deadline = block.timestamp + votingPeriod;
emit ProposalCreated(proposalId, description, newProposal.deadline);
return proposalId;
}
/**
* @dev 投票
*/
function vote(uint256 proposalId, bool support) public {
Proposal storage proposal = proposals[proposalId];
require(block.timestamp < proposal.deadline, "Voting ended");
require(!proposal.hasVoted[msg.sender], "Already voted");
uint256 weight = balanceOfAt(msg.sender, proposal.snapshotId);
require(weight > 0, "No voting power");
proposal.hasVoted[msg.sender] = true;
if (support) {
proposal.forVotes += weight;
} else {
proposal.againstVotes += weight;
}
emit Voted(proposalId, msg.sender, support, weight);
}
/**
* @dev 执行提案
*/
function executeProposal(uint256 proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(block.timestamp >= proposal.deadline, "Voting not ended");
require(!proposal.executed, "Already executed");
require(proposal.forVotes > proposal.againstVotes, "Proposal rejected");
proposal.executed = true;
emit ProposalExecuted(proposalId);
// 执行提案逻辑...
}
/**
* @dev 获取提案信息
*/
function getProposal(uint256 proposalId)
public
view
returns (
string memory description,
uint256 forVotes,
uint256 againstVotes,
uint256 deadline,
bool executed
)
{
Proposal storage proposal = proposals[proposalId];
return (
proposal.description,
proposal.forVotes,
proposal.againstVotes,
proposal.deadline,
proposal.executed
);
}
}
第二部分:ERC721 NFT标准
2.1 ERC721标准概述
ERC721是非同质化代币(Non-Fungible Token, NFT)标准,每个代币都是唯一的。
2.1.1 标准接口
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev ERC721标准接口
*/
interface IERC721 {
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);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}
/**
* @dev ERC721接收器接口
*/
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
/**
* @dev ERC721元数据接口
*/
interface IERC721Metadata {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);
}
2.2 完整的ERC721实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title ERC721
* @dev 完整的ERC721实现
*/
contract ERC721 is IERC721, IERC721Metadata {
// Token名称
string private _name;
// Token符号
string private _symbol;
// tokenId到owner的映射
mapping(uint256 => address) private _owners;
// owner到token数量的映射
mapping(address => uint256) private _balances;
// tokenId到授权地址的映射
mapping(uint256 => address) private _tokenApprovals;
// owner到operator授权的映射
mapping(address => mapping(address => bool)) private _operatorApprovals;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev 返回token名称
*/
function name() public view override returns (string memory) {
return _name;
}
/**
* @dev 返回token符号
*/
function symbol() public view override returns (string memory) {
return _symbol;
}
/**
* @dev 返回token URI
*/
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, _toString(tokenId)))
: "";
}
/**
* @dev 基础URI
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev 返回owner的token数量
*/
function balanceOf(address owner) public view override returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address");
return _balances[owner];
}
/**
* @dev 返回tokenId的owner
*/
function ownerOf(uint256 tokenId) public view override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: owner query for nonexistent token");
return owner;
}
/**
* @dev 授权
*/
function approve(address to, uint256 tokenId) public override {
address owner = ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
msg.sender == owner || isApprovedForAll(owner, msg.sender),
"ERC721: approve caller is not owner nor approved for all"
);
_approve(to, tokenId);
}
/**
* @dev 获取tokenId的授权地址
*/
function getApproved(uint256 tokenId) public view override returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId];
}
/**
* @dev 设置operator授权
*/
function setApprovalForAll(address operator, bool approved) public override {
require(operator != msg.sender, "ERC721: approve to caller");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
/**
* @dev 检查operator是否被授权
*/
function isApprovedForAll(address owner, address operator)
public
view
override
returns (bool)
{
return _operatorApprovals[owner][operator];
}
/**
* @dev 转移token
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public override {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
/**
* @dev 安全转移token
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev 安全转移token(带数据)
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public override {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(from, to, tokenId, data);
}
/**
* @dev 内部安全转移函数
*/
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory data
) internal {
_transfer(from, to, tokenId);
require(
_checkOnERC721Received(from, to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev 检查token是否存在
*/
function _exists(uint256 tokenId) internal view returns (bool) {
return _owners[tokenId] != address(0);
}
/**
* @dev 检查spender是否被授权
*/
function _isApprovedOrOwner(address spender, uint256 tokenId)
internal
view
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 铸造token
*/
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
/**
* @dev 安全铸造token
*/
function _safeMint(address to, uint256 tokenId) internal {
_safeMint(to, tokenId, "");
}
/**
* @dev 安全铸造token(带数据)
*/
function _safeMint(
address to,
uint256 tokenId,
bytes memory data
) internal {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
/**
* @dev 销毁token
*/
function _burn(uint256 tokenId) internal {
address owner = ownerOf(tokenId);
// 清除授权
_approve(address(0), tokenId);
_balances[owner] -= 1;
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
}
/**
* @dev 内部转移函数
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal {
require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
require(to != address(0), "ERC721: transfer to the zero address");
// 清除授权
_approve(address(0), tokenId);
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
}
/**
* @dev 内部授权函数
*/
function _approve(address to, uint256 tokenId) internal {
_tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId);
}
/**
* @dev 检查ERC721接收
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory data
) private returns (bool) {
if (_isContract(to)) {
try IERC721Receiver(to).onERC721Received(msg.sender, 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;
}
}
/**
* @dev 检查是否为合约
*/
function _isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
/**
* @dev 将uint256转为string
*/
function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}
2.3 ERC721扩展功能
2.3.1 可枚举的NFT
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721Enumerable {
function totalSupply() external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
}
contract ERC721Enumerable is ERC721, IERC721Enumerable {
// 所有token的数组
uint256[] private _allTokens;
// tokenId到索引的映射
mapping(uint256 => uint256) private _allTokensIndex;
// owner的token数组
mapping(address => uint256[]) private _ownedTokens;
// tokenId到owner索引的映射
mapping(uint256 => uint256) private _ownedTokensIndex;
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {}
/**
* @dev 返回总供应量
*/
function totalSupply() public view override returns (uint256) {
return _allTokens.length;
}
/**
* @dev 根据索引返回tokenId
*/
function tokenByIndex(uint256 index) public view override returns (uint256) {
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
return _allTokens[index];
}
/**
* @dev 根据owner和索引返回tokenId
*/
function tokenOfOwnerByIndex(address owner, uint256 index)
public
view
override
returns (uint256)
{
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
return _ownedTokens[owner][index];
}
/**
* @dev 重写_mint
*/
function _mint(address to, uint256 tokenId) internal override {
super._mint(to, tokenId);
_addTokenToAllTokensEnumeration(tokenId);
_addTokenToOwnerEnumeration(to, tokenId);
}
/**
* @dev 重写_burn
*/
function _burn(uint256 tokenId) internal override {
address owner = ownerOf(tokenId);
super._burn(tokenId);
_removeTokenFromAllTokensEnumeration(tokenId);
_removeTokenFromOwnerEnumeration(owner, tokenId);
}
/**
* @dev 重写_transfer
*/
function _transfer(
address from,
address to,
uint256 tokenId
) internal override {
super._transfer(from, to, tokenId);
_removeTokenFromOwnerEnumeration(from, tokenId);
_addTokenToOwnerEnumeration(to, tokenId);
}
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
_allTokensIndex[tokenId] = _allTokens.length;
_allTokens.push(tokenId);
}
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
_ownedTokens[to].push(tokenId);
}
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
uint256 lastTokenIndex = _ownedTokens[from].length - 1;
uint256 tokenIndex = _ownedTokensIndex[tokenId];
if (tokenIndex != lastTokenIndex) {
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
_ownedTokens[from][tokenIndex] = lastTokenId;
_ownedTokensIndex[lastTokenId] = tokenIndex;
}
delete _ownedTokensIndex[tokenId];
_ownedTokens[from].pop();
}
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
uint256 lastTokenIndex = _allTokens.length - 1;
uint256 tokenIndex = _allTokensIndex[tokenId];
uint256 lastTokenId = _allTokens[lastTokenIndex];
_allTokens[tokenIndex] = lastTokenId;
_allTokensIndex[lastTokenId] = tokenIndex;
delete _allTokensIndex[tokenId];
_allTokens.pop();
}
}
2.3.2 NFT市场合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title NFTMarketplace
* @dev NFT交易市场
*/
contract NFTMarketplace {
struct Listing {
address seller;
uint256 price;
bool active;
}
// NFT合约地址 => tokenId => 挂单信息
mapping(address => mapping(uint256 => Listing)) public listings;
// 平台手续费率(基点,1% = 100)
uint256 public feeRate = 250; // 2.5%
address public feeRecipient;
event Listed(
address indexed nftContract,
uint256 indexed tokenId,
address indexed seller,
uint256 price
);
event Sold(
address indexed nftContract,
uint256 indexed tokenId,
address indexed buyer,
address seller,
uint256 price
);
event Cancelled(
address indexed nftContract,
uint256 indexed tokenId,
address indexed seller
);
constructor(address _feeRecipient) {
feeRecipient = _feeRecipient;
}
/**
* @dev 挂单出售NFT
*/
function listNFT(
address nftContract,
uint256 tokenId,
uint256 price
) external {
require(price > 0, "Price must be greater than 0");
IERC721 nft = IERC721(nftContract);
require(nft.ownerOf(tokenId) == msg.sender, "Not token owner");
require(
nft.isApprovedForAll(msg.sender, address(this)) ||
nft.getApproved(tokenId) == address(this),
"Marketplace not approved"
);
listings[nftContract][tokenId] = Listing({
seller: msg.sender,
price: price,
active: true
});
emit Listed(nftContract, tokenId, msg.sender, price);
}
/**
* @dev 购买NFT
*/
function buyNFT(address nftContract, uint256 tokenId) external payable {
Listing memory listing = listings[nftContract][tokenId];
require(listing.active, "Listing not active");
require(msg.value >= listing.price, "Insufficient payment");
// 计算费用
uint256 fee = (listing.price * feeRate) / 10000;
uint256 sellerProceeds = listing.price - fee;
// 删除挂单
delete listings[nftContract][tokenId];
// 转移NFT
IERC721(nftContract).safeTransferFrom(listing.seller, msg.sender, tokenId);
// 转账
payable(listing.seller).transfer(sellerProceeds);
payable(feeRecipient).transfer(fee);
// 退还多余ETH
if (msg.value > listing.price) {
payable(msg.sender).transfer(msg.value - listing.price);
}
emit Sold(nftContract, tokenId, msg.sender, listing.seller, listing.price);
}
/**
* @dev 取消挂单
*/
function cancelListing(address nftContract, uint256 tokenId) external {
Listing memory listing = listings[nftContract][tokenId];
require(listing.active, "Listing not active");
require(listing.seller == msg.sender, "Not seller");
delete listings[nftContract][tokenId];
emit Cancelled(nftContract, tokenId, msg.sender);
}
/**
* @dev 更新价格
*/
function updatePrice(
address nftContract,
uint256 tokenId,
uint256 newPrice
) external {
Listing storage listing = listings[nftContract][tokenId];
require(listing.active, "Listing not active");
require(listing.seller == msg.sender, "Not seller");
require(newPrice > 0, "Price must be greater than 0");
listing.price = newPrice;
emit Listed(nftContract, tokenId, msg.sender, newPrice);
}
}
第三部分:ERC1155多代币标准
3.1 ERC1155标准概述
ERC1155是多代币标准,可以在单个合约中管理多种类型的代币(同质化和非同质化)。
3.1.1 标准接口
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC1155 {
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
event ApprovalForAll(address indexed account, address indexed operator, bool approved);
event URI(string value, uint256 indexed id);
function balanceOf(address account, uint256 id) external view returns (uint256);
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external
view
returns (uint256[] memory);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address account, address operator) external view returns (bool);
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;
function safeBatchTransferFrom(
address from,
address to,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external;
}
interface IERC1155Receiver {
function onERC1155Received(
address operator,
address from,
uint256 id,
uint256 value,
bytes calldata data
) external returns (bytes4);
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata values,
bytes calldata data
) external returns (bytes4);
}
interface IERC1155MetadataURI {
function uri(uint256 id) external view returns (string memory);
}
3.2 完整的ERC1155实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC1155 is IERC1155, IERC1155MetadataURI {
// id => account => balance
mapping(uint256 => mapping(address => uint256)) private _balances;
// account => operator => approved
mapping(address => mapping(address => bool)) private _operatorApprovals;
// URI
string private _uri;
constructor(string memory uri_) {
_uri = uri_;
}
function uri(uint256) public view virtual override returns (string memory) {
return _uri;
}
function balanceOf(address account, uint256 id)
public
view
override
returns (uint256)
{
require(account != address(0), "ERC1155: balance query for the zero address");
return _balances[id][account];
}
function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
public
view
override
returns (uint256[] memory)
{
require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");
uint256[] memory batchBalances = new uint256[](accounts.length);
for (uint256 i = 0; i < accounts.length; ++i) {
batchBalances[i] = balanceOf(accounts[i], ids[i]);
}
return batchBalances;
}
function setApprovalForAll(address operator, bool approved) public virtual override {
require(msg.sender != operator, "ERC1155: setting approval status for self");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function isApprovedForAll(address account, address operator)
public
view
override
returns (bool)
{
return _operatorApprovals[account][operator];
}
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) public virtual override {
require(
from == msg.sender || isApprovedForAll(from, msg.sender),
"ERC1155: caller is not owner nor approved"
);
_safeTransferFrom(from, to, id, amount, data);
}
function safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) public virtual override {
require(
from == msg.sender || isApprovedForAll(from, msg.sender),
"ERC1155: transfer caller is not owner nor approved"
);
_safeBatchTransferFrom(from, to, ids, amounts, data);
}
function _safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal {
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = msg.sender;
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
emit TransferSingle(operator, from, to, id, amount);
_doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
}
function _safeBatchTransferFrom(
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
require(to != address(0), "ERC1155: transfer to the zero address");
address operator = msg.sender;
for (uint256 i = 0; i < ids.length; ++i) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
unchecked {
_balances[id][from] = fromBalance - amount;
}
_balances[id][to] += amount;
}
emit TransferBatch(operator, from, to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
}
function _mint(
address to,
uint256 id,
uint256 amount,
bytes memory data
) internal {
require(to != address(0), "ERC1155: mint to the zero address");
address operator = msg.sender;
_balances[id][to] += amount;
emit TransferSingle(operator, address(0), to, id, amount);
_doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
}
function _mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal {
require(to != address(0), "ERC1155: mint to the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = msg.sender;
for (uint256 i = 0; i < ids.length; i++) {
_balances[ids[i]][to] += amounts[i];
}
emit TransferBatch(operator, address(0), to, ids, amounts);
_doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
}
function _burn(
address from,
uint256 id,
uint256 amount
) internal {
require(from != address(0), "ERC1155: burn from the zero address");
address operator = msg.sender;
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
emit TransferSingle(operator, from, address(0), id, amount);
}
function _burnBatch(
address from,
uint256[] memory ids,
uint256[] memory amounts
) internal {
require(from != address(0), "ERC1155: burn from the zero address");
require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
address operator = msg.sender;
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
uint256 amount = amounts[i];
uint256 fromBalance = _balances[id][from];
require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
unchecked {
_balances[id][from] = fromBalance - amount;
}
}
emit TransferBatch(operator, from, address(0), ids, amounts);
}
function _doSafeTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256 id,
uint256 amount,
bytes memory data
) private {
if (_isContract(to)) {
try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (
bytes4 response
) {
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non ERC1155Receiver implementer");
}
}
}
function _doSafeBatchTransferAcceptanceCheck(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) private {
if (_isContract(to)) {
try
IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data)
returns (bytes4 response) {
if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
revert("ERC1155: ERC1155Receiver rejected tokens");
}
} catch Error(string memory reason) {
revert(reason);
} catch {
revert("ERC1155: transfer to non ERC1155Receiver implementer");
}
}
}
function _isContract(address account) private view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
}
3.3 游戏道具合约示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title GameItems
* @dev 游戏道具合约,使用ERC1155管理多种道具
*/
contract GameItems is ERC1155 {
address public owner;
uint256 public constant GOLD_COIN = 0;
uint256 public constant SILVER_COIN = 1;
uint256 public constant SWORD = 2;
uint256 public constant SHIELD = 3;
uint256 public constant POTION = 4;
// 道具供应上限
mapping(uint256 => uint256) public itemSupplyCap;
// 当前供应量
mapping(uint256 => uint256) public itemSupply;
event ItemCrafted(address indexed player, uint256 indexed itemId, uint256 amount);
event ItemUsed(address indexed player, uint256 indexed itemId, uint256 amount);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor() ERC1155("https://game.example/api/item/{id}.json") {
owner = msg.sender;
// 设置供应上限
itemSupplyCap[GOLD_COIN] = type(uint256).max; // 无上限
itemSupplyCap[SILVER_COIN] = type(uint256).max;
itemSupplyCap[SWORD] = 10000;
itemSupplyCap[SHIELD] = 10000;
itemSupplyCap[POTION] = 50000;
}
/**
* @dev 铸造游戏道具
*/
function mintItem(
address to,
uint256 id,
uint256 amount
) public onlyOwner {
require(
itemSupply[id] + amount <= itemSupplyCap[id],
"Exceeds supply cap"
);
itemSupply[id] += amount;
_mint(to, id, amount, "");
}
/**
* @dev 批量铸造道具
*/
function mintBatch(
address to,
uint256[] memory ids,
uint256[] memory amounts
) public onlyOwner {
require(ids.length == amounts.length, "Length mismatch");
for (uint256 i = 0; i < ids.length; i++) {
require(
itemSupply[ids[i]] + amounts[i] <= itemSupplyCap[ids[i]],
"Exceeds supply cap"
);
itemSupply[ids[i]] += amounts[i];
}
_mintBatch(to, ids, amounts, "");
}
/**
* @dev 合成道具
* 例如:2个银币 = 1个金币
*/
function craftGoldCoin() external {
require(balanceOf(msg.sender, SILVER_COIN) >= 2, "Insufficient silver coins");
_burn(msg.sender, SILVER_COIN, 2);
_mint(msg.sender, GOLD_COIN, 1, "");
emit ItemCrafted(msg.sender, GOLD_COIN, 1);
}
/**
* @dev 使用药水(销毁)
*/
function usePotion(uint256 amount) external {
require(balanceOf(msg.sender, POTION) >= amount, "Insufficient potions");
_burn(msg.sender, POTION, amount);
emit ItemUsed(msg.sender, POTION, amount);
}
/**
* @dev 批量转移道具
*/
function giftItems(
address to,
uint256[] memory ids,
uint256[] memory amounts
) external {
safeBatchTransferFrom(msg.sender, to, ids, amounts, "");
}
}
第四部分:ERC2612(EIP-2612)许可代币
4.1 ERC2612标准概述
ERC2612扩展了ERC20,添加了permit函数,允许用户通过签名授权,无需单独发送授权交易。
4.1.1 标准接口
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC2612 {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
4.2 完整的ERC2612实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ERC20Permit is ERC20, IERC2612 {
mapping(address => uint256) private _nonces;
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
uint256 private immutable _CACHED_CHAIN_ID;
address private immutable _CACHED_THIS;
bytes32 private immutable _HASHED_NAME;
bytes32 private immutable _HASHED_VERSION;
bytes32 private immutable _TYPE_HASH;
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
bytes32 hashedName = keccak256(bytes(name));
bytes32 hashedVersion = keccak256(bytes("1"));
bytes32 typeHash = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
_HASHED_NAME = hashedName;
_HASHED_VERSION = hashedVersion;
_CACHED_CHAIN_ID = block.chainid;
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
_CACHED_THIS = address(this);
_TYPE_HASH = typeHash;
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual override {
require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
bytes32 structHash = keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline)
);
bytes32 hash = _hashTypedDataV4(structHash);
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
_approve(owner, spender, value);
}
function nonces(address owner) public view virtual override returns (uint256) {
return _nonces[owner];
}
function DOMAIN_SEPARATOR() external view override returns (bytes32) {
return _domainSeparatorV4();
}
function _domainSeparatorV4() internal view returns (bytes32) {
if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {
return _CACHED_DOMAIN_SEPARATOR;
} else {
return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
}
}
function _buildDomainSeparator(
bytes32 typeHash,
bytes32 nameHash,
bytes32 versionHash
) private view returns (bytes32) {
return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));
}
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
}
function _useNonce(address owner) internal virtual returns (uint256 current) {
current = _nonces[owner];
_nonces[owner] = current + 1;
}
}
4.3 使用Permit的实际示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title TokenSwapWithPermit
* @dev 使用permit的代币交换合约
*/
contract TokenSwapWithPermit {
IERC20Permit public tokenA;
IERC20Permit public tokenB;
event Swapped(address indexed user, uint256 amountA, uint256 amountB);
constructor(address _tokenA, address _tokenB) {
tokenA = IERC20Permit(_tokenA);
tokenB = IERC20Permit(_tokenB);
}
/**
* @dev 传统方式:需要先调用approve
*/
function swapTraditional(uint256 amountA) external {
// 用户必须先调用tokenA.approve(address(this), amountA)
tokenA.transferFrom(msg.sender, address(this), amountA);
uint256 amountB = amountA; // 1:1兑换
tokenB.transfer(msg.sender, amountB);
emit Swapped(msg.sender, amountA, amountB);
}
/**
* @dev 使用permit:一次交易完成授权和兑换
*/
function swapWithPermit(
uint256 amountA,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// 使用签名授权
tokenA.permit(msg.sender, address(this), amountA, deadline, v, r, s);
// 执行转账
tokenA.transferFrom(msg.sender, address(this), amountA);
uint256 amountB = amountA;
tokenB.transfer(msg.sender, amountB);
emit Swapped(msg.sender, amountA, amountB);
}
}
第五部分:标准对比和最佳实践
5.1 ERC标准对比
| 特性 | ERC20 | ERC721 | ERC1155 |
|---|---|---|---|
| 代币类型 | 同质化 | 非同质化 | 多代币(同质+非同质) |
| 唯一性 | 所有代币相同 | 每个代币唯一 | 支持两种类型 |
| 批量转账 | 不支持 | 不支持 | 支持 |
| Gas效率 | 中等 | 较高 | 最优 |
| 使用场景 | 货币、积分 | 艺术品、收藏品 | 游戏道具、多类资产 |
| 复杂度 | 简单 | 中等 | 较复杂 |
5.2 选择建议
ERC20:适用于同质化代币
- 加密货币
- 治理代币
- 积分系统
- 稳定币
ERC721:适用于唯一资产
- 数字艺术品
- 域名
- 虚拟土地
- 身份证明
ERC1155:适用于多种资产
- 游戏道具
- 票务系统
- 混合资产平台
- 需要批量操作的场景
5.3 安全最佳实践
ERC20安全
- 使用
increaseAllowance和decreaseAllowance避免竞态条件 - 检查地址不为零地址
- 防止整数溢出(0.8.x自动检查)
- 实现暂停机制
- 使用
ERC721安全
- 使用
safeTransferFrom而非transferFrom - 验证token存在性
- 检查授权和所有权
- 防止重入攻击
- 使用
ERC1155安全
- 批量操作时检查数组长度
- 实现接收检查
- 验证余额充足
- 防止整数溢出
5.4 Gas优化技巧
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasOptimizedERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
// 使用immutable减少存储读取
string public immutable name;
string public immutable symbol;
uint8 public immutable decimals;
constructor(string memory _name, string memory _symbol) {
name = _name;
symbol = _symbol;
decimals = 18;
}
function transfer(address to, uint256 amount) external returns (bool) {
// 缓存变量减少存储读取
address sender = msg.sender;
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "Insufficient balance");
// 使用unchecked减少gas
unchecked {
_balances[sender] = senderBalance - amount;
_balances[to] += amount;
}
return true;
}
// 批量转账节省gas
function batchTransfer(address[] calldata recipients, uint256[] calldata amounts)
external
returns (bool)
{
require(recipients.length == amounts.length, "Length mismatch");
address sender = msg.sender;
uint256 totalAmount = 0;
// 先计算总额
for (uint256 i = 0; i < amounts.length; ) {
totalAmount += amounts[i];
unchecked { i++; }
}
require(_balances[sender] >= totalAmount, "Insufficient balance");
// 执行转账
unchecked {
_balances[sender] -= totalAmount;
}
for (uint256 i = 0; i < recipients.length; ) {
_balances[recipients[i]] += amounts[i];
unchecked { i++; }
}
return true;
}
}