古老的Solidity智能合约错误代码编写

        任何编程语言都有不完善的地方,而使用语言的过程中也可能产生一些逻辑上的Bug。在Solidity0.4.23版本的时候,有人在GitHub上列举了一些使用Solidity编写智能合约时常见的错误用法。虽然现在大家基本上都不会再写同样的问题代码,但是重新学习一下仍然有着借鉴意义。

1、tx.origin

错误用法:判断调用者地址时使用tx.origin作为验证地址。
原因:tx.orgin作为交易的外部发起者,不管中间合约调用(消息)有多少次,它是固定不变的。因此如果tx.origin无意中调用了一个攻击合约,攻击合约再调用被攻击合约,就能通过这个验证。
修复:使用 msg.sender作验证。

2、storage override

错误用法:向一个类似结构数组的状态变量增加元素时,在创建元素时,错误的使用了storage 存储区域。
原因:新创建storage存储区域的元素未初始化时,它的指针指向了存储位置0,内容会被改写。
修复:函数内创建结构类型变量时使用memory关键字。

3、send

错误用法:使用send向一个地址发送ETH时未验证返回值。
原因:如果send返回fail(比如一个合约拒收ETH),此时ether并没有发送成功。
修复:使用transfer而不是send(transfer发送失败会抛出错误引起重置)。

4、reentrancy(重入攻击)

错误用法:在发送ETH之前未改变数据状态。
原因:如果采用低级的call发送eth(或者有足够的gas),在接收方为合约时,可以执行一个回调函数来再次调用这个发送ETH的方法。
修复:将状态改变写在发送ETH之前,使重入无法通过条件验证。

5、overunderflow

错误用法:直接使用运算符进行算术操作。
原因:数据类型都是有大小的,超过了会溢出。
修复:使用safeMath库。

6、forceether

错误用法:当一个合约拒绝接收ETH时,使用this.balance == 0来作一个验证条件。
原因:当一个合约自杀时,它可以指定一个地址来发送剩余的ETH,这个ETH转移无法被拒绝。
修复:不要依赖this.balance == 0作验证条件

7、extcodesize

错误用法:使用它来检查一个地址是否合约,即判断它的代码长度是否为0。
原因:当合约A部署时,在构造器中可以调用外部合约 ,此时合约A的代码长度为0(未部署完成)。
修复:分两种情况,如果只是限定一个方法不能为合约调用,可以使用 tx.orgin != msg.sender作验证,因为如果是合约调用,它的tx.origin必定是一个外部账号。如果只是验证一个合约是合约,不作相反的认证,可以使用它的代码长度不为0来判断。

8、dos

错误用法:当竞标时,将上一个较低竞标的ETH自动退回。
原因:当退回ETH给一个拒绝接收ETH的合约时,会退回失败抛出错误,这样就导致无法再提高竞标价格。
修复:采用提取模式,用户自提。

9、delegatecall

错误用法:比如含有delegatecall调用的合约初始化owner,只验证了是不是委托调用的地址发过来的,而没有验证是否多次初始化。(这个有真实案例)
原因:delegatecall可以调用任何合约的任意方法,并且保持msg.sender不变,这一点尤其需要注意。ERC20代币中的ApproveAndCall也存在一点点小问题。
修复:在使用delegatecall涉及到权限认证时,需要小心谨慎。

10、dos_unbound_array

错误用法:遍历无固定大小的数组
原因:当数组很大很大时,可能会有问题(0.6.0对这一问题作了改进)
修复:使用分页遍历,每次只访问固定数量的数组元素。

11、ArrayStorageOverride

错误用法:通过改变动态数组length来改变动态数组大小时,未验证数组是否为空。
原因:空数组再减1会溢出,形成一个巨大的数组,插入时会改写其它存储内容。
修复:增加数组长度大于0的验证。(注:在Solidity0.6.0中已经不能通过修改动态数组长度来改变数组大小,数组长度为只读的,已经不存在这个问题了)


GITHUB地址 => https://github.com/fergarrui/ethereum-security

不对之处敬请大家留言指正

发布了15 篇原创文章 · 获赞 0 · 访问量 554

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/103903030