ERC20 全称 “Ethereum Request for Comment 20”,是一种标准接口,用于实现代币合约。ERC20 标准定义了一组函数和事件,使得代币可以在不同的应用和平台之间相互操作。
ERC20 标准接口定义了一组必须实现的函数和事件:
interface IERC20 {
// 查询代币的总供应量
function totalSupply() external view returns (uint);
// 查询 account 的代币余额
function balanceOf(address account) external view returns (uint);
// 查询 owner 授权给 spender 的代币数量
function allowance(
address owner,
address spender
) external view returns (uint);
// 从调用者转移 amount 数量的代币到 recipient
function transfer(address recipient, uint amount) external returns (bool);
// 授权 spender 可以转移调用者 amount 数量的代币
function approve(address spender, uint amount) external returns (bool);
// 调用者从 sender 转移 amount 数量的代币到 recipient, 需要先通过 approve 授权
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
}
// 在转移代币时触发, 包括零值转移
event Transfer(address indexed from, address indexed to, uint value);
// 在调用 approve 时触发
event Approval(address indexed owner, address indexed spender, uint value);
以下是一个简单的 ERC20 代币合约示例:
contract ERC20 is IERC20 {
// 存储代币的名称、符号和小数位数
string public name = "TestToken";
string public symbol = "TTK";
uint8 public decimals = 18;
// 存储代币的总供应量
uint public totalSupply;
// 存储每个地址的代币余额; owner => balance
mapping(address => uint) public balanceOf;
// 存储每个地址对其他地址的授权额度; owner => spender => amount
mapping(address => mapping(address => uint)) public allowance;
// 从调用者转移 amount 数量的代币到 recipient
function transfer(
address recipient,
uint amount
) external override returns (bool) {
require(
balanceOf[msg.sender] >= amount,
"ERC20: transfer amount exceeds balance"
);
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
// 授权 spender 可以转移调用者 amount 数量的代币
function approve(
address spender,
uint amount
) external override returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
// 调用者从 sender 转移 amount 数量的代币到 recipient, 需要先通过 approve 授权
function transferFrom(
address sender,
address recipient,
uint amount
) external override returns (bool) {
require(
balanceOf[sender] >= amount,
"ERC20: transfer amount exceeds balance"
);
require(
allowance[sender][msg.sender] >= amount,
"ERC20: transfer amount exceeds allowance"
);
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
allowance[sender][msg.sender] -= amount;
emit Transfer(sender, recipient, amount);
return true;
}
}
除了上述核心方法,你可能还听过 mint
& burn
。它们通常用于 ERC20 代币合约中,以增加或减少代币的总供应量。
// 为调用者创建 amount 数量的代币
function mint(uint amount) external {
totalSupply += amount;
balanceOf[msg.sender] += amount;
emit Transfer(address(0), msg.sender, amount);
}
// 为调用者销毁 amount 数量的代币
function burn(uint amount) external {
require(
balanceOf[msg.sender] >= amount,
"ERC20: burn amount exceeds balance"
);
totalSupply -= amount;
balanceOf[msg.sender] -= amount;
emit Transfer(msg.sender, address(0), amount);
}
OpenZeppelin 提供了安全且经过审计的 ERC20 实现,使用 OpenZeppelin 库可以简化 ERC20 代币的实现:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(
string memory name,
string memory symbol,
uint initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply * 10 ** uint(decimals()));
}
}
ERC721 是用于创建非同质化代币(NFT,Non-Fungible Token)的标准。与 ERC20 不同,ERC721 代币是独一无二的,每个代币都有自己的唯一标识符。
ERC721 标准定义了一组必须实现的函数和事件,使得代币可以在不同的应用和平台之间互操作:
interface IERC165 {
// 查询合约是否支持某个接口
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
interface IERC721 is IERC165 {
// 查询 owner 拥有的代币数量
function balanceOf(address owner) external view returns (uint balance);
// 查询代币 tokenId 的拥有者
function ownerOf(uint tokenId) external view returns (address owner);
// 安全地将代币 tokenId 从 from 转移到 to
function safeTransferFrom(address from, address to, uint tokenId) external;
// 安全地将代币 tokenId 从 from 转移到 to, 并传递额外数据
function safeTransferFrom(
address from,
address to,
uint tokenId,
bytes calldata data
) external;
// 将代币 tokenId 从 from 转移到 to
function transferFrom(address from, address to, uint tokenId) external;
// 批准 to 可以转移代币 tokenId
function approve(address to, uint tokenId) external;
// 查询被批准可以转移代币 tokenId 的地址
function getApproved(uint tokenId) external view returns (address operator);
// 批准或撤销 operator 可以管理调用者所有的代币
function setApprovalForAll(address operator, bool _approved) external;
// 查询 operator 是否被批准可以管理 owner 的所有代币
function isApprovedForAll(
address owner,
address operator
) external view returns (bool);
}
// 代币转移时触发
event Transfer(
address indexed from,
address indexed to,
uint indexed tokenId
);
// 批准代币转移时触发
event Approval(
address indexed owner,
address indexed operator,
uint indexed tokenId
);
// 批准或撤销 operator 可以管理调用者所有的代币时触发
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
为实现 safeTransferFrom 方法,还需要一个辅助接口:
interface IERC721Receiver {
// 检查指定合约是否支持接收代币
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}
开始实现 ERC721:
contract ERC721 is IERC721 {
// tokenId => owner, 记录代币的拥有者
mapping(uint => address) internal _owners;
// owner => token 数量, 记录 owner 拥有的代币数量
mapping(address => uint) internal _balances;
// tokenId => operator, 记录被批准可以转移代币 tokenId 的地址
mapping(uint => address) internal _tokenApprovals;
// owner => operator => approved, 记录 operator 是否被批准可以管理 owner 的所有代币
mapping(address => mapping(address => bool)) internal _operatorApprovals;
// 查询合约是否支持 IERC721、IERC165 接口
function supportsInterface(
bytes4 interfaceId
) external pure override returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
// 查询 owner 拥有的代币数量
function balanceOf(address owner) external view override returns (uint) {
require(
owner != address(0),
"ERC721: balance query for the zero address"
);
return _balances[owner];
}
// 查询代币 tokenId 的拥有者
function ownerOf(uint tokenId) external view override returns (address) {
address owener = _owners[tokenId];
require(
owener != address(0),
"ERC721: owner query for nonexistent token"
);
return owener;
}
// 批准或撤销 operator 可以管理调用者所有的代币
function setApprovalForAll(
address operator,
bool _approved
) external override {
_operatorApprovals[msg.sender][operator] = _approved;
emit ApprovalForAll(msg.sender, operator, _approved);
}
// 批准 to 可以转移代币 tokenId
function approve(address to, uint tokenId) external override {
address owner = _owners[tokenId];
require(
msg.sender == owner || _operatorApprovals[owner][msg.sender],
"ERC721: approve caller is not owner nor approved for all"
);
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
// 查询被批准可以转移代币 tokenId 的地址
function getApproved(
uint tokenId
) external view override returns (address) {
require(
_owners[tokenId] != address(0),
"ERC721: approved query for nonexistent token"
);
return _tokenApprovals[tokenId];
}
// 查询 spender 是否能转移 tokenId
function _isApprovedOrOwner(
address owner,
address spender,
uint tokenId
) private view returns (bool) {
return
owner == spender ||
_tokenApprovals[tokenId] == spender ||
_operatorApprovals[owner][spender];
}
// 将代币 tokenId 从 from 转移到 to
function transferFrom(
address from,
address to,
uint tokenId
) public override {
require(
_owners[tokenId] == from,
"ERC721: transfer of token that is not own"
);
require(to != address(0), "ERC721: transfer to the zero address");
require(
_isApprovedOrOwner(from, msg.sender, tokenId),
"ERC721: transfer caller is not owner nor approved"
);
_balances[from]--;
_balances[to]++;
_owners[tokenId] = to;
delete _tokenApprovals[tokenId];
emit Transfer(from, to, tokenId);
}
// 安全地将代币 tokenId 从 from 转移到 to
function safeTransferFrom(
address from,
address to,
uint tokenId
) external override {
transferFrom(from, to, tokenId);
// 检查是否为合约地址
// 如果是合约地址, 则需要检查合约是否支持接收代币
require(
to.code.length == 0 ||
IERC721Receiver(to).onERC721Received(
msg.sender,
from,
tokenId,
""
) ==
IERC721Receiver.onERC721Received.selector,
"ERC721: transfer to non ERC721Receiver implementer"
);
}
// 安全地将代币 tokenId 从 from 转移到 to, 并传递额外数据
function safeTransferFrom(
address from,
address to,
uint tokenId,
bytes calldata data
) external override {
transferFrom(from, to, tokenId);
// 检查是否为合约地址
// 如果是合约地址, 则需要检查合约是否支持接收代币
require(
to.code.length == 0 ||
IERC721Receiver(to).onERC721Received(
msg.sender,
from,
tokenId,
data
) ==
IERC721Receiver.onERC721Received.selector,
"ERC721: transfer to non ERC721Receiver implementer"
);
}
// 查询 operator 是否被批准可以管理 owner 的所有代币
function isApprovedForAll(
address owner,
address operator
) public view returns (bool) {
return _operatorApprovals[owner][operator];
}
}
除了上述核心方法, 一般还会有 mint 和 burn 方法:
// 为 to 创建 tokenId 代币
function _mint(address to, uint tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(_owners[tokenId] == address(0), "ERC721: token already minted");
_balances[to]++;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
// 销毁 tokenId 代币
function _burn(uint tokenId) internal {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: burn of token that is not own");
_balances[owner]--;
delete _owners[tokenId];
delete _tokenApprovals[tokenId];
emit Transfer(owner, address(0), tokenId);
}
现在,我们就可以创建自己的简易 NFT 啦:
contract MyNFT is ERC721 {
function mint(address to, uint tokenId) public {
_mint(to, tokenId);
}
function burn(uint _tokenId) public {
require(
msg.sender == _owners[_tokenId],
"ERC721: burn caller is not owner nor approved"
);
_burn(_tokenId);
}
}
WETH(Wrapped Ether)是以太坊(ETH)的包装版本,它遵循 ERC20 代币标准。由于 ETH 本身并不符合 ERC20 标准,因此在某些去中心化应用(DApp)和去中心化金融(DeFi)平台上使用时会有一些限制。WETH 的出现解决了这个问题,使 ETH 可以在这些平台上无缝使用。
contract WETH {
// 代币名称、符号、小数位数
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
// 记录每个地址的 WETH 余额
mapping(address => uint) public balanceOf;
// 记录存入 ETH 的事件
event Deposit(address indexed account, uint amount);
// 记录提取 ETH 的事件
event Withdrawal(address indexed account, uint amount);
// 使合约可以接受 ETH
receive() external payable {
deposit();
}
// 接受 ETH 并将其转换为 WETH
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
// 将 WETH 转换回 ETH 并提取到调用者的地址
function withdraw(uint amount) public {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}
// 返回合约中存储的 ETH 总量
function totalSupply() public view returns (uint) {
return address(this).balance;
}
}
我们可以直接使用 OpenZeppelin 的 ERC20 合约库来实现 WETH 合约:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20 {
// 记录存入 ETH 的事件
event Deposit(address indexed account, uint amount);
// 记录提取 ETH 的事件
event Withdrawal(address indexed account, uint amount);
// 初始化 WETH 合约
constructor() ERC20("Wrapped Ether", "WETH") {}
// 使合约可以接受 ETH
receive() external payable {
deposit();
}
// 接受 ETH 并将其转换为 WETH
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
// 将 WETH 转换回 ETH 并提取到调用者的地址
function withdraw(uint amount) public {
_burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}
}
部署 WETH 合约
调用 WETH 合约的 deposit 函数,设置传入的 ETH 数量,这里以 1 ETH 为例
调用 WETH 合约继承的 balanceOf 函数,传入部署 WETH 合约的地址,查看 WETH 余额
调用 WETH 合约继承的 totalSupply 函数,查看合约中存储的 ETH 总量
调用 WETH 合约的 withdraw 函数,传入提取的 WETH 数量,提取 ETH
调用 WETH 合约继承的 balanceOf 函数,查看 WETH 余额
调用 WETH 合约继承的 totalSupply 函数,查看合约中存储的 ETH 总量