关闭

【Solidity】注意事项

标签: Solidity智能合约区块链技术
2121人阅读 评论(0) 收藏 举报
分类:

安全注意事项

虽然通常很容易构建按预期工作的软件,但是更难检查没有人可以以预期的方式使用它。

在Solidity中,这更重要,因为您可以使用智能合同来处理令牌,或者甚至更有价值的东西。 此外,每次执行智能合同都会在公共场合发生,除此之外,源代码通常是可用的。

当然,你总是必须考虑到多少问题:您可以将智能合同与对公众开放的网络服务进行比较(因此也可以与恶意角色进行比较),甚至可以开源。 如果您仅将该杂货清单存储在该Web服务上,则可能不需要太多的关心,但如果您使用该Web服务管理您的银行帐户,则应该更加小心。

本节将列出一些陷阱和一般安全建议,但当然可以永远不会完成。 另外,请记住,即使您的智能合同代码是无错误的,编译器或平台本身也可能出现错误。 可以在已知错误列表中找到编译器的一些已知的安全相关错误的列表,它们也是机器可读的。 请注意,有一个错误赏金程序涵盖了Solidity编译器的代码生成器。

和往常一样,使用开源文档,请帮助我们扩展本节(特别是一些例子不会造成伤害)!

陷阱

私有信息和随机性

您在智能合同中使用的一切都是公开的,即使局部变量和状态变量标记private

如果您不希望矿工能够作弊,在智能合同中使用随机数是非常棘手的。

重入特性

任何来自合约(A)与另一份合约(B)的任何交互以及以太网转交给该合约的任何转让(B)。 这使得B在此交互完成之前可以回叫到A。 举一个例子,下面的代码包含一个错误(它只是一个代码段,而不是一个完整的合同):

pragma solidity ^0.4.0;

// 本合约包含错误 - 请勿使用
contract Fund {
    /// 映射合约的ether
    mapping(address => uint) shares;
    /// 提取你的份额
    function withdraw() {
        if (msg.sender.send(shares[msg.sender]))
            shares[msg.sender] = 0;
    }
}

这里的问题不是太严重,因为gas作为发送的一部分,但仍然存在弱点:Ether传输总是包括代码执行,所以收件人可能是一个回叫撤回的合同。这将让它获得多次退款,并基本上检索合同中的所有Ether。

为了避免重入,您可以使用下面进一步列出的Checks-Effects-Interactions模式:

pragma solidity ^0.4.11;

contract Fund {
    /// 映射合约的ether
    mapping(address => uint) shares;
    /// 提取你的份额
    function withdraw() {
        var share = shares[msg.sender];
        shares[msg.sender] = 0;
        msg.sender.transfer(share);
    }
}

请注意,重入不仅仅是ether传输的影响,而是对另一个合约的任何功能调用。 此外,您还必须考虑多合约情况。 被调用合约可以修改您依赖的另一个合约的状态。

gas限制和循环

没有固定次数循环的循环,例如,依赖于存储值的循环,必须仔细使用:由于gas限制,事务只能消耗一定量的gas。 由于正常操作,明确地或仅仅是循环中的迭代次数可以超过块gas限制,这可能导致完整的合约在某一点停滞。 这可能不适用于只执行从块链接读取数据的常量函数。 尽管如此,这些功能可能被其他合约称为在线操作的一部分,并将其拖延。 请在您的合约文件中明确说明这些情况。

发送和接收Ether

合同和“外部账户”目前都无法阻止某人发送它们。 合同可以做出反应并拒绝常规传输,但是有一些方法可以在不创建消息调用的情况下移动Ether。 一种方法是简单地“挖掘”合同地址,第二种方式是使用selfdestruct(x)

如果合同收到Ether(没有被调用的功能),则执行回退功能。 如果没有回退功能,则Ether将被拒绝(通过抛出异常)。 在执行回退职能时,合约只能依靠当时可用的“gas津贴”(2300gas)。 这个补贴不足以以任何方式访问存储。 为确保您的合同能够以这种方式接收Ether,请检查回退功能的gas需求(例如Remix中的“详细信息”部分)。

有一种方法可以使用addr.call.value(x)()将更多的气体转发给接收合同。 这与addr.transfer(x)基本相同,只是它转发所有剩余的气体,并打开了收件人执行更昂贵的操作的能力(它只返回故障代码,不会自动传播错误)。 这可能包括回调发送合同或者您可能没有想到的其他状态更改。 因此,它可以为诚实用户提供极大的灵活性,也可以为恶意角色扮演角色。

如果要使用address.transfer发送Ether,请注意一些细节:

1.如果收件人是合约,则会导致其回退功能被执行,从而可以回调发送合同。

2.由于呼叫深度超过1024,发送Ether可能会失败。由于呼叫者完全控制呼叫深度,因此可能会迫使传输失败; 考虑这种可能性或使用发送,并确保始终检查其返回值。 更好的是,使用收件人可以提取Ether的模式来编写合约。

3.发送以太网也可能失败,因为接收方合同的执行需要超过分配的气体量(明确地通过使用require, assert, revert, throw出或因为操作太贵) - 它“耗尽”(OOG)。 如果您使用transfersend返回值检查,这可能为收件人阻止发送合同中的进度提供了一种方法。 再次,这里的最佳做法是使用“撤回”模式而不是“发送”模式。

调用堆栈深度

外部函数调用可以随时失败,因为它们超过了1024的最大调用堆栈。在这种情况下,Solidity会引发异常。 在与您的合同交互之前,恶意代理人可能会强制调用堆栈达到高价值。

请注意,如果调用堆栈耗尽,则.send()不会引发异常,而在这种情况下返回false。 低级函数.call(),..callcode().delegatecall()的行为方式相同。

tx.origin

切勿使用tx.origin进行授权。 假设你有这样的钱包合同:

pragma solidity ^0.4.11;

// 本合约包含错误 - 请勿使用
contract TxUserWallet {
    address owner;

    function TxUserWallet() {
        owner = msg.sender;
    }

    function transferTo(address dest, uint amount) {
        require(tx.origin == owner);
        dest.transfer(amount);
    }
}

现在有人把你发送给这个攻击钱包的地址:

pragma solidity ^0.4.11;

interface TxUserWallet {
    function transferTo(address dest, uint amount);
}

contract TxAttackWallet {
    address owner;

    function TxAttackWallet() {
        owner = msg.sender;
    }

    function() {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
    }
}

如果您的钱包已经检查了msg.sender的授权,它将获得攻击钱包的地址,而不是所有者的地址。 但是通过检查tx.origin,它会获得启动交易的原始地址,该地址仍然是所有者地址。 攻击钱包立即耗尽所有资金。

次要细节

对于for (var i = 0; i < arrayName.length; i++) { ... }i的类型将为uint8,因为这是保存值0所需的最小类型。如果数组有更多 超过255个元素,循环将不会终止。

函数的常量关键字当前没有被编译器强制执行。 此外,它不是由EVM执行的,所以“claims”为常数的合同函数可能仍然导致状态的改变。

不占用32个字节的类型可能包含“脏高位”。 如果您访问msg.data,这是特别重要的 - 它带来可延展性风险:您可以制作一个调用函数f(uint8 x)的事务,原始字节参数为0xff000001和0x00000001。 两者都符合合同规定,两者将看起来像x一样,但是msg.data将不同,所以如果你使用keccak256(msg.data),你会得到不同的结果。

建议

限制Ether的量

限制可以存储在智能合同中的Ether(或其他令牌)数量。 如果您的源代码,编译器或平台有错误,这些资金可能会丢失。 如果你想限制你的损失,限制以太网的数量。

保持小而模块化

保持您的合约小而易于理解。 在其他合同或库中单独列出不相关的功能。 关于源代码质量的一般建议当然适用:限制局部变量的数量,函数的长度等。 记录您的功能,以便其他人可以看到您的意图,以及它是否与代码不同。

使用Checks-Effects-Interactions模式

大多数函数将首先执行一些检查(谁称为函数,是范围的参数,它们是否发送足够的以太网,该人是否具有令牌等)。 这些检查应该先做好。

作为第二步,如果所有支票通过,都应对当前合同的状态变量产生影响。 与其他合同的互动应该是任何功能的最后一步。

早期合同延迟了一些影响,并等待外部函数调用返回到非错误状态。 这通常是一个严重的错误,因为上面解释的重入问题。

请注意,对已知合同的调用也可能会导致未知合同的调用,因此总是应用此模式可能更好。

引入故障安全模式

在使系统完全分散化的同时,将删除任何中介,这可能是一个好主意,特别是对于新的代码,包括某种故障安全机制:

您可以在您的智能合同中添加一个功能,执行一些自检,例如“有任何ether泄漏?”,“令牌的总和等于合同的余额”或类似的事情。 请记住,您不能使用太多的气体,因此可能需要通过脱机计算来帮助。

如果自检失败,合同将自动切换为某种“故障安全”模式,例如,禁用大部分功能,对固定和受信任的第三方进行控制,或者将合同转换为简单的“ 给我回钱“合约。

形式化验证

使用形式验证,可以执行自动数学证明,您的源代码满足一定的正式规范。 规范仍然是正式的(就像源代码一样),但通常要简单得多。

请注意,正式验证本身只能帮助您了解您所做的(规格)与实际操作(实际执行)之间的区别。 您仍然需要检查规格是否符合您的要求,并且您不会错过任何意外的影响。

0
0
查看评论

Solidity番外篇(一)Solidity在线or插件使用

在学习以太坊合约的过程中会需要自己编写智能合约,官方提供了几种方式供大家使用。下面分别简单介绍一下,如果有错误的地方,还留言指正补充。DAPP IDE说实话,这个版本IDE我还没有使用过,只提供一个连接地址供大家参考。 The-DApp-IDE 此连接中有具体的使用描述。在线编程Solidity...
  • wo541075754
  • wo541075754
  • 2016-11-18 11:13
  • 2417

Solidity 文档--第三章:Solidity 编程实例

Solidity 编程实例Voting 投票接下来的合约非常复杂,但展示了很多Solidity的特性。它实现了一个投票合约。当然,电子选举的主要问题是如何赋予投票权给准确的人,并防止操纵。我们不能解决所有的问题,但至少我们会展示如何委托投票可以同时做到投票统计是自动和完全透明。思路是为每张选票创建一...
  • wo541075754
  • wo541075754
  • 2016-11-21 09:57
  • 3073

【Solidity】4.单位和全局可变量 - 深入理解Solidity

单位和全局可变量Ether单元一个字面上的数字可以带有wei,finney,szabo或者以太网的后缀,可以在以太网的子目录之间进行转换,其中没有后缀的以太网货币号被假定为魏。 2 ether == 2000 finney评估为true。时间单位可以使用文字数字后的秒,分,小时,天,周和年份进行后缀...
  • diandianxiyu
  • diandianxiyu
  • 2017-09-12 10:42
  • 2144

Solidity 文档--目录

Solidity是一种语法类似JavaScript的高级语言。它被设计成以编译的方式生成以太坊虚拟机代码。在后续内容中你将会发现,使用它很容易创建用于投票、众筹、封闭拍卖、多重签名钱包等等的合约。 注意 目前尝试Solidity的最好方式是使用基于浏览器的编译器(需要一点时间加载,请耐心等待)。...
  • fidelhl
  • fidelhl
  • 2016-02-22 17:12
  • 6333

『0011』 - Solidity Types - 地址(Address)

孔壹学院:国内区块链职业教育领先品牌 作者:黎跃春,区块链、高可用架构工程师 微信:liyc1215 QQ群:348924182 博客:http://liyuechun.org 以太坊钱包地址位数验证以太坊中的地址的长度为20字节,一字节等于8位,一共160位,所以address...
  • liyuechun520
  • liyuechun520
  • 2017-11-01 09:46
  • 254

【Solidity】3.类型 - 深入理解Solidity

类型Solidity是一种静态类型的语言,这意味着每个变量(州和地方)的类型需要被指定的(或至少已知的 - 见下文型扣)在编译时。 Solidity提供了几种可以组合形成复杂类型的基本类型。另外,类型可以在含有运算符的表达式与彼此交互。 对于操作的快速参考,请参阅运算符的优先顺序。值类型以下类型也称...
  • diandianxiyu
  • diandianxiyu
  • 2017-09-11 10:52
  • 2063

Solidity 文档--第二章:安装 Solidity

安装Solidity基于浏览器的Solidity如果你只是想尝试一个使用Solidity的小合约,你不需要安装任何东西,只要访问基于浏览器的Solidity。如果你想离线使用,你可以保存页面到本地,或者从 http://github.com/chriseth/browser-solidity 克隆一...
  • wo541075754
  • wo541075754
  • 2016-11-18 15:47
  • 2614

【Solidity】5.表达式和控制结构 - 深入理解Solidity

表达式和控制结构输入参数和输出参数与Javascript一样,函数可以将参数作为输入; 与Javascript和C不同,它们也可以返回任意数量的参数作为输出。输入参数输入参数的声明方式与变量相同。 作为例外,未使用的参数可以省略变量名称。 例如,假设我们希望我们的合约接受一种具有两个整数的外部调用,...
  • diandianxiyu
  • diandianxiyu
  • 2017-09-13 12:54
  • 2093

solidity数据结构

pragma solidity ^0.4.0; contract aaa{ uint[] a; function aaa(){ a.push(111); } function add(uint n){ a.push(n); }...
  • Vinsuan1993
  • Vinsuan1993
  • 2017-07-28 23:22
  • 343

Solidity 官方文档中文版(二)

深入理解 Solidity 此节将帮助你深入理解Solidity,如果有遗漏,请和我们联系Githhub上发pull request Importing other Source Files Structure of a Contract 合约的结构 Val...
  • xiatiancc
  • xiatiancc
  • 2017-12-27 11:34
  • 491
    个人资料
    • 访问:651679次
    • 积分:9136
    • 等级:
    • 排名:第2441名
    • 原创:298篇
    • 转载:13篇
    • 译文:4篇
    • 评论:83条
    博客专栏
    微信订阅号
    欢迎加入QQ群
    玩家老黄历(微信小程序)
    谢谢支持~
    最新评论