在区块链世界的“经济系统”中,代币无疑是连接价值、传递权益的核心载体,而以太坊作为智能合约平台的领军者,其ERC20标准(Ethereum Request for Comments 20)定义了代币合约的统一“语法规则”,使得不同代币能在以太坊生态中无缝交互,本文将深入解析ERC20代币合约的核心原理、代码结构,并手把手教你如何编写一个属于自己的ERC20代币合约。
ERC20标准:代币的“通用语言”
ERC20是以太坊社区提出的代币接口标准,于2015年11月通过,它并非一个具体的合约代码,而是一套规范,要求所有基于以太坊的代币合约必须实现特定的函数和事件,以确保兼容性,就像USB接口让不同设备能共用充电器一样,ERC20让钱包、交易所、DApp等工具能统一处理各类代币。
ERC20的核心接口规范
ERC20标准定义了6个必须实现的函数和2个必须触发的事件,构成了代币合约的“骨架”:
必须实现的函数
-
name()
返回代币的完整名称,如“USD Coin”“ChainLink Token”。
示例返回值: "USD Coin" -
symbol()
返回代币的简称,通常2-3个字符,如"USDC""LINK"。
示例返回值: "USDC" -
decimals()
返回代币的小数位数,用于精确计算(避免浮点数误差),以太坊主币ETH的decimals为18,多数代币也遵循此惯例。
示例返回值: 18 -
totalSupply()
返回代币的总供应量,类型为uint256(无符号256位整数)。
示例返回值: 1000000000000000000000000(即100万,假设decimals为18) -
balanceOf(address account)
查询指定地址account的代币余额,返回uint256类型。
参数: account - 要查询的地址
示例返回值: 500000000000000000000(即500代币) -
transfer(address to, uint256 amount)
向指定地址to转移amount数量的代币,调用者需有足够的余额。
参数: to - 接收地址;amount - 转移数量(已乘以decimals)
返回值: bool(成功返回true,失败抛出异常) -
transferFrom(address from, address to, uint256 amount)
从地址from向地址to转移amount数量的代币,通常用于授权场景(如交易所批量提现),调用者需先被from地址授权足够的allowance。
参数: from - 转出地址;to - 接收地址;amount - 转移数量
返回值: bool -
approve(address spender, uint256 amount)
授权地址spender可调用transferFrom转移自己的代币,最大额度为amount。
参数: spender - 被授权地址;amount - 授权额度
返回值: bool -
allowance(address owner, address spender)
查询地址owner已授权给spender的代币额度。
参数: owner - 授权方地址;spender - 被授权方地址
返回值: uint256
必须触发的事件
-
Transfer(address indexed from, address indexed to, uint256 value)
在代币转移时触发(包括铸造和销毁)。from:转出地址(铸造时为0x000...000,销毁时为接收地址);to:接收地址(销毁时为0x000...000);value:转移数量。
-
Approval(address indexed owner, address indexed spender, uint256 value)
在调用approve修改授权额度时触发。owner:授权方;spender:被授权方;value:新的授权额度。
ERC20代币合约代码:从“骨架”到“血肉”
基于ERC20标准,我们可以编写一个完整的代币合约,以Solidity语言(以太坊智能合约开发语言)为例,以下是一个基础ERC20代币合约的完整代码,并附关键注释解析:
// SPDX-License-Identifier: MIT
// 指定许可证,MIT是开源合约常用许可证,允许自由使用和修改
pragma solidity ^0.8.20;
// 指定Solidity编译器版本,^0.8.20表示使用0.8.20及以上、0.9.0以下的版本
/**ERC20代币合约
* @dev 实现ERC20标准接口,包含基本代币功能:铸造、转移、授权
*/
contract MyToken is ERC20 {
address public owner; // 合约所有者地址,用于控制铸造权限
/**
* @dev 构造函数,在合约部署时执行
* @param _name 代币名称
* @param _symbol 代币简称
*/
constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
owner = msg.sender; // 将部署者地址设为所有者
_mint(owner, 1000000 * 10**decimals()); // 初始铸造100万代币给所有者(自动乘以decimals)
}
/**
* @dev 铸造新代币(仅所有者可调用)
* @param _account 接收代币的地址
* @param _amount 铸造数量(未乘以decimals)
*/
function mint(address _account, uint256 _amount) public onlyOwner {
_mint(_account, _amount * 10**decimals());
}
/**
* @dev 销毁代币(调用者需有足够余额)
* @param _amount 销毁数量(未乘以decimals)
*/
function
burn(uint256 _amount) public {
_burn(msg.sender, _amount * 10**decimals());
}
/**
* @dev 销毁指定地址的代币(仅所有者可调用,可用于销毁错误发送的代币)
* @param _account 要销毁代币的地址
* @param _amount 销毁数量(未乘以decimals)
*/
function burnFrom(address _account, uint256 _amount) public onlyOwner {
_burnFrom(_account, _amount * 10**decimals());
}
}
// 继承OpenZeppelin的ERC20接口,避免重复造轮子(推荐做法)
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
代码关键点解析
继承OpenZeppelin的ERC20合约
实际开发中,不建议从零实现ERC20,而是使用OpenZeppelin(开源智能合约库)提供的标准化ERC20合约,它已通过多次安全审计,实现完整且安全,并包含常用功能(如权限控制、事件触发等),上述代码中,contract MyToken is ERC20表示MyToken合约继承自OpenZeppelin的ERC20合约。
构造函数(constructor)
- 在合约部署时执行,仅运行一次;
- 通过
ERC20(_name, _symbol)调用父合约(ERC20)的构造函数,设置代币名称和简称; owner = msg.sender将合约部署者设为“所有者”,后续可通过onlyOwner修饰符限制敏感操作;_mint()是ERC20合约内置的铸造函数,1000000 * 10**decimals()表示铸造100万代币(decimals()默认为18,所以实际铸造数量为1000000 * 10^18,即100万乘以精度单位)。
铸造(mint)与销毁(burn)
mint:新增代币,需由所有者调用,避免无序增发导致通胀;burn:销毁代币,调用者销毁自己的代币;burnFrom:所有者销毁指定地址的代币(可用于回收错误发送的代币)。
*注:铸造和销毁时,数量需手动乘以10**decimals(),因为用户输入的“100万”是实际代币数量,而合约内部存储的是最小单位(如“100万×10^18”)。*