Openzeppelin 学习:ERC20源码分析

ERC20源码解析ERC20是以太坊上的一种代币标准,定义了一组规则,任何实现它的智能合约都可以发行一个“可替代代币”(FungibleToken,FT)可替代”是一个经济学术语,意思是:每一个单位的代币都是一样的、等价可互换的比如:你有一个1USDT,我也有一个1U

ERC20 源码解析

ERC20 是以太坊上的一种代币标准,定义了一组规则,任何实现它的智能合约都可以发行一个“可替代代币”(Fungible Token, FT

可替代”是一个经济学术语,意思是: 每一个单位的代币都是一样的、等价可互换的

比如:

  • 你有一个 1 USDT,我也有一个 1 USDT,它们没有任何区别,随时可以交换
  • ERC20 代币的余额是一个整数,就像银行账户一样:你可以持有 100 个单位

🔍 核心区别总结:FT vs NFT

特性 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_;
 }
  • 只在部署合约时调用,用来初始化代币的名称和符号。

MetaData 获取

 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(模拟以太单位)。
    • 所有 Metadata 接口均为 view 且可 virtual 以供 override

标准接口函数实现

1. totalSupply()balanceOf()

function totalSupply() public view virtual returns (uint256) {
    return _totalSupply;
}
function balanceOf(address account) public view virtual returns (uint256) {
    return _balances[account];
}
  • 分别返回全局供应量和单一地址余额。

2. transfer()

  • transfer -> _transfer -> _update
    
    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() 读取授权额度。

4. transferFrom()

function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
    address spender = _msgSender();
    _spendAllowance(from, spender, value);
    _transfer(from, to, value);
}
  • fromto 发送 value 代币,调用 _spendAllowance()_transfer()

核心内部函数

1. _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() 进行实际操作。

2. _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);
    }
  • 设计亮点:该函数统一处理三种操作:

    • 正常转账:from ≠ 0 且 to ≠ 0
    • 铸造(mint):from == 0
    • 销毁(burn):to == 0
  • 所有余额和总量变化都统一经由 _update() 完成,有助于自定义逻辑拓展。

3. _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。

授权操作函数

1. _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 事件的开关。

2. _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 的关键一环,用于消耗 spenderowner 的授权额度。
  • 如果授权额度是最大值 type(uint256).max,说明是“无限授权”,就不会减少额度(节省 gas)。
  • 否则,进行额度减少处理。
  • 使用 unchecked 可以省 gas,因为已经做过越界检查。

错误处理(EIP-6093)

合约中大量使用:

revert ERC20InvalidSender(address(0));
revert ERC20InsufficientBalance(from, balance, required);

这些错误类型定义于 IERC20Errors,相比使用 require(...)string 更节省 gas。

ERC20 Demo

// 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);
    }
}

ERC20 扩展模块

OpenZeppelin v5 中围绕 ERC20 提供了 多个扩展模块(extensions),快速构建功能强大的代币系统,适配各种业务场景,比如可销毁、可暂停、快照、投票、许可等

模块 说明 常用场景
ERC20Burnable 添加了 burnburnFrom 功能,可销毁自己的代币或允许销毁被批准的代币 通缩模型、销毁代币机制
ERC20Capped 设置最大总供应量,mint 超过 cap 将报错 限量代币、预挖、ICO 总量限制
ERC20Pausable 添加 pause() / unpause(),可暂停转账、授权操作 应急响应、合规控制
ERC20Permit 支持 EIP-2612 permit() 签名授权转账,无需先调用 approve() 提升用户体验、Gasless 交易
ERC20Votes 引入治理功能,通过代币权重进行投票 DAO 治理、链上投票
ERC20FlashMint 支持无抵押的闪电贷(必须在同一交易内归还) 闪电贷、DeFi 套利、流动性操作

🛠️ 示例组合使用(实际场景)

  1. DAO 代币:

    • ERC20Votes + ERC20Permit + ERC20Snapshot → 投票、Gasless 授权、治理快照
  2. 预挖代币:

    • ERC20Capped + ERC20Mintable(自定义) → 限制供应、按需增发
  3. 应急控制:

    • ERC20Pausable → 突发事件可暂停转账,例如攻击检测时冻结功能
  4. 可销毁代币:

    • ERC20Burnable → 用户主动销毁代币或通过机制回收销毁
  5. DeFi 支持代币:

    • ERC20Permit + ERC20FlashMint → 支持 Gasless 授权 + 闪电贷策略
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Henry Wei
Henry Wei
Web3 探索者