ERC20
ERC-20 全称 “Ethereum Request for Comment 20”,是一种标准接口,用于实现代币合约。ERC20 标准定义了一组函数和事件,使得代币可以在不同的应用和平台之间互操作。
ERC20 标准接口定义了一组必须实现的函数和事件:
interface IERC20 {
// 返回代币的总供应量
function totalSupply() external view returns (uint);
// 返回指定地址的代币余额
function balanceOf(address account) external view returns (uint);
// 返回授权给 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 账户转移指定数量的代币到 recipient, 需要预先通过 approve 授权
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
}
interface IERC20 {
// 在代币转移时触发, 包括零值转移
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;
// 定义两个事件, 用于记录代币转移和授权操作
event Transfer(address indexed from, address indexed to, uint value);
event Approval(
address indexed owner,
address indexed spender,
uint value
);
// 将指定数量 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 账户转移指定数量的代币到 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;
}
}
使用 OpenZeppelin 库可以简化 ERC20 代币的实现。OpenZeppelin 提供了安全且经过审计的 ERC20 实现。
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply * 10 ** uint256(decimals()));
}
}
除了上述核心方法,你可能还听过 mint
& burn
。它们通常用于 ERC20 代币合约中,以增加或减少代币的总供应量。
contract ERC20 is IERC20 {
// ...
// 创建 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);
}
}
ERC721
ERC721 是以太坊上用于创建不可替代代币(NFT)的标准。与 ERC20 不同,ERC721 代币是独一无二的,每个代币都有自己的唯一标识符。
ERC721 标准定义了一组必须实现的函数和事件,使得代币可以在不同的应用和平台之间互操作。
interface IERC721 {
// 返回某个地址拥有的代币数量
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);
}
interface IERC721 {
// 在代币转移时触发
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
// 在调用 approve 时触发
event Approval(
address indexed owner,
address indexed approved,
uint256 indexed tokenId
);
// 在调用 setApprovalForAll 时触发
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
}
WETH
WETH(Wrapped Ether)是以太坊(ETH)的包装版本,它遵循 ERC-20 代币标准。由于 ETH 本身并不符合 ERC-20 标准,因此在某些去中心化应用(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 合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
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 总量