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 总量