ERC20
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
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 方法:
![](/qrcode.jpg)
// 为 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
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 总量