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 授权 + 闪电贷策略如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!