关于以太坊智能合约的新认知

1. 智能合约是可以删除的

删除智能合约的 EVM 字节码为 SELFDESTRUCT (之前称为 SUICIDE)。该操作系统会提供 gas 退款,激励用户删除存储状态的方式释放资源。删除智能合约并不会清楚合约之前历史的交易记录,区块链本身并不可改变。

2. Solidity 目前的版本号码是Version 0.5.9

其中主版本号是 0,它表示任何东西都有可能修改。次版本号是 5,在 Solidity 实际编码中,次版本号视为主版本号。9 为补丁号,实际编码中视为次版本号。当然建议开发者使用最新的版本。

3. ABI,Application Binary Interface,应用程序二进制接口

ABI 定义了数据结构和函数如何在机器指令中被访问。ABI 是向机器指令层面编码和解码并传送数据的主要方式。与 API 不同。ABI 对智能合约进行编码,具体是对 EVM 的调用和从交易获取数据的调用进行编码。例如,钱包软件调用 withdraw 函数时,需要通过 ABI 知道,该调用需要一个 uint256 类型的变量,变量名称为 withdraw_amount。然后钱包软件就会提示用户输入该参数,接着创建一个以太坊交易,调用合约的 withdraw 函数。

4. 智能合约设计中的安全设计模式

4.1 重入:典型案例为DAO。
  • 尽可能地使用内置的 transfer 函数想外部合约发送以太币。因为 transfer 仅会给外部调用2300gas,所以不足以支持目标地址或者合约再次调用其他合约,也就不足以重入发送以太币的合约。避免使用 send 或者 call。

  • 确保状态变量的修改都发生在合约发送以太币之前。即按照”检查-生效-交互”模式。

  • 引入互斥锁,也就是增加一个状态变量在代码执行中锁定合约,避免重入的调用。请参照智能合约退款方式一文

4.2 溢出:典型案例为美团币被盗。

使用 openzeppelin 的 safemath 做计算防范溢出。

4.3 以太币余额逻辑陷阱:典型案例为游戏应用中的存 ether,中奖 ether漏洞。
  • 避免使用 this.balance 的具体数值,它可能会被操纵。

  • 如果需要判断以太币的具体充值数额,可以使用一个自定义的变量在 payable 的数字中记录数额的变动。需要保证这个变量不会被 selfdestruct 调用强制发送的以太币所影响。

4.4 尽量避免使用 DELEGATECALL,尽量使用 library。保持库合约是无状态的,不会selfdestruct自我销毁的。典型案例 Parity 多重签名钱包的第二次共计漏洞。
4.5 保持正确的可见性,public/private/internal,不建议省略,因为省略即为 public。
4.6 区块链上的随机性目前还是一个难题。使用过往或者最近的变量都是灾难性的。

随机数必须来自区块链外部。或者,由像”提交-揭示”这样的模式在节点之间来实现。或者通过更改一组参与者之间的信任模型来实现。例如:randao , oraclize

4.7 参数攻击

向智能合约传递参数时,这些参数需要依照 ABI 规范进行编码。不过,发送的实际数据长度小于标准的参数编码长度也是可以的。在这种情况下,EVM 会在数据末尾补 0 使得数据长度达到要求。这可能会引起实际的转账以太币数量增长 10 倍,100倍甚至更高。防范方法是外部应用程序在把输入参数发送到区块链之前都应该对它们进行校验。由于数据填充发生在尾部,所以在合约中仔细考虑参数的顺序也可以在一定程度上减轻共计的危害。

4.8 Tx.Origin

智能合约中不应该使用Tx.Origin来进行验证授权。攻击者可以将共计合约的地址伪装成权限拥有人的地址,这样就可以通过验证,操作后续的(提款)逻辑。

Don’t roll your own crypto!