智能合约编程/Dapp漏洞 -- 浮点数精度Floating Points and Precision

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

到Solidity v0.4.24为止,Solidity并不支持定点数和浮点数。这意味着浮点数的表示必须要用Solidity的整数来表示。如果实现不正确的话,会导致合约漏洞。

攻击原理

由于Solidity里并没有浮点数,程序员需要用标准的整型来自己实现浮点类型。在这个过程中,有一系列的陷阱,这里会介绍其中几个。

下面是程序示例 (简单起见,请忽略其中可能的over/under flow 问题)

contract FunWithNumbers {

    uint constant public tokensPerEth = 10;

    uint constant public weiPerEth = 1e18;

    mapping(address => uint) public balances;

    function buyTokens() public payable {

        uint tokens = msg.value/weiPerEth*tokensPerEth; // convert wei to eth, then multiply by token rate

        balances[msg.sender] += tokens;

    }

    function sellTokens(uint tokens) public {

        require(balances[msg.sender] >= tokens);

        uint eth = tokens/tokensPerEth;

        balances[msg.sender] -= tokens;

        msg.sender.transfer(eth*weiPerEth); //

    }

}

 

这个简单的token买卖合约有几个明显的问题。尽管计算公式是对的,但是由于Solidity缺乏浮点数支持会导致错误的结果。比如在第7行买tokens on line [7], 如果买的数额小于1 ether的话,最初的除法结果是0,导致最后的结果也是0。同样的,在卖tokens的时候小于10最后结果也会是0。实际上,取整一直是向下取整,所以卖29tokens,最终结果是2 token.

这个合约的问题在于精度在最接近的ether (i.e. 1e18 wei)。如果在ERC20合约里需要高精度的话,情况会很复杂。

防护方式

保持正确的精度在智能合约编程中是非常重要的,特别是在处理比率和兑换率的场合。你必须尽量保证分子比较大。比如在例子中,我们使用了兑换率tokensPerEth。此处最好使用weiPerTokens以保证分子是个比较大的数。在想获取token的数量的时候使用msg.value/weiPerTokens.这样会保证高精度

另外一个要注意的是关于操作的次序问题。在上面的例子中,在计算购买tokens的数额时,使用了msg.value/weiPerEth*tokenPerEth。必须注意这里除法在乘法之前。最好把乘法放在除法之前比如msg.value*tokenPerEth/weiPerEth.

最后,为了执行数学运算,最好定义一个虚拟的精度数,把所有的变量都变换成一个高精度数,只有在输出的时候在把他们变换回来。一般,我们会使用uint256类型(可以省Gas费),范围大致有大概60位其中有些位可以用来保持精度。在Solidity合约中最好把所有的变量都变换成一个高精度数,在外部的app中的时候在把他们变换回来。 这也是ERC20中Decimal类型工作的方式。建议去学习Maker DAO合约的DSMath库看看是如何实现的。

转载于:https://my.oschina.net/gavinzheng731/blog/3019752

你可能感兴趣的:(智能合约编程/Dapp漏洞 -- 浮点数精度Floating Points and Precision)