深入浅出,以太坊ERC20代币合约代码解析与实践指南

时间: 2026-03-06 4:15 阅读数: 1人阅读

在区块链的世界里,以太坊(Ethereum)无疑是最具影响力的智能合约平台之一,而ERC20(Ethereum Request for Comments 20)标准,则是以太坊上应用最广泛、最成熟的代币标准,它像一套通用的“语法规则”,使得不同代币能够在以太坊生态中无缝交互,被钱包识别、在交易所交易、用于DeFi协议等,本文将带您深入了解以太坊ERC20代币合约的代码结构、核心功能以及如何编写一个基本的ERC20代币。

什么是ERC20标准

ERC20不是一个具体的代币,而是一套技术标准,任何遵循ERC20标准的代币合约,都必须实现一组预定义的接口(Interface)和事件(Event),这确保了所有ERC20代币都具有一致的行为方式,

  • 名称(Name):代币的完整名称,如“USD Coin”。
  • 符号(Symbol):代币的简短代码,如“USDC”。
  • 小数位数(Decimals):代币可分割的最小单位,类似于比特币的“聪”,通常为18。
  • 总供应量(Total Supply):代币的总量。
  • 余额查询(balanceOf):查询某个地址拥有多少代币。
  • 转移(transfer):向指定地址发送代币。
  • 转账授权(approve):授权某个地址可以花费你的代币。
  • 从授权地址转账(transferFrom):由被授权者从授权地址转移代币。

ERC20代币合约的核心代码结构

一个标准的ERC20代币合约通常使用Solidity语言编写,继承自ERC20接口或直接实现其所有方法,下面我们通过一个简化版的ERC20合约代码来解析其核心组成部分。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**ERC20接口
 * @dev 见 https://eips.ethereum.org/EIPS/eip-20
 */
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**简化版ERC20代币合约
 */
contract MyToken is IERC20 {
    // 1. 状态变量
    string private _name;
    string private _symbol;
    uint8 private _decimals;
    uint256 private _totalSupply;
    mapping(address => uint256) private _balances; // 地址到余额的映射
    mapping(address => mapping(address => uint256)) private _allowances; // 授权映射
    // 2. 构造函数
    constructor(string memory name_, string memory symbol_, uint8 decimals_) {
        _name = name_;
        _symbol = symbol_;
        _decimals = decimals_;
        // 初始供应量,10亿代币,18位小数,即 1,000,000,000 * 10^18
        uint256 initialSupply = 1000000000 * (10 ** uint256(decimals_));
        _totalSupply = initialSupply;
        _balances[msg.sender] = initialSupply; // 将初始供应量分配给合约部署者
        emit Transfer(address(0), msg.sender, initialSupply); // 触发Transfer事件,从零地址(创世)转移到部署者
    }
    // 3. 实现IERC20接口的方法
    /**
     * @dev 返回代币名称
     */
    function name() public view override returns (string memory) {
        return _name;
    }
    /**
     * @dev 返回代币符号
     */
    function symbol() public view override returns (string memory) {
        return _symbol;
    }
    /**
     * @dev 返回代币小数位数
     */
    function decimals() public view override returns (uint8) {
        return _decimals;
    }
    /**
     * @dev 返回代币总供应量
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }
    /**
     * @dev 返回账户的代币余额
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }
    /**
     * @dev 转账代币
     */
    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }
    /**
     * @dev 授权某个地址可以花费你的代币
     */
    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }
    /**
     * @dev 从授权地址转账代币
     */
    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, msg.sender, currentAllowance - amount);
        return true;
    }
    /**
     * @dev 返回授权额度
     */
    function allowance(address owner, address spender) public view override returns (uint256) {
        return _allowances[owner][spender];
    }
    // 4. 内部函数(Internal Functions) - 实现核心逻辑,避免代码重复
    /**
     * @dev 内部转账函数
     */
    function _transfer(address sender, address recipient, uint256 amount) internal {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(_balances[sender] >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] -= amount;
        _balances[recipient] += amount;
        emit Transfer(sender, recipient, amount);
    }
    /**
     * @dev 内部授权函数
     */
    function _approve(address owner, address spender, uint256 amount) internal {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
}

代码核心解析

  1. 接口(Interface)IERC20

    • 它定义了所有ERC20代币必须实现的方法和事件,合约通过contract MyToken is IERC20来声明遵循此接口。
  2. 状态变量(State Variables)

    • _name, _symbol, _decimals:存储代币的基本信息。
    • _totalSupply:记录代币的总供应量。
    • _balances:是一个mapping(键值对映射),用于存储每个地址对应的代币余额。
    • _allowances:是一个二维mapping,用于存储owner地址授权给spender地址的代币花费额度。
  3. 构造函数(Constructor)

    • 在合约部署时执行一次,用于初始化代币的名称、符号、小数位数和初始供应量。
    • 在这个例子中,初始供应量会被分配给合约的部署者(msg.sender),并触发一个Transfer事件,记录从“零地址”(代表创世)到部署者的代币创建过程。
  4. 核心接口方法实现

    • name(), symbol(), decimals(), totalSupply(), balanceOf():这些是查询函数,使用view修饰符,表示它们只读取状态变量而不修改状态,因此不会消耗Gas(除了在交易中调用时)。
    • transfer(recipient, amount):允许代币持有者向recipient地址转移amount数量的代币,它内部调用_transfer来完成实际逻辑。
    • approve(spender, amount):允许代币持有者授权spend
      随机配图
      er
      地址可以花费其最多amount数量的代币,它内部调用_approve
    • transferFrom(sender, recipient, amount):允许被授权的spender(即调用此方法的msg.sender)从sender地址向recipient地址转移amount数量的代币,它会先检查授权额度,然后调用_transfer,并更新授权额度。