专门用于批量空投的 ETH 智能合约

目录 区块链

2018 年 4 月份,美链 BeautyChain (BEC) 爆出了智能合约漏洞,导致市值 60 亿的代币价格几乎归零,这在币圈里也算是一场大事件了。

那么漏洞主要出在什么地方呢?主要是 BEC 在标准的 ERC20 接口之外,自己添加了一个 batchTransfer 接口。一般而言,添加这种接口主要是为了便于空投时批量转账,可是 BEC 在这个接口设计上犯了两个错误:

  1. 没有使用安全的数值计算,存在数值溢出漏洞。这也是在网上被广泛传播的漏洞分析。
  2. 没有限定 batchTransfer 的使用范围。如果它限制了 batchTransfer 只能合约拥有者调用,即使存在漏洞也不能被人利用了。

BEC 为了便于空投增加的这个接口可谓代价惨重,但如果他们知道还有不需要修改代币主合约就能批量转账的办法,不知道会不会吐一口老血?

批量转账,指的是在一笔 ETH 交易中转多笔代币到不同的账户,一般用于 ERC20 代币项目启动时对用户进行空投(有人叫糖果发放)。

批量空投的好处主要有两个,一是省 GAS 费,但事实上省得不多;二是省时间,这是最主要目的。以太坊是以交易为粒度打包,如果转账只能单对单,即使一次发起多笔单对单的交易,等待这些交易被打包的时间也非常漫长,而且还有笔数上限限制。将多笔转账放到同一个交易中,被打包确认的速度就会非常快。一般 ERC20 代币项目启动时都会大撒币,空投地址动辄都是几万几十万,批量空投接口对效率会有上百倍的提升。

空投合约基本原理:ERC20 可以通过 approve 和 transferFrom 两个接口授权其它地址一定的额度。那既然是这样,我们也可以授权一个合约地址来花自己的代币,如果这个合约支持批量转账,那么就可以通过这个合约来实现批量空投了。

下面是具体实操流程:

假设已经存在一个 ERC20 代币的合约,合约地址为“TOKEN_ADDR”,而你的钱包里已经有了 100 万 TOKEN,你的钱包地址是“WALLET_ADDR”。

STEP1: 用自己的钱包部署支持批量转账的空投合约,假设创建成功后地址为“AD_ADDR”。下面给出最关键的部分,完整合约参考 github 链接 。这里 transferFrom 取的是 msg.sender,理论上来讲不加 onlyOwner 限定这个合约也可以给其它人使用,但为了安全还是加上较为稳妥。

contract Airdropper is Ownable {
    function multisend(address _tokenAddr, address[] dests,
                       uint256[] values) public onlyOwner returns (uint256) {
        uint256 i = 0;
        while (i < dests.length) {
           ERC20(_tokenAddr).transferFrom(msg.sender, dests[i], values[i]);
           i += 1;
        }
        return(i);
    }
}

STEP2: 用自己的钱包授权空投合约地址 AD_ADDR 100 万 TOKEN 额度。即通过代码或者 remix 执行 approve(AD_ADDR, 1000000*精度),注意这是 ERC20 合约里的接口,需要将交易发往 TOKEN_ADDR。

STEP3: 检查 AD_ADDR 是否得到了 100 万 TOKEN 授权。通过代码或者 remix 执行 allowance(WALLET_ADDR, AD_ADDR),如果结果是 100 万 TOKEN,说明空投合约已经得到你的 100万额度授权。

STEP4: 用自己的钱包调用空投合约的 multisend 接口发起批量空投。通过代码或者 remix 执行 multisend(TOKEN_ADDR, [addr1, addr2, ...], [value1, value2, ...]),执行成功即能实现批量转账。这是空投合约里的接口,需要将交易发往 AD_ADDR。

这里最容易混淆的是几个地址,TOKEN_ADDR/WALLET_ADDR/AD_ADDR,在每一步操作中要想明白调用的是哪个合约的接口,参数应该填哪个地址。 第一步只需要操作一次,第四步可以操作很多次,第二步可根据需求随时调整授权额度。

有了这个空投合约,你就可以将自己钱包里任意类型代币都通过批量转账方式空投出去。额度可控,任意账户均可用,还避免了在代币主合约里额外增加非 ERC20 标准接口带来的风险。

为什么两笔 Token 转账消耗 GAS 不同

目录 区块链

大家都知道,以太坊 Token 转账的过程就是智能合约执行的过程,所以每笔交易都会根据智能合约的执行情况消耗一定数量的 GAS 作为交易手续费,同时也限制了交易对资源的滥用。

大家都能接受交易有手续费的概念,因为银行转账,或者支付宝微信转账,也都有可能产生手续费。大部分情况下,手续费是按照金额一定比例收取的。但会出乎很多人意料的是,在以太坊上即使往同一个地址里转账同一种 Token,消耗的 GAS 也有可能不同。

随便在 etherscan.io 上找一个近期有多笔 Token 交易的地址(其实找了好一会儿),比如这个 : https://etherscan.io/address/0x5debb351b536eb1a61be12810abe614485167f46#tokentxns

可以看到近期正好有两笔 Rating Token 转入到这个账户,对比下 GAS 的消耗:

可以看到两笔转账消耗的 GAS 分别是 37434 和 22434,差了 15000。这就奇怪了,第一笔转账的 Token 金额比第二笔少,但是消耗的 GAS 反而更多,这完全不合理啊!

其实这种现象的解释也很简单,通过对比两笔交易的 VMTRACE 可以发现,最大的差异出现在一条指令上:“SSTORE”。第一笔交易的 SSTORE 消耗了 20000 GAS,第二笔交易只消耗了 5000 GAS。

这时候只好去查文档了,以太坊黄皮书 https://ethereum.github.io/yellowpaper/paper.pdf 附录G:FEE SCHEDULE。发现下面这段说明:

当 SSTORE 将存储的值从 0 改成非 0 时,消耗 20000 GAS,其它情况下消耗 5000 GAS。真相大白,不是以太坊乱收费,文档就是这么写的。

虽然明白了原理,但这的确颠覆了我们的认知,转账的 GAS 费用居然跟对方的账户余额有关!

不过这也解释了一件事:为什么我们在 etherscan 上经常能看到那么多 *.9999999 的转账?可能很多交易所或者黑客早就弄明白了这件事,故意留一点点金额在账户里,减少未来转账的 GAS 手续费。