ERC20
EIP(Ethereum Improvement Proposals)
ERC(Ethereum Request For Comments)
ERC20
ERC20 是以太坊上最早、最广泛使用的代币标准,由以太坊社区提出的 Ethereum Request for Comments #20 定义。它规定了一套智能合约接口,使得在以太坊网络上创建的代币具有统一的交互方式,从而能够被钱包、交易所、DApp 普遍支持。
- 定义一套最小功能接口(即合约必须实现的函数和事件),确保不同代币之间的交互方式一致。
- 让任何第三方只要遵守这套接口,就能无差别地与代币交互(如转账、授权、余额查询)。
ERC20合约标准
总结如下:
扩展要求
非强制但建议实现。
- name() → 代币名称(如 "USD Coin")
- symbol() → 代币符号(如 "USDC")
- decimals() → 小数位数(如 18)
必须要求
- 函数(Methods)
| 函数签名 | 返回值 | 作用 |
|---|---|---|
totalSupply() view returns (uint256) | 总发行量 | 返回代币总供应量 |
balanceOf(address _owner) view returns (uint256) | 余额 | 查询某个地址的代币余额 |
transfer(address _to, uint256 _value) returns (bool) | 成功与否 | 从调用者账户向指定地址转账 |
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) | 成功与否 | 从授权的账户转账(需要先 approve) |
function approve(address _spender, uint256 _value) public returns (bool success) | 成功与否 | 授权某个地址可以支配自己账户中最多 _value 的代币 |
function allowance(address _owner, address _spender) public view returns (uint256 remaining) | 授权额度 | 查询 _spender 还能使用 _owner 的多少代币 |
- 事件(Events)
ERC20 必须触发以下事件,以便链外应用监听:
| 事件 | 参数 | 用途 |
|---|---|---|
event Transfer(address indexed _from, address indexed _to, uint256 _value) | _from, _to, _value | 记录代币转账 |
event Approval(address indexed _owner, address indexed _spender, uint256 _value) | _owner, _spender, _value | 记录授权操作 |
意义
ERC20 的意义:
- 统一接口 → 钱包、交易所可以无缝支持任何
ERC20代币。 - 简化开发 → 开发者可以快速创建新代币,而不必重新定义交互逻辑。
- 生态基础 → 稳定币(
USDT、USDC、DAI)、DeFi 项目代币(UNI、AAVE)、治理代币等,几乎全部是ERC20。
Create ERC20 Token
Manually
一个简单的 ERC20 Token 合约代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract TokenERC20 {
string private s_name;
string private s_symbol;
uint8 private constant DECIMALS = 18;
uint256 private immutable i_totalSupply;
// mappings
mapping(address => uint256) private s_balances; // address => amount
mapping(address => mapping(address => uint256)) private s_allowances; // owner => (spender => amount)
// evetns
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
) {
s_name = _name;
s_symbol = _symbol;
i_totalSupply = initialSupply * (10 ** uint256(DECIMALS));
s_balances[msg.sender] = i_totalSupply;
emit Transfer(address(0), msg.sender, i_totalSupply); // address(0) means token creation
}
// optional methods
function name() public view returns (string memory) {
return s_name;
}
function symbol() public view returns (string memory) {
return s_symbol;
}
function decimals() public pure returns (uint8) {
return DECIMALS;
}
function totalSupply() public view returns (uint256) {
return i_totalSupply;
}
function balanceOf(address _owner) public view returns (uint256 balance) {
require(
_owner != address(0),
"ERC20: balance query for the zero address"
);
// mapping 是 Solidity 的哈希表,任何 key 都存在, 未赋值的 key 对应的 value 默认是 0
require(s_balances[_owner] > 0, "ERC20: has zero balance"); // every key in mapping has a default value of 0
return s_balances[_owner];
}
function transfer(
address _to,
uint256 _value
) public returns (bool success) {
require(_to != address(0), "ERC20: transfer to the zero address");
require(
s_balances[msg.sender] >= _value,
"ERC20: transfer amount exceeds balance"
);
s_balances[msg.sender] -= _value;
s_balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(
address _from,
address _to,
uint256 _value
) public returns (bool success) {
require(_from != address(0), "ERC20: transfer from the zero address");
require(_to != address(0), "ERC20: transfer to the zero address");
require(
s_balances[_from] >= _value,
"ERC20: transfer amount exceeds balance"
);
require(
s_allowances[_from][msg.sender] >= _value,
"ERC20: transfer amount exceeds allowance"
);
s_balances[_from] -= _value;
s_balances[_to] += _value;
s_allowances[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
function approve(
address _spender,
uint256 _value
) public returns (bool success) {
require(_spender != address(0), "ERC20: approve to the zero address");
require(
s_balances[msg.sender] >= _value,
"ERC20: approve amount exceeds balance"
);
s_allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(
address _owner,
address _spender
) public view returns (uint256 remaining) {
require(
_owner != address(0),
"ERC20: allowance query for the zero address"
);
require(
_spender != address(0),
"ERC20: allowance query for the zero address"
);
return s_allowances[_owner][_spender];
}
}
OpenZeppelin
OpenZeppelin 提供的 ERC20 实现就是业界公认的专业、安全的代币模板,生产级项目建议直接引用。
引入 OpenZeppelin 库:
yarn add --dev @openzeppelin/contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract OurToken is ERC20 {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply * (10 ** uint256(decimals())));
}
}
OpenZeppelin 还提供了进阶模板:
ERC20Burnable→ 支持销毁代币ERC20Capped→ 限制最大供应量ERC20Mintable/AccessControl→ 权限控制,可增发ERC20Snapshot→ 快照余额,常用于治理或分红
deploy
部署脚本:
// deploy.js
const { network } = require("hardhat")
const INITIAL_SUPPLY = "10000"
const TOKEN_NAME = "FilToken"
const TOKEN_SYMBOL = "FCC"
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy, log } = deployments
const { deployer } = await getNamedAccounts()
const ourToken = await deploy("OurToken", {
from: deployer,
args: [TOKEN_NAME, TOKEN_SYMBOL, INITIAL_SUPPLY],
log: true,
// we need to wait if on a live network so we can verify properly
waitConfirmations: network.config.blockConfirmations || 1,
})
log(`ourToken deployed at ${ourToken.address}`)
}
module.exports.tags = ["all", "token"]