12.ETH-美链

ERC-20

ERC-20 本身不是一个代币,也不是一段代码,而是一套“技术标准”或“接口规范”。它为所有基于以太坊的“同质化代币”(Fungible Tokens)定义了一套通用的、必须遵守的规则。“同质化”意味着每个代币之间都是完全相同、可以互换的,就像你钱包里的一元硬币和我钱包里的一元硬币一样,没有区别。

“ERC”是“Ethereum Request for Comments”的缩写,意为“以太坊意见征求稿”,而“20”是这份提案的编号。该标准规定,任何希望被视为ERC-20代币的智能合约,必须实现以下一套函数和事件(Events)。

核心函数与事件接口

函数/事件 描述 作用与解释
name() (可选) 返回代币的名称,如 "Tether USD"。 方便用户界面展示。
symbol() (可选) 返回代币的符号,如 "USDT"。 方便用户界面展示。
decimals() (可选) 返回代币支持的小数位数。 8意味着1,00000000个最小单位等于1个代币。USDT是6,ETH是18。
totalSupply() (必须) 返回代币的总供应量。 提供了代币总量的透明度。
balanceOf(address _owner) (必须) 返回指定地址的代币余额。 查询任何人的持币数量。
transfer(address _to, uint256 _value) (必须) 从消息发送者(msg.sender)的账户向 _to 地址发送 _value 数量的代币。 这是最基础的点对点转账功能。
approve(address _spender, uint256 _value) (必须) 授权 _spender 地址可以从你的账户中提取不超过 _value 数量的代币。 这是实现合约交互的关键。你授权一个DEX(去中心化交易所)可以动用你100个USDT。
allowance(address _owner, address _spender) (必须) 查询 _spender 地址仍然被授权可以从 _owner 地址提取的代币数量。 查询授权余额。
transferFrom(address _from, address _to, uint256 _value) (必须)_spender 地址调用,从 _from 地址向 _to 地址转移 _value 数量的代币。 approve 之后,被授权的DEX调用此函数,来实际执行你授权给它的那笔转账。
Transfer(address indexed _from, address indexed _to, uint256 _value) (必须事件) 在代币被转移时必须触发的事件。 方便链下应用(如钱包、浏览器)追踪代币流转历史。
Approval(address indexed _owner, address indexed _spender, uint256 _value) (必须事件)approve 函数被成功调用时必须触发的事件。 方便追踪授权记录。

approve + transferFrom 的工作流程

这是理解ERC-20与DApp交互的核心。你不能直接“发送”代币给一个智能合约,因为合约无法知道这笔钱是干嘛的。正确的流程是“授权”:

  1. 你 (用户) 想在Uniswap(一个DEX合约)上用100个USDT兑换ETH。
  2. 你首先需要调用USDT合约的 approve() 函数,授权Uniswap的合约地址可以动用你账户里最多100个USDT。
  3. 然后,你再调用Uniswap的 swap() 函数。
  4. Uniswap的 swap() 函数在执行时,会它自己去调用USDT合约的 transferFrom() 函数,把经过你授权的100个USDT从你的地址转移到它自己的地址,然后完成后续的兑换操作。

ERC-20标准的重要性

  1. 互操作性 (Interoperability) 这是最大的优点。因为所有ERC-20代币都遵循同一套规则,任何钱包(MetaMask, Trust Wallet)、交易所(Uniswap, Curve)、借贷协议(Aave, Compound)都可以无需定制开发,直接支持任何一个新的ERC-20代币。这创造了一个无需许可、可自由组合的“金融乐高”世界。
  2. 降低开发成本与风险 开发者无需为每个新代币重新设计底层逻辑。他们可以使用经过千锤百炼、由社区审计过的标准模板(例如 OpenZeppelin 的ERC-20实现)。这极大地减少了犯错的可能性。像本节下文讨论的美链(BEC)事件中的整数溢出漏洞,在使用标准的、安全的模板下是不会发生的。
  3. 催生生态繁荣 正是因为ERC-20的标准化,才使得2017年的ICO(首次代币发行)热潮成为可能,并为后来的DeFi(去中心化金融)和GameFi的爆发奠定了基础。它为以太坊上的价值表示和流转提供了通用语言。

现实中的例子:我们熟知的稳定币 USDTUSDC,去中心化交易所代币 UNI,以及各种Meme币如 SHIB 等,绝大多数都是以太坊上的ERC-20代币。

美链(Beauty Chain, BEC)

事件概述

  • 时间:2018年4月22日
  • 主角:一个名为“美链(BEC)”的ERC-20代币。
  • 事件:一名或多名攻击者利用了BEC智能合约中的一个**整数溢出(Integer Overflow)**漏洞,成功地调用了一个函数,从无到有地“凭空创造”了天量(具体是 2^255 * 2,一个近乎无穷大的数字)的BEC代币,并将其转入自己的两个地址。
  • 后果
    1. 当这笔异常巨大的转账记录出现在区块链上时,立刻被市场上的监控工具捕捉到。
    2. 市场迅速反应,意识到BEC代币的总量已经失控,其价值基础不复存在。
    3. 各大交易所(如OKEx)紧急暂停了BEC的充值和交易。
    4. 在短短几个小时内,BEC代币的价格暴跌超过99.5%,几乎归零。一个市值一度高达数亿美元的项目,瞬间灰飞烟灭。

这次攻击来自于合约中一个名为 batchTransfer 的批量转账函数里,一行极其简单的乘法代码。

// 这是BEC合约中存在漏洞的批量转账函数
function batchTransfer(address[] _receivers, uint256 _value) public returns (bool) {
    // 1. 获取要转账的地址数量
    uint cnt = _receivers.length;
    
    // 2. 检查:要求接收者数量在1到20之间
    require(cnt > 0 && cnt <= 20);
    
    // 3. 计算总转账金额
    // !!! 致命的漏洞就在下面这一行 !!!
    uint256 amount = uint256(cnt) * _value;

    // 4. 检查:要求调用者的余额必须大于或等于总转账金额
    require(_value > 0 && balances[msg.sender] >= amount);

    // 5. 从调用者账户中减去总额
    balances[msg.sender] -= amount;
    
    // 6. 循环给每个接收者转入_value数量的代币
    for (uint i = 0; i < cnt; i++) {
        balances[_receivers[i]] += _value;
        Transfer(msg.sender, _receivers[i], _value);
    }
    
    return true;
}

攻击者的目标是绕过第4步的余额检查 balances[msg.sender] >= amount。他需要让 amount 这个值变得非常小(最好是0),但同时,在第6步的循环中,他又希望转给自己的 _value 是一个巨大的数字。

利用整数溢出,他完美地做到了这一点:

  1. 构造参数:攻击者调用 batchTransfer 函数,并传入两个精心构造的参数:
    • _receivers: 一个包含两个他自己控制的地址的数组。因此 cnt = 2
    • _value: 一个极其巨大的数字,例如 2^255 (大约是 uint256 最大值的一半)。
  2. 触发溢出:在第3步计算总金额时,合约执行 amount = 2 * (2^255)
    • 在数学上,结果是 2256。
    • 但在 uint256 类型中,这个数字超过了它的最大表示范围,于是发生了溢出amount 的值“翻转”变为了 0
  3. 绕过检查:在第4步进行余额检查时,require(balances[msg.sender] >= 0) 这个条件永远为真。攻击者几乎不需要任何BEC余额就能通过检查。
  4. 凭空印钞
    • 在第5步,合约从攻击者余额中减去 amount(也就是减0),攻击者的余额不变。
    • 在第6步,循环执行两次。每一次,都将那个巨大的 _value(即 2^255)转入攻击者的地址。
    • 最终,攻击者成功地给自己控制的两个地址,每个地址转入了 2^255 个BEC代币,而他自己的余额几乎没有减少。

后果

美链事件是所有智能合约开发者的警钟,它带来了深刻的教训:

  1. 安全审计的绝对必要性:这是一个非常基础的漏洞,任何一个合格的智能合约审计师都能轻易发现。这表明,项目在上线前进行专业的第三方安全审计是不可或缺的。
  2. 安全数学库的重要性:在此事件之后,社区更加重视使用经过安全审计的数学库,例如当时广泛使用的 OpenZeppelin 的 SafeMath 库。这个库会重写加减乘除运算,在发生溢出时直接抛出错误(revert),而不是允许其“翻转归零”。
  3. 语言设计的演进:这个漏洞是推动Solidity语言自身进化的重要动力之一。为了从根本上解决这个问题,自Solidity 0.8.0版本起,语言已经默认内置了对整数溢出和下溢的检查。现在,除非开发者使用一个特殊的 unchecked { ... } 块,否则任何会导致溢出的算术运算都会直接 revert