深入浅出,以太坊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);
}
}
代码核心解析
-
接口(Interface)
IERC20:- 它定义了所有ERC20代币必须实现的方法和事件,合约通过
contract MyToken is IERC20来声明遵循此接口。
- 它定义了所有ERC20代币必须实现的方法和事件,合约通过
-
状态变量(State Variables):
_name,_symbol,_decimals:存储代币的基本信息。_totalSupply:记录代币的总供应量。_balances:是一个mapping(键值对映射),用于存储每个地址对应的代币余额。_allowances:是一个二维mapping,用于存储owner地址授权给spender地址的代币花费额度。
-
构造函数(Constructor):
- 在合约部署时执行一次,用于初始化代币的名称、符号、小数位数和初始供应量。
- 在这个例子中,初始供应量会被分配给合约的部署者(
msg.sender),并触发一个Transfer事件,记录从“零地址”(代表创世)到部署者的代币创建过程。
-
核心接口方法实现:
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,并更新授权额度。