24-ETH-美链

声明:本文是要点笔记,介绍和系列笔记均收录在专题:区块链技术与应用

美链事件

2018年4月发生的事件,美链是发行在以太坊上的代币,这些代币没有自己的区块链,而是以智能合约的形式运行在以太坊的EVM平台上。发行这个代币的智能合约,对应的是以太坊状态树的一个节点,这个节点有它自己的账户余额,就相当于这个智能合约一共有多少个以太币,就是发行这个代币的智能合约它的资产有多少个以太币,然后在这个合约里每个账户上有多少个代币,这个是作为存储树中的变量,存储在智能合约的账户里。代币的发行、转账、销毁都是通过调用智能合约中的函数来实现的,这个也是跟以太坊上的以太币不太一样的地方,它不像以太坊一样需要挖矿来维护一个底层的基础链,像以太坊上每个账户有多少个以太币,这个是直接保存在状态树中的变量,然后以太坊上面两个账户转账是通过发布一个交易到区块链上,这个交易会打包到发布的区块链上面,而代币发生转账的话实际上就是智能合约上面两个账户之间发生转账,通过调用智能合约上的函数,就可以完成了。每个代币都可以制定自己的发行规则,比如某个代币是1个以太坊兑换100个代币,那么比如说从某个外部账户转1个以太币给这个智能合约,这个智能合约就可以给你在这个智能合约里的代币账户上发送100个代币,每个代币账户上有多少个代币的信息都是维护在存储树里面,发行这个代币的智能合约的存储树里面。

以太坊平台的出现让发行代币提供了方便,包括以前说的 eos,这个在上线之前也是作为以太坊上的代币形式,上线的意思是有自己的基础链了,不用依附在以太坊上了。以太坊发行代币的标准为ERC20(Ethereum Request for Comments)。

美链

美链的 batchTransfer 的函数:

batchTransfer

这个函数有两个参数,第一个参数是一个数组,接收者地址的数组,第二个参数value是转账的金额,给每个人转多少。

uint256 amount = uint256(cnt) * _value; // 计算一共要转的总金额
require(cnt > 0 && cnt <= 20); // 检查接收者的数目不超过20个
require(_value > 0 && balances[msg.sender] >= amount); // 检查发起调用函数的这个账户是否有这么多钱
balances[msg.sender] = balances[msg.sender].sub(amount); // 把发起调用的账户余额减去amount
// 下面用一个循环给每个接收者接收 value 这么多的代币
...

那么,问题出在哪?

问题出在:uint256 amount = uint256(cnt) * _value;, 当 value 的值很大的时候可能会发生溢出,那么 amount 算出来可能是个很小的一个值,所以从调用者的代币中减的时候是很小一部分的代币,但还是给每个 receivers 增加那么多 value 的代币。最后系统中相当于多发行了许多的代币。

攻击细节

攻击细节 1

第0号参数是 _receivers 数组在参数列表中的位置,这里是16进制的,0040对应的是4乘16=64,第一个参数出现在第64个字节的位置,一行有64个数字,1个16进制的数字需要用4个二进制的数字来表示,64乘4=256位,也就是说一行有32个字节。所以是从 [2] 行开始表示 receivers,表示了数组的长度是2,[3] [4]两行是接受的地址。[1] 行表示value的值。注意,[1] 行的参数是80000...再乘以接收者2个,算出来的溢出正好是0。

攻击细节 2

红框中可以看到每个地址都是接收了很大数量的代币。

攻击结果

攻击结果

攻击使代币的价格造成致命性的打击,差不多快要归零了。

暂停提币

代币上市的交易所在发生攻击后暂停提币的功能,并且回滚了交易。

反思

在进行数学运算的时候一定要考虑溢出的可能性。solidity有一个safeMath库,里面提供的操作运算都会自动检测有没有出现溢出。

C语言里,两个数相乘会有一定的精度损失,再除以一个数,不一定会得到和另外一个数一模一样的数。但是在solidity里面是不存在的,因为两个数都是256位的整数,整数先进行乘法,再进行除法。

batchTransfer的加法和减法都用的safeMath库,只有乘法不小心没有使用,结果酿成了悲剧。曾经有人怀疑是不是故意的,但从事件的结果来看又不像使故意的。

预防措施

mul() 函数中,先用a*b = c,再用c/a看看是否等于b,如果发生溢出的话,assert()会抛出异常。

你可能感兴趣的:(24-ETH-美链)