💰 第16课:ERC20代币

DeFi 阶段四 ✅ 验证通过

🎯 学习目标:深入理解ERC20标准接口与实现细节,掌握approve/transferFrom授权机制,从零编写完整的ERC20代币合约,理解代币扩展(Burnable、Pausable、Permit)。

📖 一、ERC20标准详解

ERC20是以太坊上最广泛使用的代币标准(EIP-20),由Fabian Vogelsteller于2015年提出。它定义了同质化代币(Fungible Token)的通用接口。

1.1 ERC20接口

// IERC20.sol — ERC20标准接口
interface IERC20 {
    // ═══════ 必须实现的6个函数 ═══════
    
    /// @notice 代币总供应量
    function totalSupply() external view returns (uint256);
    
    /// @notice 查询账户余额
    function balanceOf(address account) external view returns (uint256);
    
    /// @notice 转账(直接转)
    function transfer(address to, uint256 amount) external returns (bool);
    
    /// @notice 查询授权额度
    function allowance(address owner, address spender) external view returns (uint256);
    
    /// @notice 授权(允许spender花我的钱)
    function approve(address spender, uint256 amount) external returns (bool);
    
    /// @notice 授权转账(由spender代替owner转账)
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    // ═══════ 2个事件 ═══════
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

1.2 approve/transferFrom 授权机制

ERC20 授权流程(approve + transferFrom) 用户(Alice) 合约(Dex) Token合约 │ │ │ │ 1. approve(dex, │ │ │ 1000 tokens) │ │ │───────────────────────────────────────►│ │ │ allowance[Alice] │ │ │ [Dex] = 1000 │ │ │ │ │ 2. swap(500) │ │ │──────────────────►│ │ │ │ 3. transferFrom │ │ │ (Alice, Bob, │ │ │ 500) │ │ │───────────────────►│ │ │ 检查: allowance │ │ │ [Alice][Dex] │ │ │ >= 500? ✅ │ │ │ 扣减allowance │ │ │ 1000→500 │ │ │ 执行transfer │
ERC20的approve存在竞态条件:如果Alice已授权Dex 100 tokens,现在想改为50,Dex可以在approve(50)生效前先花掉100,再花掉50,总共150!解决方法:先approve(0)再approve(50)。

📖 二、从零实现ERC20

// contracts/SimpleERC20.sol — 从零实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract SimpleERC20 {
    // ═══════ 状态变量 ═══════
    string public name;
    string public symbol;
    uint8 public immutable decimals;
    uint256 public totalSupply;
    
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    
    address public owner;

    // ═══════ 事件 ═══════
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);

    // ═══════ 构造函数 ═══════
    constructor(string memory _name, string memory _symbol, uint256 _initialSupply) {
        name = _name;
        symbol = _symbol;
        decimals = 18;
        owner = msg.sender;
        
        // 铸造初始供应量给部署者
        _mint(msg.sender, _initialSupply * 10 ** decimals);
    }

    // ═══════ 核心函数 ═══════
    
    function transfer(address to, uint256 amount) external returns (bool) {
        return _transfer(msg.sender, to, amount);
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        uint256 currentAllowance = allowance[from][msg.sender];
        require(currentAllowance >= amount, "ERC20: insufficient allowance");
        
        // 无需检查amount != type(uint256).max(无限授权不扣减)
        if (currentAllowance != type(uint256).max) {
            allowance[from][msg.sender] = currentAllowance - amount;
        }
        
        return _transfer(from, to, amount);
    }

    // ═══════ 内部函数 ═══════
    
    function _transfer(address from, address to, uint256 amount) internal returns (bool) {
        require(from != address(0), "ERC20: transfer from zero");
        require(to != address(0), "ERC20: transfer to zero");
        require(balanceOf[from] >= amount, "ERC20: insufficient balance");
        
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }

    function _mint(address to, uint256 amount) internal {
        require(to != address(0), "ERC20: mint to zero");
        totalSupply += amount;
        balanceOf[to] += amount;
        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal {
        require(from != address(0), "ERC20: burn from zero");
        require(balanceOf[from] >= amount, "ERC20: burn exceeds balance");
        balanceOf[from] -= amount;
        totalSupply -= amount;
        emit Transfer(from, address(0), amount);
    }
}

📖 三、ERC20扩展

3.1 Burnable — 可销毁

// contracts/DeFiToken.sol — 完整DeFi代币
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DeFiToken is ERC20, ERC20Burnable, ERC20Pausable, Ownable {
    uint256 public maxSupply;
    
    constructor(
        string memory _name,
        string memory _symbol,
        uint256 _initialSupply,
        uint256 _maxSupply
    ) ERC20(_name, _symbol) Ownable(msg.sender) {
        maxSupply = _maxSupply * 10 ** decimals();
        _mint(msg.sender, _initialSupply * 10 ** decimals());
    }

    // 所有者铸造新代币
    function mint(address to, uint256 amount) external onlyOwner {
        require(
            totalSupply() + amount <= maxSupply,
            "Exceeds max supply"
        );
        _mint(to, amount);
    }

    // 暂停所有代币转账
    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    // 重写内部钩子函数
    function _update(address from, address to, uint256 value)
        internal
        override(ERC20, ERC20Pausable)
    {
        super._update(from, to, value);
    }
}

3.2 Permit — 免授权转账(EIP-2612)

// 传统approve需要2笔交易: approve + transferFrom
// Permit只需1笔: 链下签名 + 合约内permit+transferFrom

// EIP-2612 Permit接口
function permit(
    address owner,
    address spender,
    uint256 value,
    uint256 deadline,
    uint8 v, bytes32 r, bytes32 s
) external;

// 前端使用:一笔交易完成授权+转账
// 1. 用户在链下签名(MetaMask弹出一次)
// 2. DApp调用permit+transferFrom(一笔交易)
// → 省Gas、省时间、更好的UX!

📖 四、知名ERC20代币分析

代币类型供应量特点
USDT稳定币830亿+中心化法币抵押,Tether发行
USDC稳定币330亿+Circle+Coinbase,合规透明
UNI治理代币10亿Uniswap治理,4年线性释放
LINK效用代币10亿预言机节点质押+支付
AAVE治理+效用1600万借贷协议治理,可质押获安全模块收益
COMP治理代币1000万Compound治理,按借款量分配

📖 五、代币安全审计要点

🔒 ERC20常见漏洞:

1. 整数溢出(0.8.0前)
• Solidity 0.8.0+已内置溢出检查
• 旧版本需使用SafeMath

2. approve竞态条件
• 先approve(0)再approve(newAmount)
• 或使用increaseAllowance/decreaseAllowance

3. 通缩代币问题
• transfer时扣税导致到账金额少于预期
• DeFi合约未考虑通缩会导致记账错误

4. 权限漏洞
• mint函数未限制权限→任何人可铸造
• owner可无限mint→信任问题

🧪 练习

1. ERC20的approve/transferFrom机制解决什么问题? 2. approve的竞态条件是什么? 3. EIP-2612 Permit的优势是什么?

📖 七、ERC4626代币化金库

// ERC4626 = 代币化金库标准(Vault)
// 存入底层代币 → 获得金库份额代币 → 赚取收益
interface IERC4626 is IERC20 {
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
    function totalAssets() external view returns (uint256);
    function convertToShares(uint256 assets) external view returns (uint256);
    function convertToAssets(uint256 shares) external view returns (uint256);
    // 存入100 USDC → 获得95 vaultToken → 收益后95 vaultToken = 110 USDC
}

📖 八、常见ERC20变体

特殊ERC20实现:
USDT: transfer不返回bool(需SafeERC20)
USDC: decimals=6(不是18!)
rebasing tokens: 余额自动增减(AMPL)
fee-on-transfer: 转账收税,到账少于发出
upgradeable tokens: 代理模式可升级逻辑

⚠️ 编写通用DeFi协议时必须处理这些变体!
💰

🏆 成就解锁:代币铸造师

你已掌握ERC20代币标准!从接口规范到从零实现,从授权机制到安全审计,你拥有了创建和管理同质化代币的完整能力。

关键收获:

✅ ERC20六大函数与两大事件
✅ approve/transferFrom授权机制与竞态条件
✅ 从零实现完整ERC20代币
✅ 扩展功能:Burnable、Pausable、Permit
✅ 代币安全审计要点

📋 课程目录