在区块链的世界里,特别是以太坊生态系统中,ERC20代币标准无疑是最具影响力和广泛应用的协议之一,它定义了一套统一的接口(Interface),使得各种代币能够在以太坊网络上互操作,就像比特币或以太坊本身一样可以被轻松交易、转移和集成到各种去中心化应用(DApps)和交易所中,本文将带你一步步了解如何编写一个基本的以太坊ERC20代币合约。
什么是ERC20
ERC20是“Ethereum Request for Comments 20”的缩写,即以太坊社区提出的第20号改进提案,它不是一个具体的合约,而是一套标准,规定了以太坊上代币合约必须实现的一组方法和事件,主要包括:
- 方法(Functions):
name(): 返回代币名称,"MyToken"。symbol(): 返回代币符号,"MTK"。decimals(): 返回代币小数位数,用于表示代币的最小单位。totalSupply(): 返回代币的总供应量。balanceOf(address): 查询指定地址的代币余额。transfer(address, uint256): 向指定地址转移指定数量的代币。transferFrom(address, address, uint256): 从一个地址转移到另一个地址(通常需要授权)。approve(address, uint256): 授权另一个地址可以调用你的transferFrom方法,最多可转移数量。allowance(address, address): 查询一个地址对另一个地址的授权额度。
- 事件(Events):
Transfer(address indexed from, address indexed to, uint256 value): 当代币转移时触发。Approval(address indexed owner, address indexed spender, uint256 value): 当授权额度设置或修改时触发。
遵循这些标准,确保了代币的兼容性和易用性。
编写一个简单的ERC20代币合约
我们将使用Solidity语言,这是以太坊智能合约的主要编程语言,在开始之前,请确保你已经安装了必要的开发环境,如Solidity编译器(solc)和Truffle或Hardhat等开发框架,或者使用在线的Solidity IDE(如Remix IDE)。
下面是一个基本的ERC20代币合约示例,我们将逐步解释其组成部分。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
_mint(msg.sender, 1000000 * 10**decimals()); // 初始发行100万个代币,考虑小数位数
}
}
如果你希望完全手动实现ERC20接口(而不是继承OpenZeppelin的实现,虽然在实际生产中推荐使用OpenZeppelin的安全审计代码),可以这样写:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.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);
}
contract MyToken is IERC20 {
string private _name;
string private _symbol;
uint8 private _decimals;
uint256 private _totalSupply;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
_decimals = 18; // 默认小数位数为18,与以太坊一致
_totalSupply = 1000000 * (10**uint256(_decimals)); // 初始供应量
_balances[msg.sender] = _totalSupply; // 将初始供应量全部转给合约部署者
emit Transfer(address(0), msg.sender, _totalSupply); // 触发Transfer事件,0地址表示代币的创世
}
function name() public view override returns (string memory) {
return _name;
}
function symbol() public view override returns (string memory) {
return _symbol;
}
function decimals() public view override returns (uint8) {
return _decimals;
}
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public override returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public override returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public
override returns (bool) {
uint256 currentAllowance = _allowances[sender][msg.sender];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
_approve(sender, msg.sender, currentAllowance - amount);
_transfer(sender, recipient, amount);
return true;
}
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);
}
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);
}
function mint(address to, uint256 amount) internal {
require(to != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[to] += amount;
emit Transfer(address(0), to, amount);
}
}
合约代码解析(以手动实现为例)
-
SPDX-License-Identifier 和 pragma solidity:
SPDX-License-Identifier: MIT: 指定了合约的许可证类型,MIT是一种宽松的开源许可证。pragma solidity ^0.8.20;: 指定编译器版本,^0.8.20表示使用0.8.20到0.9.0(不含0.9.0)之间的编译器版本。
-
接口定义 (IERC20):
- 我们定义了
IERC20接口,包含ERC20标准要求的所有方法和事件,这确保了我们的合约将实现这些接口。
- 我们定义了
-
合约状态变量:
_name,_symbol,_decimals: 存储代币的名称、符号和小数位数。_totalSupply: 存储代币的总供应量。_balances: 一个mapping,存储每个地址的代币余额。_allowances: 一个嵌套的mapping,存储一个地址授权给另一个地址的代币数量。
-
构造函数 (constructor):
- 在合约部署时调用一次,用于初始化代币的基本信息。
_name = name_;和_symbol = symbol_;设置代币名称和符号。_decimals = 18;设置小数位数,这是ERC20最常见的设置。_totalSupply = 1000000 * (10**uint256(_decimals));计算初始总供应量,乘以10**decimals是为了将整数转换为最小单位(如果小数位是18,1个代币实际上表示1 * 10^18个最小单位)。_balances[msg.sender] = _totalSupply;将所有初始代币分配给合约的部署者(msg.sender)。emit Transfer(address(0), msg.sender, _totalSupply);触发Transfer事件,记录代