ERC20源码解析ERC20是以太坊上的一种代币标准,定义了一组规则,任何实现它的智能合约都可以发行一个“可替代代币”(FungibleToken,FT)可替代”是一个经济学术语,意思是:每一个单位的代币都是一样的、等价可互换的比如:你有一个1USDT,我也有一个1U
ERC20 是以太坊上的一种代币标准,定义了一组规则,任何实现它的智能合约都可以发行一个“可替代代币”(Fungible Token, FT)
可替代”是一个经济学术语,意思是: 每一个单位的代币都是一样的、等价可互换的
比如:
100 个单位| 特性 | FT(ERC20) | NFT(ERC721/ERC1155) |
|---|---|---|
| 替代性 | 可替代(同质化) | 不可替代(非同质化) |
| 单位表示 | 数字余额(如 100) | 唯一 ID(如 tokenId = 1, 2, 3) |
| 使用场景 | 金融资产、积分、投票权等 | 数字收藏品、门票、产权等 |
| 常见标准 | ERC20 | ERC721 / ERC1155 |
| 是否可以批量 | 可以转账任意数量 | 通常一次转一个(ERC721),ERC1155 可批量 |
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
继承的核心模块:
IERC20: ERC20 标准接口定义(包括 transfer, approve 等)。IERC20Metadata: 补充接口,定义如 name(), symbol(), decimals()。Context: 提供 _msgSender() 方法,方便 meta-transaction 支持。IERC20Errors: 标准错误定义(ERC-6093 草案)用于 revert 时的错误格式(比如 ERC20InvalidSender 等)。abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
abstract 表示这是一个抽象合约,不能直接部署。 mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
_balances: 每个地址的代币余额。_allowances: 授权机制(某地址允许另一个地址最多能转多少 token)。_totalSupply: 总供应量。_name, _symbol: 代币的名称与符号constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
只在部署合约时调用,用来初始化代币的名称和符号。 function name() public view virtual returns (string memory) {
return _name;
}
function symbol() public view virtual returns (string memory) {
return _symbol;
}
function decimals() public view virtual returns (uint8) {
return 18;
}
decimals() 默认返回 18(模拟以太单位)。
view 且可 virtual 以供 overridetotalSupply() 和 balanceOf()function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
transfer()
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
* 将当前调用者的余额转账至 `to`。
* 实际调用 `_transfer(owner, to, value)` 实现转账逻辑。
#### 3. `approve()` 与 `allowance()`
* approve -> _approve -> _approve重载
```solidity
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
approve() 设置授权额度(包含 uint256.max 的无限授权逻辑)。allowance() 读取授权额度。transferFrom()function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
}
from 向 to 发送 value 代币,调用 _spendAllowance() 和 _transfer()_transfer()function _transfer(address from, address to, uint256 value) internal {
// from 不能为零地址
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
// to 不能为零地址
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
_update() 进行实际操作。_update()function _update(address from, address to, uint256 value) internal virtual {
//场景:token铸造
if (from == address(0)) {
_totalSupply += value;
} else {
// 场景:正常转账- from账户减少token
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
_balances[from] = fromBalance - value;
}
}
//场景:token 销毁
if (to == address(0)) {
unchecked {
_totalSupply -= value;
}
} else {
// 场景:正常转账- to账户增加token
unchecked
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
设计亮点:该函数统一处理三种操作:
所有余额和总量变化都统一经由 _update() 完成,有助于自定义逻辑拓展。
_mint() 和 _burn()function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
_update() 的封装,分别传入 from/to 为 zero address。_approve()(两个版本)function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
// 增加:事件触发逻辑
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
Approval 事件的开关。_spendAllowance()function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender); // 当前授权额度
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
transferFrom 的关键一环,用于消耗 spender 对 owner 的授权额度。type(uint256).max,说明是“无限授权”,就不会减少额度(节省 gas)。unchecked 可以省 gas,因为已经做过越界检查。合约中大量使用:
revert ERC20InvalidSender(address(0));
revert ERC20InsufficientBalance(from, balance, required);
这些错误类型定义于 IERC20Errors,相比使用 require(...) 加 string 更节省 gas。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyCustomToken is ERC20, Ownable {
constructor(
string memory name_,
string memory symbol_,
uint256 initialSupply
) ERC20(name_, symbol_) Ownable(msg.sender) {
// 初始发行全部给合约部署者
_mint(msg.sender, initialSupply);
}
/// @notice 增发代币,只允许合约拥有者调用
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
/// @notice 销毁自己账户的代币
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
}
OpenZeppelin v5 中围绕 ERC20 提供了 多个扩展模块(extensions),快速构建功能强大的代币系统,适配各种业务场景,比如可销毁、可暂停、快照、投票、许可等
| 模块 | 说明 | 常用场景 |
|---|---|---|
ERC20Burnable |
添加了 burn 和 burnFrom 功能,可销毁自己的代币或允许销毁被批准的代币 |
通缩模型、销毁代币机制 |
ERC20Capped |
设置最大总供应量,mint 超过 cap 将报错 |
限量代币、预挖、ICO 总量限制 |
ERC20Pausable |
添加 pause() / unpause(),可暂停转账、授权操作 |
应急响应、合规控制 |
ERC20Permit |
支持 EIP-2612 permit() 签名授权转账,无需先调用 approve() |
提升用户体验、Gasless 交易 |
ERC20Votes |
引入治理功能,通过代币权重进行投票 | DAO 治理、链上投票 |
ERC20FlashMint |
支持无抵押的闪电贷(必须在同一交易内归还) | 闪电贷、DeFi 套利、流动性操作 |
DAO 代币:
ERC20Votes + ERC20Permit + ERC20Snapshot → 投票、Gasless 授权、治理快照预挖代币:
ERC20Capped + ERC20Mintable(自定义) → 限制供应、按需增发应急控制:
ERC20Pausable → 突发事件可暂停转账,例如攻击检测时冻结功能可销毁代币:
ERC20Burnable → 用户主动销毁代币或通过机制回收销毁DeFi 支持代币:
ERC20Permit + ERC20FlashMint → 支持 Gasless 授权 + 闪电贷策略如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!