什么是ERC20?

就是一份技术规范(一键发币模板)。2017年定稿。从此任何钱包交易所与DApp都不再需要为每个新代币进行定制开发,直接用这个模板就行。

EIP-20(全称为 Ethereum Improvement Proposal 20),也常被称为 “ERC-20 标准”(ERC 即 Ethereum Request for Comments,是 EIP 被社区接受后的正式名称)。它定义了 fungible token(可替代代币)的统一接口规范,让不同代币在以太坊网络中能被一致地交互和管理。

内容

ERC20标准提供了namesymboldecimals等元数据接口,用于描述代币的基本信息,如名称、符号和小数位数,方便用户识别和使用。

6个函数

  1. 总供应量
1
function totalSupply() public view returns (uint256)
  1. 转账(从自己账户直接转移代币)
1
function transfer(address _to, uint256 _value) public returns (bool success)
  1. 余额查询
1
function balanceOf(address _owner) public view returns (uint256 balance)
  1. 给第三方_spender授权最大额度
1
function approve(address _spender, uint256 _value) public returns (bool success)
  1. 转移授权(第三方用已经授权的地址从_from地址划扣代币_value代币转给_to,返回成功与否)
1
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
  1. 查询剩余授权额度
1
function allowance(address _owner, address _spender) public view returns (uint256 remaining)

2个事件

  1. 转账事件
  2. 授权事件

其他

  1. mint函数:mint函数由onlyOwner调用,为指定地址增加代币,可用于初始发行或后续增发,灵活控制代币供应
  2. burn函数:burn函数允许用户销毁自有代币,减少总供应量,可用于通缩机制或用户主动销毁。
  3. ERC20Capped供应上限设计是 OpenZeppelin 提供的一个 ERC20 扩展合约(重写_mint函数),它只在铸币时增加了一条检查:“如果新铸币数量会导致总供应量超过事先设定的上限,交易回滚并抛出 ERC20ExceededCap错误。”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MarchToken is ERC20Capped, Ownable(msg.sender) {
    constructor() ERC20("March", "MARCH") ERC20Capped(100000 * 10**18) {
    }

    function mint(address to, uint256 amount) external onlyOwner {
        // 调用 ERC20Capped 的 _mint 函数,自动检查是否超过上限
        _mint(to, amount);
    }
}

Ownable合约(OpenZeppelin v4.x 及以上版本)的构造函数需要接收一个initialOwner参数(默认值为 msg.sender,但在多重继承时需显式确认)。

  1. ERC20Pausable是 OpenZeppelin 为 ERC-20 代币提供的一个「一键急停开关」扩展。在普通 ERC-20 的基础上增加了 暂停 / 恢复 机制——当合约被设为 paused 状态 时,任何转账、铸币、销毁操作都会被强制回滚,有效防止重大漏洞或黑天鹅事件造成更大损失。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MarchToken is ERC20Pausable, Ownable(msg.sender) {
    constructor() ERC20("March", "MARCH"){
        _mint(msg.sender, 100000*10**decimals());
    }

    function pause()external onlyOwner{
        _pause();
    }
    function unpause()external  onlyOwner{
        _unpause();
    }
    //子类重写显示声明
    function _update(address from, address to, uint256 value)internal override(ERC20,ERC20Pausable){
        super._update(from,to,value);
    }
}

具体执行流程:

  1. 调用super._update → 先执行 ERC20Pausable 的 _update(检查合约是否被暂停,若暂停则 revert)。
  2. 若未暂停,ERC20Pausable 的 _update 会继续通过 super 调用 ERC20 原生的 _update(处理余额变更、触发 Transfer 事件等核心逻辑)。
  3. ERC20Snapshot快照功能设计是 OpenZeppelin为ERC-20代币提供的一个扩展。在任意指定区块高度(由管理员显式触发)记录那一刻所有账户的余额和代币总供应量,后续任何时刻都可以精准地回溯查询这些数据,而不会受之后的转账、铸币或销毁操作影响。
    作用:
  • 防止“一币多用”攻击:攻击者无法在快照后通过闪电贷或快速转账,把同一份代币重复用于投票、领空投或分红。
  • 无需信任的分红 / 空投:按快照时的持仓比例发放奖励,杜绝后续刷量行为。
  • 治理投票权重:把投票权固定在某一历史时刻的持仓快照上,避免投票期间“买票”或“闪转”干扰结果。

关于decimals

区块链上的数字都是整数(无法直接存储小数),但代币通常需要支持小额交易(比如转账 0.5 个代币)。为了解决这个问题,ERC20 标准引入了decimals(小数位数)的概念。
例如:

1
2
3
4
5
6
7
8
9
10
11
    function mint(address to, uint256 amount)external onlyOwner returns(bool){
        require(to != address(0), "Error:Your to is zero");
        require(amount != 0, "Error:Your mint_amount is zero");
        uint256 scaledAmount = amount * (10 ** uint256(decimals));//在这里
        totalSupply += scaledAmount;
        balance[to] += scaledAmount;

        emit Mint(to,scaledAmount);
        emit Transfer(address(0), to, scaledAmount);
        return true;
    }

例如:
假设代币的decimals = 2(默认常用 18):

  • 用户调用mint(to, 5),表示想铸造 5 个整数代币。
  • 代码计算:scaledAmount = 5 * (10^2) = 5 * 100 = 500
  • 最终合约会存储 “500 个最小单位”,等价于 “5 个整数代币”。
    当用户查询余额时,合约返回的是最小单位数量,前端会自动除以10^decimals,展示为用户易懂的整数 + 小数形式(比如 500 → 5.00)

合约编写

OpenZeppelin 是一个开源的智能合约开发框架,专为以太坊及其他兼容 EVM 的区块链设计,提供经过安全审计、可重用、模块化的 Solidity 代码库,帮助开发者快速、安全地构建去中心化应用(DApp)和智能合约。

使用openzeppelin合约库

1
npm install @openzeppelin/contracts

在线remix已经默认下载好了。
在合约里的体现:

1
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"

编写一个最基础的ERC20合约

1
2
3
4
5
6
7
8
9
10
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MarchToken is ERC20 {
    constructor() ERC20("March", "MARCH") {
        _mint(msg.sender, 1000000 * 10 ** decimals());//为部署者铸造初始供应量
    }
}

提示:将_mint权限限制为onlyOwner,防止恶意铸造导致代币供应失控,维护代币经济模型稳定。

onlyOwner 也是OpenZeppelin提供的一个权限控制修饰符,定义在Ownable.sol合约中。如果不导入下面这个包,Solidity编译器不认识onlyOwner修饰符,会直接报错。

1
import "@openzeppelin/contracts/access/Ownable.sol";

当然,自己写一个onlyOwner的函数修饰器也行。

WETH合约实现

概述

WETH(Wrapped Ether)合约是一种将原生以太坊(ETH) 转换为符合 ERC20 标准的代币的智能合约。

为什么要转换?
原生 ETH 是以太坊网络的基础货币,但其设计并不遵循 ERC20 标准(没有transferapprove等 ERC20 标准函数)。
这导致了两个问题:1.无法直接参与ERC20生态。(许多 DeFi 协议的核心逻辑基于 ERC20 标准设计,ETH 无法直接与这些协议交互,例如无法作为交易的一方、无法被质押)2.功能限制。(ETH 没有 ERC20 的 allowance 授权机制,无法实现 “第三方代转账” 等功能,而这是许多 DeFi 操作的基础。)

WETH解决方案:通过deposit与withdraw实现ETH与WETH的双向兑换,解决了兼容性问题。

WETH9合约地址:0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2

函数接口设计

  1. deposit函数它无需参数,通过payable接收ETH,为调用者铸造等量WETH,实现把原生 ETH 1:1 封装成 ERC-20 形式的 WETH。
1
2
3
4
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
  1. withdraw函数是WETH合约的反向操作函数。withdraw函数销毁指定数量WETH并转回ETH,实现WETH到ETH的转换。这个函数传入的参数就是指销毁的WHTETH的数量(单位:wei)。
1
2
3
4
5
6
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
}

只要涉及转账一定要防止重入攻击。

WETH合约版本

目前常用的 WETH(Wrapped Ether)主要有 5 个版本(或实现),核心差异体现在 Solidity 版本、功能丰富度、Gas 效率、授权机制等方面。
WETH合约版本

注意:WETH9合约是“从零手搓”的 ERC-20 合约,它没有继承 ERC20、Ownable —— 所有状态变量和函数都在 WETH9 内显式声明。但是它依然兼容 ERC-20,它的接口与 ERC-20 完全一致,前端和 DeFi 协议可直接把它当 ERC-20 使用。