从软件工程角度看,智能合约系统希望在合理的情况下最大程度地实现重用。 在Solidity中重用合约代码有很多方法。 使用你拥有的以前部署的经过验证的智能合约是实现代码重用的最安全的方式。
在以前所拥有已部署智能合约不可重用时重复还是很需要的。 现在Live Libs 和Zeppelin Solidity 正寻求提供安全的智能合约组件使其能够被重用而不需要每次都重新编写。任何合约安全性分析都必须标明重用代码,特别是以前没有建立与目标智能合同系统中处于风险中的资金相称的信任级别的代码。
2 安全通知以下这些地方通常会通报在Ethereum或Solidity中新发现的漏洞。安全通告的官方来源是Ethereum Blog,但是一般漏洞都会在其他地方先被披露和讨论。
Ethereum Blog: The official Ethereum blog
(地址:https://blog.ethereum.org/)
Ethereum Blog - Security only: 所有相关博客都带有Security标签
(https://blog.ethereum.org/category/security/)
Ethereum Gitter 聊天室
(地址:https://gitter.im/ethereum/home)
Solidity(地址:https://gitter.im/ethereum/solidity)
Go-Ethereum(地址:https://gitter.im/ethereum/go-ethereum)
CPP-Ethereum(地址:https://gitter.im/ethereum/cpp-ethereum)
Research(地址:https://gitter.im/ethereum/research)
Reddit(地址:https://www.reddit.com/r/ethereum/)
Network Stats(地址:https://ethstats.net/)
强烈建议你经常浏览这些网站,尤其是他们提到的可能会影响你的智能合约的漏洞。
另外, 这里列出了以太坊参与安全模块相关的核心开发成员, 浏览 bibliography 获取更多信息。
(地址:https://github.com/ConsenSys/smart-contract-best-practices#smart-contract-security-bibliography)
Vitalik Buterin: Twitter, Github, Reddit, Ethereum Blog
Dr. Christian Reitwiessner: Twitter, Github, Ethereum Blog
Dr. Gavin Wood: Twitter, Blog, Github
Vlad Zamfir: Twitter, Github, Ethereum Blog
除了关注核心开发成员,参与到各个区块链安全社区也很重要,因为安全漏洞的披露或研究将通过各方进行。
3 关于使用Solidity开发的智能合约安全建议外部调用
尽量避免外部调用
调用不受信任的外部合约可能会引发一系列意外的风险和错误。外部调用可能在其合约和它所依赖的其他合约内执行恶意代码。因此,每一个外部调用都会有潜在的安全威胁,尽可能的从你的智能合约内移除外部调用。当无法完全去除外部调用时,可以使用这一章节其他部分提供的建议来尽量减少风险。
仔细权衡“send()”、“transfer()”、以及“call.value()”
当转账Ether时,需要仔细权衡
“someAddress.send()”、“someAddress.transfer()”、和“someAddress.call.value()()”之间的差别。
x.transfer(y)和if (!x.send(y)) throw;是等价的。send是transfer的底层实现,建议尽可能直接使用transfer。
someAddress.send()和someAddress.transfer() 能保证可重入 安全 。尽管这些外部智能合约的函数可以被触发执行,但补贴给外部智能合约的2,300 gas,意味着仅仅只够记录一个event到日志中。
someAddress.call.value()() 将会发送指定数量的Ether并且触发对应代码的执行。被调用的外部智能合约代码将享有所有剩余的gas,通过这种方式转账是很容易有可重入漏洞的,非常 不安全。
使用send() 或transfer() 可以通过制定gas值来预防可重入, 但是这样做可能会导致在和合约调用fallback函数时出现问题,由于gas可能不足,而合约的fallback函数执行至少需要2,300 gas消耗。
一种被称为push 和pull的机制试图来平衡两者, 在 push 部分使用send() 或transfer(),在pull 部分使用call.value()()。
(*译者注:在需要对外未知地址转账Ether时使用send() 或transfer(),已知明确内部无恶意代码的地址转账Ether使用call.value()())
需要注意的是使用send() 或transfer() 进行转账并不能保证该智能合约本身重入安全,它仅仅只保证了这次转账操作时重入安全的。
处理外部调用错误
Solidity提供了一系列在raw address上执行操作的底层方法,比如:
address.call(),address.callcode(), address.delegatecall()和address.send。
这些底层方法不会抛出异常(throw),只是会在遇到错误时返回false。
另一方面, contract calls (比如,
ExternalContract.doSomething())会自动传递异常,(比如,
doSomething()抛出异常,那么ExternalContract.doSomething() 同样会进行throw )。
如果你选择使用底层方法,一定要检查返回值来对可能的错误进行处理。
// bad someAddress.send(55);
someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn\'tcheck for result
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted
// good
if(!someAddress.send(55)) {
// Some failurecode
}
ExternalContract(someAddress).deposit.value(100);
不要假设你知道外部调用的控制流程