编码规范问题
编译器版本
旧版编译器可能存在安全问题, 例如:
-
solidity 0.4.12 之前, 编译器会跳过参数中的空字符串, 比如
send(from,to,"",amount)
会被编译成 send(from,to,amount)
参考: https://paper.seebug.org/631/#44-dividenddistributor
-
V0.4.22 中, 如果合约使用了两种构造函数, 会忽略其中一个函数
-
V0.4.25 修复了未初始化存储指针问题: https://etherscan.io/solcbuginfo
构造函数书写问题
参考上述第 2 点
返回标准
遵循 ERC20 规范, 要求 transfer、approve 函数应返回 bool 值, 需要添加返回值代码
transferFrom 返回结果应该和 transfer 返回结果一致
事件标准
遵循 ERC20 规范, 要求 transfer、approve 函数触发相应的事件
假充值问题
转账函数中,对余额以及转账金额的判断,需要使用 require 函数抛出错误,否则会错误
的判断为交易成功
设计缺陷问题
approve 函数条件竞争
使用 approve 授权函数修改 allowance 前, 需要先将 value 置零, 否则可以通过更高的
gas price 抢先使用代币
循环 Dos 问题
循环消耗问题
循环次数越大, 交易消耗的 gas 越多, 当超过 gas 限制时会导致交易失败
案例:
- Simoleon(SIM): https://paper.seebug.org/646/
- Pandemica: https://bcsec.org/index/detail/id/260/tag/2
循环安全问题
避免让用户控制循环次数, 否则容易受到 Dos 攻击, 比如以下代码的_addresses.length
由用户所控制:
编码安全问题
溢出问题
-
算术溢出, 应该使用 SafeMath 库进行运算
-
铸币烧币溢出, 应该设置 totalSupply 的上限
重入漏洞
在智能合约中提供了 call、send、transfer 三种方式来交易以太坊,其中 call 最大的区
别就是 没有限制 gas ,而其他两种在 gas 不够的情况下都会报 out of gas
重入漏洞有以下特征:
- 使用了 call 函数作为转账函数
- 没有限制 call 函数的 gas
- 扣余额在转账之后
- call 时加入了
()
来执行 fallback 函数
demo:
通过互斥锁避免递归:
call 注入
call 函数调用时,应该做严格的权限控制,或直接写死 call 调用的函数
案例:
权限控制
检查合约中各函数是否正确使用了 public、private 等关键词进行可见性修饰,检查合约是
否正确定义并使用了 modifier 对关键函数进行访问限制,避免越权导致的问题
以下函数不该为 public:
案例:
重放攻击
合约中如果涉及委托管理的需求,应注意验证的不可复用性,避免重放攻击
这里举例子为 transferProxy 函数,该函数用于当 user1 转 token 给 user3,但没有 eth 来支付
gasprice,所以委托 user2 代理支付,(user2)通过调用 transferProxy 来完成
这个函数的问题在于 nonce 值是可以预判的,其他变量不变的情况下,可以进行重放攻击,
多次转账
编码设计问题
地址初始化
涉及到地址的函数中,建议加入 require(_to!=address(0))
验证,有效避免用户误操作或
未知错误导致的不必要的损失
判断函数
涉及到条件判断的地方,使用 require 函数而不是 assert 函数,因为 assert 会导致剩余的 gas
全部消耗掉,而他们在其他方面的表现都是一致的
余额判断
不要假设合约创建时余额为 0,可以强制转账
攻击者可以用 1wei 来创建合约,然后调用 selfdestruct(victimAddress)
来销毁,这样余
额就会强制转移给目标,而且目标合约没有代码执行,无法阻止
转账函数
在完成交易时,默认情况下推荐使用 transfer 而不是 send 完成交易
当 transfer 或者 send 函数的目标是合约时,会调用合约的 fallback 函数。但 fallback 函数执
行失败时, transfer 会抛出错误并自动回滚,而 send 会返回 false ,所以在使用 send 时需
要判断返回类型,否则可能会导致转账失败但余额减少的情况
代码外部调用设计
对于外部合约优先使用 pull 而不是 push
错误样例:
当需要向某一方转账时,将转账改为定义 withdraw 函数,让用户自己来执行合约将余额取出,
这样可以最大程度的避免未知的损失
范例代码:
错误处理
合约中涉及到 call 等在 address 底层操作的方法时,做好合理的错误处理
操作如果遇到错误并不会抛出异常,而是会返回 false 并继续执行:
弱随机数
合约中要保证生成的随机数无法被猜测
案例:
变量覆盖
在合约中避免 array 变量 key 可以被控制
编码问题隐患
语法特性
在智能合约中,所有的整数除法都会向下取整到最接近的整数,当我们需要更高的精度时,
我们需要使用乘数来加大这个数字
错误样例:
正确代码:
数据隐私
在合约中,所有的数据包括私有变量都是公开的,不可以将任何有私密性的数据储存在链上
数据可靠性
合约中不应该让时间戳参与到代码中,容易受到矿工的干扰,应使用 block.height 等不变的
数据
gas 消耗优化
对于某些不涉及状态变化的函数和变量可以加 constant 来避免 gas 的消耗
合约用户
合约中,应尽量考虑交易目标为合约时的情况,避免因此产生的各种恶意利用
日志记录
关键事件应有 Event 记录,为了便于运维监控,除了转账,授权等函数以外,其他操作也需
要加入详细的事件记录,如转移管理员权限、其他特殊的主功能
TODO 回调函数