├── .gitignore ├── README-zh.md ├── README.md ├── docs ├── about │ ├── contributing.md │ ├── license.md │ └── reviewers.md ├── bibliography.md ├── bug_bounty_list.md ├── documentation_procedures.md ├── general_philosophy.md ├── img │ ├── diligence_logo.svg │ └── favicon.ico ├── index.md ├── known_attacks.md ├── recommendations.md ├── security_eips.md ├── security_notifications.md ├── security_tools.md ├── software_engineering.md ├── stylesheets │ └── extra.css └── tokens.md ├── mkdocs.yml ├── requirements.txt └── theme ├── assets └── images │ ├── diligence_logo-02.svg │ └── diligence_logo.svg └── partials └── footer.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | 4 | # virtualenv directory 5 | /venv 6 | .python-version 7 | 8 | # build directory 9 | /site 10 | 11 | # JetBrains IDE 12 | /.idea 13 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # 以太坊智能合约 —— 最佳安全开发指南 2 | 3 | *本文翻译自:https://github.com/ConsenSys/smart-contract-best-practices。 4 | 为了使语句表达更加贴切,个别地方未按照原文逐字逐句翻译,如有出入请以原文为准。* 5 | 6 | [![Join the chat at https://gitter.im/ConsenSys/smart-contract-best-practices](https://badges.gitter.im/ConsenSys/smart-contract-best-practices.svg)](https://gitter.im/ConsenSys/smart-contract-best-practices?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | **主要章节如下**: 9 | 10 | - [**Solidity安全贴士**](#solidity-tips) 11 | - [**已知的攻击手段**](#known-attacks) 12 | - [***竞态***](#race-conditions) 13 | - [***可重入***](#reentrancy) 14 | - [***交易顺序依赖***](#transaction-ordering-dependence) 15 | - [***针对Gas的攻击***](#dos-with-block-gas-limit) 16 | - [***整数上溢/整数下溢***](#integer-overflow-and-underflow) 17 | - [**软件工程开发技巧**](#eng-techniques) 18 | - [**参考文献**](#bibliography) 19 | 20 | 这篇文档旨在为Solidity开发人员提供一些智能合约的安全准则(**security baseline**)。当然也包括智能合约的**安全开发理念、bug赏金计划指南、文档例程以及工具。** 21 | 22 | 我们邀请社区对该文档提出修改或增补建议,欢迎各种合并请求(Pull Request)。若有相关的文章或者博客的发表,也清将其加入到[参考文献](#bibliography)中,具体详情请参见我们的[社区贡献指南](CONTRIBUTING.md)。 23 | 24 | #### 更多期待内容 25 | 我们欢迎并期待社区开发者贡献以下几个方面的内容: 26 | -  Solidity代码测试(包括代码结构,程序框架 以及 常见软件工程测试) 27 | -  智能合约开发经验总结,以及更广泛的基于区块链的开发技巧分享 28 | 29 | ## 基本理念 30 | 31 | 32 | 以太坊和其他复杂的区块链项目都处于早期阶段并且有很强的实验性质。因此,随着新的bug和安全漏洞被发现,新的功能不断被开发出来,其面临的安全威胁也是不断变化的。这篇文章对于开发人员编写安全的智能合约来说只是个开始。 33 | 34 | 开发智能合约需要一个全新的工程思维,它不同于我们以往项目的开发。因为它犯错的代价是巨大的,并且很难像传统软件那样轻易的打上补丁。就像直接给硬件编程或金融服务类软件开发,相比于web开发和移动开发都有更大的挑战。因此,仅仅防范已知的漏洞是不够的,你还需要学习新的开发理念: 35 | 36 | - **对可能的错误有所准备**。任何有意义的智能合约或多或少都存在错误。因此你的代码必须能够正确的处理出现的bug和漏洞。始终保证以下规则: 37 | - 当智能合约出现错误时,停止合约,(“断路开关”) 38 | - 管理账户的资金风险(限制(转账)速率、最大(转账)额度) 39 | - 有效的途径来进行bug修复和功能提升 40 | 41 | - [**谨慎发布智能合约**](#contract-rollout)。 尽量在正式发布智能合约之前发现并修复可能的bug。 42 | - 对智能合约进行彻底的测试,并在任何新的攻击手法被发现后及时的测试(包括已经发布的合约) 43 | - 从alpha版本在测试网(testnet)上发布开始便提供[bug赏金计划](#bounties) 44 | - 阶段性发布,每个阶段都提供足够的测试 45 | 46 | - **保持智能合约的简洁**。复杂会增加出错的风险。 47 | - 确保智能合约逻辑简洁 48 | - 确保合约和函数模块化 49 | - 使用已经被广泛使用的合约或工具(比如,不要自己写一个随机数生成器) 50 | - 条件允许的话,清晰明了比性能更重要 51 | - 只在你系统的去中心化部分使用区块链 52 | 53 | - **保持更新**。通过下一章节所列出的资源来确保获取到最新的安全进展。 54 | - 在任何新的漏洞被发现时检查你的智能合约 55 | - 尽可能快的将使用到的库或者工具更新到最新 56 | - 使用最新的安全技术 57 | 58 | - **清楚区块链的特性**。尽管你先前所拥有的编程经验同样适用于以太坊开发,但这里仍然有些陷阱你需要留意: 59 | - 特别小心针对外部合约的调用,因为你可能执行的是一段恶意代码然后更改控制流程 60 | - 清楚你的public function是公开的,意味着可以被恶意调用。(在以太坊上)你的private data也是对他人可见的 61 | - 清楚gas的花费和区块的gas limit 62 | 63 | ### 基本权衡:简单性与复杂性 64 | 65 | 66 | 在评估一个智能合约的架构和安全性时有很多需要权衡的地方。对任何智能合约的建议是在各个权衡点中找到一个平衡点。 67 | 68 | 从传统软件工程的角度出发:一个理想的智能合约首先需要模块化,能够重用代码而不是重复编写,并且支持组件升级。从智能合约安全架构的角度出发同样如此,模块化和重用被严格审查检验过的合约是最佳策略,特别是在复杂智能合约系统里。 69 | 70 | 然而,这里有几个重要的例外,它们从合约安全和传统软件工程两个角度考虑,所得到的重要性排序可能不同。当中每一条,都需要针对智能合约系统的特点找到最优的组合方式来达到平衡。 71 | 72 | - 固化 vs 可升级 73 | - 庞大 vs 模块化 74 | - 重复 vs 可重用 75 | 76 | #### 固化 vs 可升级 77 | 78 | 在很多文档或者开发指南中,包括该指南,都会强调延展性比如:可终止,可升级或可更改的特性,不过对于智能合约来说,延展性和安全之间是个*基本权衡*。 79 | 80 | 延展性会增加程序复杂性和潜在的攻击面。对于那些只在特定的时间段内提供有限的功能的智能合约,简单性比复杂性显得更加高效,比如无管治功能,有限短期内使用的代币发行的智能合约系统(governance-fee,finite-time-frame token-sale contracts)。 81 | 82 | #### 庞大 vs 模块化 83 | 84 | 一个庞大的独立的智能合约把所有的变量和模块都放到一个合约中。尽管只有少数几个大家熟知的智能合约系统真的做到了大体量,但在将数据和流程都放到一个合约中还是享有部分优点--比如,提高代码审核(code review)效率。 85 | 86 | 和在这里讨论的其他权衡点一样,传统软件开发策略和从合约安全角度出发考虑,两者不同主要在对于简单、短生命周期的智能合约;对于更复杂、长生命周期的智能合约,两者策略理念基本相同。 87 | 88 | #### 重复 vs 可重用 89 | 90 | 从软件工程角度看,智能合约系统希望在合理的情况下最大程度地实现重用。 在Solidity中重用合约代码有很多方法。 使用**你拥有的**以前部署的经过验证的智能合约是实现代码重用的最安全的方式。 91 | 92 | 在以前所拥有已部署智能合约不可重用时重复还是很需要的。 现在[Live Libs](https://github.com/ConsenSys/live-libs) 和[Zeppelin Solidity](https://github.com/OpenZeppelin/zeppelin-solidity) 正寻求提供安全的智能合约组件使其能够被重用而不需要每次都重新编写。任何合约安全性分析都必须标明重用代码,特别是以前没有建立与目标智能合同系统中处于风险中的资金相称的信任级别的代码。 93 | 94 | ## 安全通知 95 | 96 | 以下这些地方通常会通报在Ethereum或Solidity中新发现的漏洞。安全通告的官方来源是Ethereum Blog,但是一般漏洞都会在其他地方先被披露和讨论。 97 | 98 | - [Ethereum Blog](https://blog.ethereum.org/): The official Ethereum blog 99 | - [Ethereum Blog - Security only](https://blog.ethereum.org/category/security/): 所有相关博客都带有**Security**标签 100 | - [Ethereum Gitter](https://gitter.im/orgs/ethereum/rooms) 聊天室 101 | - [Solidity](https://gitter.im/ethereum/solidity) 102 | - [Go-Ethereum](https://gitter.im/ethereum/go-ethereum) 103 | - [CPP-Ethereum](https://gitter.im/ethereum/cpp-ethereum) 104 | - [Research](https://gitter.im/ethereum/research) 105 | - [Reddit](https://www.reddit.com/r/ethereum) 106 | - [Network Stats](https://ethstats.net/) 107 | 108 | 强烈建议你经常浏览这些网站,尤其是他们提到的可能会影响你的智能合约的漏洞。 109 | 110 | 另外, 这里列出了以太坊参与安全模块相关的核心开发成员, 浏览 [bibliography](https://github.com/ConsenSys/smart-contract-best-practices#smart-contract-security-bibliography) 获取更多信息。 111 | 112 | - **Vitalik Buterin**: [Twitter](https://twitter.com/vitalikbuterin), [Github](https://github.com/vbuterin), [Reddit](https://www.reddit.com/user/vbuterin), [Ethereum Blog](https://blog.ethereum.org/author/vitalik-buterin/) 113 | - **Dr. Christian Reitwiessner**: [Twitter](https://twitter.com/ethchris), [Github](https://github.com/chriseth), [Ethereum Blog](https://blog.ethereum.org/author/christian_r/) 114 | - **Dr. Gavin Wood**: [Twitter](https://twitter.com/gavofyork), [Blog](http://gavwood.com/), [Github](https://github.com/gavofyork) 115 | - **Vlad Zamfir**: [Twitter](https://twitter.com/vladzamfir), [Github](https://github.com/vladzamfir), [Ethereum Blog](https://blog.ethereum.org/author/vlad/) 116 | 117 | 除了关注核心开发成员,参与到各个区块链安全社区也很重要,因为安全漏洞的披露或研究将通过各方进行。 118 | 119 | 120 | ## 关于使用Solidity开发的智能合约安全建议 121 | 122 | ### 外部调用 123 | 124 | #### 尽量避免外部调用 125 | 126 | 127 | 调用不受信任的外部合约可能会引发一系列意外的风险和错误。外部调用可能在其合约和它所依赖的其他合约内执行恶意代码。因此,每一个外部调用都会有潜在的安全威胁,尽可能的从你的智能合约内移除外部调用。当无法完全去除外部调用时,可以使用这一章节其他部分提供的建议来尽量减少风险。 128 | 129 | 130 | 131 | #### 仔细权衡“send()”、“transfer()”、以及“call.value()” 132 | 133 | 当转账Ether时,需要仔细权衡“someAddress.send()”、“someAddress.transfer()”、和“someAddress.call.value()()”之间的差别。 134 | 135 | - `x.transfer(y)`和`if (!x.send(y)) throw;`是等价的。send是transfer的底层实现,建议尽可能直接使用transfer。 136 | - `someAddress.send()`和`someAddress.transfer()` 能保证[可重入](#reentrancy) **安全** 。 137 |    尽管这些外部智能合约的函数可以被触发执行,但补贴给外部智能合约的2,300 gas,意味着仅仅只够记录一个event到日志中。 138 | - `someAddress.call.value()()` 将会发送指定数量的Ether并且触发对应代码的执行。被调用的外部智能合约代码将享有所有剩余的gas,通过这种方式转账是很容易有可重入漏洞的,非常 **不安全**。 139 | 140 | 使用`send()` 或`transfer()` 可以通过制定gas值来预防可重入, 但是这样做可能会导致在和合约调用fallback函数时出现问题,由于gas可能不足,而合约的fallback函数执行至少需要2,300 gas消耗。 141 | 142 | 一种被称为[*push* 和*pull*](#favor-pull-over-push-payments)的 机制试图来平衡两者, 在 *push* 部分使用`send()` 或`transfer()`,在*pull* 部分使用`call.value()()`。(*译者注:在需要对外未知地址转账Ether时使用`send()` 或`transfer()`,已知明确内部无恶意代码的地址转账Ether使用`call.value()()`) 143 | 144 | 需要注意的是使用`send()` 或`transfer()` 进行转账并不能保证该智能合约本身重入安全,它仅仅只保证了这次转账操作时重入安全的。 145 | 146 | 147 | 148 | #### 处理外部调用错误 149 | 150 | Solidity提供了一系列在raw address上执行操作的底层方法,比如: `address.call()`,`address.callcode()`, `address.delegatecall()`和`address.send`。这些底层方法不会抛出异常(throw),只是会在遇到错误时返回false。另一方面, *contract calls* (比如,`ExternalContract.doSomething()`))会自动传递异常,(比如,`doSomething()`抛出异常,那么`ExternalContract.doSomething()` 同样会进行`throw`) )。 151 | 152 | 如果你选择使用底层方法,一定要检查返回值来对可能的错误进行处理。 153 | 154 | ```sh 155 | // bad 156 | someAddress.send(55); 157 | someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result 158 | 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 159 | 160 | // good 161 | if(!someAddress.send(55)) { 162 | // Some failure code 163 | } 164 | 165 | ExternalContract(someAddress).deposit.value(100); 166 | ``` 167 | 168 | 169 | #### 不要假设你知道外部调用的控制流程 170 | 171 | 无论是使用**raw calls** 或是**contract calls**,如果这个`ExternalContract`是不受信任的都应该假设存在恶意代码。即使`ExternalContract`不包含恶意代码,但它所调用的其他合约代码可能会包含恶意代码。一个具体的危险例子便是恶意代码可能会劫持控制流程导致竞态。(浏览[Race Conditions](https://github.com/ConsenSys/smart-contract-best-practices/#race-conditions)获取更多关于这个问题的讨论) 172 | 173 | 174 | 175 | #### 对于外部合约优先使用*pull* 而不是*push* 176 | 177 | 外部调用可能会有意或无意的失败。为了最小化这些外部调用失败带来的损失,通常好的做法是将外部调用函数与其余代码隔离,最终是由收款发起方负责发起调用该函数。这种做法对付款操作尤为重要,比如让用户自己撤回资产而不是直接发送给他们。(*译者注:事先设置需要付给某一方的资产的值,表明接收方可以从当前账户撤回资金的额度,然后由接收方调用当前合约提现函数完成转账*)。(这种方法同时也避免了造成 [gas limit相关问题](https://github.com/ConsenSys/smart-contract-best-practices/#dos-with-block-gas-limit)。) 178 | 179 | ```sh 180 | // bad 181 | contract auction { 182 | address highestBidder; 183 | uint highestBid; 184 | 185 | function bid() payable { 186 | if (msg.value < highestBid) throw; 187 | 188 | if (highestBidder != 0) { 189 | if (!highestBidder.send(highestBid)) { // if this call consistently fails, no one else can bid 190 | throw; 191 | } 192 | } 193 | 194 | highestBidder = msg.sender; 195 | highestBid = msg.value; 196 | } 197 | } 198 | 199 | // good 200 | contract auction { 201 | address highestBidder; 202 | uint highestBid; 203 | mapping(address => uint) refunds; 204 | 205 | function bid() payable external { 206 | if (msg.value < highestBid) throw; 207 | 208 | if (highestBidder != 0) { 209 | refunds[highestBidder] += highestBid; // record the refund that this user can claim 210 | } 211 | 212 | highestBidder = msg.sender; 213 | highestBid = msg.value; 214 | } 215 | 216 | function withdrawRefund() external { 217 | uint refund = refunds[msg.sender]; 218 | refunds[msg.sender] = 0; 219 | if (!msg.sender.send(refund)) { 220 | refunds[msg.sender] = refund; // reverting state because send failed 221 | } 222 | } 223 | } 224 | ``` 225 | 226 | 227 | #### 标记不受信任的合约 228 | 229 | 当你自己的函数调用外部合约时,你的变量、方法、合约接口命名应该表明和他们可能是不安全的。 230 | 231 | ```sh 232 | // bad 233 | Bank.withdraw(100); // Unclear whether trusted or untrusted 234 | 235 | function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe 236 | Bank.withdraw(amount); 237 | } 238 | 239 | // good 240 | UntrustedBank.withdraw(100); // untrusted external call 241 | TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp 242 | 243 | function makeUntrustedWithdrawal(uint amount) { 244 | UntrustedBank.withdraw(amount); 245 | } 246 | ``` 247 | 248 | ### 使用`assert()`强制不变性 249 | 250 | 当断言条件不满足时将触发断言保护 -- 比如不变的属性发生了变化。举个例子,代币在以太坊上的发行比例,在代币的发行合约里可以通过这种方式得到解决。断言保护经常需要和其他技术组合使用,比如当断言被触发时先挂起合约然后升级。(否则将一直触发断言,你将陷入僵局) 251 | 252 | 例如: 253 | 254 | ``` 255 | contract Token { 256 | mapping(address => uint) public balanceOf; 257 | uint public totalSupply; 258 | 259 | function deposit() public payable { 260 | balanceOf[msg.sender] += msg.value; 261 | totalSupply += msg.value; 262 | assert(this.balance >= totalSupply); 263 | } 264 | } 265 | ``` 266 | 注意断言保护 **不是** 严格意义的余额检测, 因为智能合约可以不通过`deposit()` 函数被 [强制发送Ether](#ether-forcibly-sent)! 267 | 268 | ### 正确使用`assert()`和`require()` 269 | 270 | 在Solidity 0.4.10 中`assert()`和`require()`被加入。`require(condition)`被用来验证用户的输入,如果条件不满足便会抛出异常,应当使用它验证所有用户的输入。 `assert(condition)` 在条件不满足也会抛出异常,但是最好只用于固定变量:内部错误或你的智能合约陷入无效的状态。遵循这些范例,使用分析工具来验证永远不会执行这些无效操作码:意味着代码中不存在任何不变量,并且代码已经正式验证。 271 | 272 | 273 | 274 | ### 小心整数除法的四舍五入 275 | 276 | 所有整数除数都会四舍五入到最接近的整数。 如果您需要更高精度,请考虑使用乘数,或存储分子和分母。 277 | 278 | (将来Solidity会有一个fixed-point类型来让这一切变得容易。) 279 | 280 | ```sh 281 | // bad 282 | uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer 283 | 284 | // good 285 | uint multiplier = 10; 286 | uint x = (5 * multiplier) / 2; 287 | 288 | uint numerator = 5; 289 | uint denominator = 2; 290 | ``` 291 | 292 | 293 | 294 | ### 记住Ether可以被强制发送到账户 295 | 296 | 谨慎编写用来检查账户余额的不变量。 297 | 298 | 攻击者可以强制发送wei到任何账户,而且这是不能被阻止的(即使让fallback函数`throw`也不行) 299 | 300 | 攻击者可以仅仅使用1 wei来创建一个合约,然后调用`selfdestruct(victimAddress)`。在`victimAddress`中没有代码被执行,所以这是不能被阻止的。 301 | 302 | ### 不要假设合约创建时余额为零 303 | 304 | 攻击者可以在合约创建之前向合约的地址发送wei。合约不能假设它的初始状态包含的余额为零。浏览[issue 61](https://github.com/ConsenSys/smart-contract-best-practices/issues/61) 获取更多信息。 305 | 306 | ### 记住链上的数据是公开的 307 | 308 | 许多应用需要提交的数据是私有的,直到某个时间点才能工作。游戏(比如,链上游戏rock-paper-scissors(石头剪刀布))和拍卖机(比如,sealed-bid second-price auctions)是两个典型的例子。如果你的应用存在隐私保护问题,一定要避免过早发布用户信息。 309 | 310 | 例如: 311 | 312 | * 在游戏石头剪刀布中,需要参与游戏的双方提交他们“行动计划”的hash值,然后需要双方随后提交他们的行动计划;如果双方的“行动计划”和先前提交的hash值对不上则抛出异常。 313 | * 在拍卖中,要求玩家在初始阶段提交其所出价格的hash值(以及超过其出价的保证金),然后在第二阶段提交他们所出价格的资金。 314 | * 当开发一个依赖随机数生成器的应用时,正确的顺序应当是(1)玩家提交行动计划,(2)生成随机数,(3)玩家支付。产生随机数是一个值得研究的领域;当前最优的解决方案包括比特币区块头(通过http://btcrelay.org验证),hash-commit-reveal方案(比如,一方产生number后,将其散列值提交作为对这个number的“提交”,然后在随后再暴露这个number本身)和 [RANDAO](http://github.com/randao/randao)。 315 | * 如果你正在实现频繁的批量拍卖,那么hash-commit机制也是个不错的选择。 316 | 317 | ### 权衡Abstract合约和Interfaces 318 | 319 | Interfaces和Abstract合约都是用来使智能合约能更好的被定制和重用。Interfaces是在Solidity 0.4.11中被引入的,和Abstract合约很像但是不能定义方法只能申明。Interfaces存在一些限制比如不能够访问storage或者从其他Interfaces那继承,通常这些使Abstract合约更实用。尽管如此,Interfaces在实现智能合约之前的设计智能合约阶段仍然有很大用处。另外,需要注意的是如果一个智能合约从另一个Abstract合约继承而来那么它必须实现所有Abstract合约内的申明并未实现的函数,否则它也会成为一个Abstract合约。 320 | 321 | ### 在双方或多方参与的智能合约中,参与者可能会“脱机离线”后不再返回 322 | 323 | 不要让退款和索赔流程依赖于参与方执行的某个特定动作而没有其他途径来获取资金。比如,在石头剪刀布游戏中,一个常见的错误是在两个玩家提交他们的行动计划之前不要付钱。然而一个恶意玩家可以通过一直不提交它的行动计划来使对方蒙受损失 -- 事实上,如果玩家看到其他玩家泄露的行动计划然后决定他是否会损失(译者注:发现自己输了),那么他完全有理由不再提交他自己的行动计划。这些问题也同样会出现在通道结算。当这些情形出现导致问题后:(1)提供一种规避非参与者和参与者的方式,可能通过设置时间限制,和(2)考虑为参与者提供额外的经济激励,以便在他们应该这样做的所有情况下仍然提交信息。 324 | 325 | 326 | 327 | ### 使Fallback函数尽量简单 328 | 329 | [Fallback函数](http://solidity.readthedocs.io/en/latest/contracts.html#fallback-function)在合约执行消息发送没有携带参数(或当没有匹配的函数可供调用)时将会被调用,而且当调用 `.send()` or `.transfer()`时,只会有2,300 gas 用于失败后fallback函数的执行(*译者注:合约收到Ether也会触发fallback函数执行*)。如果你希望能够监听`.send()`或`.transfer()`接收到Ether,则可以在fallback函数中使用event(译者注:让客户端监听相应事件做相应处理)。谨慎编写fallback函数以免gas不够用。 330 | 331 | ```sh 332 | // bad 333 | function() payable { balances[msg.sender] += msg.value; } 334 | 335 | // good 336 | function deposit() payable external { balances[msg.sender] += msg.value; } 337 | 338 | function() payable { LogDepositReceived(msg.sender); } 339 | ``` 340 | 341 | 342 | 343 | ### 明确标明函数和状态变量的可见性 344 | 345 | 明确标明函数和状态变量的可见性。函数可以声明为 `external`,`public`, `internal` 或 `private`。 分清楚它们之间的差异, 例如`external` 可能已够用而不是使用 `public`。对于状态变量,`external`是不可能的。明确标注可见性将使得更容易避免关于谁可以调用该函数或访问变量的错误假设。 346 | 347 | ```sh 348 | // bad 349 | uint x; // the default is private for state variables, but it should be made explicit 350 | function buy() { // the default is public 351 | // public code 352 | } 353 | 354 | // good 355 | uint private y; 356 | function buy() external { 357 | // only callable externally 358 | } 359 | 360 | function utility() public { 361 | // callable externally, as well as internally: changing this code requires thinking about both cases. 362 | } 363 | 364 | function internalAction() internal { 365 | // internal code 366 | } 367 | ``` 368 | 369 | 370 | 371 | ### 将程序锁定到特定的编译器版本 372 | 373 | 智能合约应该应该使用和它们测试时使用最多的编译器相同的版本来部署。锁定编译器版本有助于确保合约不会被用于最新的可能还有bug未被发现的编译器去部署。智能合约也可能会由他人部署,而pragma标明了合约作者希望使用哪个版本的编译器来部署合约。 374 | 375 | ```sh 376 | // bad 377 | pragma solidity ^0.4.4; 378 | 379 | 380 | // good 381 | pragma solidity 0.4.4; 382 | ``` 383 | 384 | 385 | (*译者注:这当然也会付出兼容性的代价*) 386 | 387 | ### 小心分母为零 (Solidity < 0.4) 388 | 389 | 早于0.4版本, 当一个数尝试除以零时,Solidity [返回zero](https://github.com/ethereum/solidity/issues/670) 并没有 `throw` 一个异常。确保你使用的Solidity版本至少为 0.4。 390 | 391 | 392 | 393 | ### 区分函数和事件 394 | 395 | 为了防止函数和事件(Event)产生混淆,命名一个事件使用大写并加入前缀(我们建议**LOG**)。对于函数, 始终以小写字母开头,构造函数除外。 396 | 397 | ```sh 398 | // bad 399 | event Transfer() {} 400 | function transfer() {} 401 | 402 | // good 403 | event LogTransfer() {} 404 | function transfer() external {} 405 | ``` 406 | 407 | 408 | 409 | ### 使用Solidity更新的构造器 410 | 411 | 更合适的构造器/别名,如`selfdestruct`(旧版本为'suicide`)和`keccak256`(旧版本为`sha3`)。 像`require(msg.sender.send(1 ether))``的模式也可以简化为使用`transfer()`,如`msg.sender.transfer(1 ether)`。 412 | 413 | 414 | 415 | ## 已知的攻击 416 | 417 | 418 | 419 | ### 竞态\* 420 | 421 | 调用外部契约的主要危险之一是它们可以接管控制流,并对调用函数意料之外的数据进行更改。 这类bug有多种形式,导致DAO崩溃的两个主要错误都是这种错误。 422 | 423 | 424 | 425 | #### 重入 426 | 427 | 这个版本的bug被注意到是其可以在第一次调用这个函数完成之前被多次重复调用。对这个函数不断的调用可能会造成极大的破坏。 428 | 429 | ```sh 430 | // INSECURE 431 | mapping (address => uint) private userBalances; 432 | 433 | function withdrawBalance() public { 434 | uint amountToWithdraw = userBalances[msg.sender]; 435 | if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call withdrawBalance again 436 | userBalances[msg.sender] = 0; 437 | } 438 | ``` 439 | (*译者注:使用msg.sender.call.value()())传递给fallback函数可用的气是当前剩余的所有气,在这里,假如从你账户执行提现操作的恶意合约的fallback函数内递归调用你的withdrawBalance()便可以从你的账户转走更多的币。*) 440 | 441 | 可以看到当调msg.sender.call.value()()时,并没有将userBalances[msg.sender] 清零,于是在这之前可以成功递归调用很多次withdrawBalance()函数。 一个非常相像的bug便是出现在针对 DAO 的攻击。 442 | 443 | 在给出来的例子中,最好的方法是 [ 使用 `send()` 而不是`call.value()()`](https://github.com/ConsenSys/smart-contract-best-practices#send-vs-call-value)。这将避免多余的代码被执行。 444 | 445 | 然而,如果你没法完全移除外部调用,另一个简单的方法来阻止这个攻击是确保你在完成你所有内部工作之前不要进行外部调用: 446 | 447 | ```sh 448 | mapping (address => uint) private userBalances; 449 | 450 | function withdrawBalance() public { 451 | uint amountToWithdraw = userBalances[msg.sender]; 452 | userBalances[msg.sender] = 0; 453 | if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // The user's balance is already 0, so future invocations won't withdraw anything 454 | } 455 | ``` 456 | 457 | 注意如果你有另一个函数也调用了 `withdrawBalance()`, 那么这里潜在的存在上面的攻击,所以你必须认识到任何调用了不受信任的合约代码的合约也是不受信任的。继续浏览下面的相关潜在威胁解决办法的讨论。 458 | 459 | #### 跨函数竞态 460 | 461 | 攻击者也可以使用两个共享状态变量的不同的函数来进行类似攻击。 462 | 463 | ```sh 464 | // INSECURE 465 | mapping (address => uint) private userBalances; 466 | 467 | function transfer(address to, uint amount) { 468 | if (userBalances[msg.sender] >= amount) { 469 | userBalances[to] += amount; 470 | userBalances[msg.sender] -= amount; 471 | } 472 | } 473 | 474 | function withdrawBalance() public { 475 | uint amountToWithdraw = userBalances[msg.sender]; 476 | if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call transfer() 477 | userBalances[msg.sender] = 0; 478 | } 479 | ``` 480 | 着这个例子中,攻击者在他们外部调用`withdrawBalance`函数时调用`transfer()`,如果这个时候`withdrawBalance`还没有执行到`userBalances[msg.sender] = 0;`这里,那么他们的余额就没有被清零,那么他们就能够调用`transfer()`转走代币尽管他们其实已经收到了代币。这个弱点也可以被用到对DAO的攻击。 481 | 482 | 同样的解决办法也会管用,在执行转账操作之前先清零。也要注意在这个例子中所有函数都是在同一个合约内。然而,如果这些合约共享了状态,同样的bug也可以发生在跨合约调用中。 483 | 484 | #### 竞态解决办法中的陷阱 485 | 486 | 由于竞态既可以发生在跨函数调用,也可以发生在跨合约调用,任何只是避免重入的解决办法都是不够的。 487 | 488 | 作为替代,我们建议首先应该完成所有内部的工作然后再执行外部调用。这个规则可以避免竞态发生。然而,你不仅应该避免过早调用外部函数而且应该避免调用那些也调用了外部函数的外部函数。例如,下面的这段代码是不安全的: 489 | 490 | ```sh 491 | // INSECURE 492 | mapping (address => uint) private userBalances; 493 | mapping (address => bool) private claimedBonus; 494 | mapping (address => uint) private rewardsForA; 495 | 496 | function withdraw(address recipient) public { 497 | uint amountToWithdraw = userBalances[recipient]; 498 | rewardsForA[recipient] = 0; 499 | if (!(recipient.call.value(amountToWithdraw)())) { throw; } 500 | } 501 | 502 | function getFirstWithdrawalBonus(address recipient) public { 503 | if (claimedBonus[recipient]) { throw; } // Each recipient should only be able to claim the bonus once 504 | 505 | rewardsForA[recipient] += 100; 506 | withdraw(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again. 507 | claimedBonus[recipient] = true; 508 | } 509 | ``` 510 | 511 | 尽管`getFirstWithdrawalBonus()` 没有直接调用外部合约,但是它调用的`withdraw()` 却会导致竞态的产生。在这里你不应该认为`withdraw()`是受信任的。 512 | 513 | ```sh 514 | mapping (address => uint) private userBalances; 515 | mapping (address => bool) private claimedBonus; 516 | mapping (address => uint) private rewardsForA; 517 | 518 | function untrustedWithdraw(address recipient) public { 519 | uint amountToWithdraw = userBalances[recipient]; 520 | rewardsForA[recipient] = 0; 521 | if (!(recipient.call.value(amountToWithdraw)())) { throw; } 522 | } 523 | 524 | function untrustedGetFirstWithdrawalBonus(address recipient) public { 525 | if (claimedBonus[recipient]) { throw; } // Each recipient should only be able to claim the bonus once 526 | 527 | claimedBonus[recipient] = true; 528 | rewardsForA[recipient] += 100; 529 | untrustedWithdraw(recipient); // claimedBonus has been set to true, so reentry is impossible 530 | } 531 | ``` 532 | 533 | 除了修复bug让重入不可能成功,[不受信任的函数也已经被标记出来](https://github.com/ConsenSys/smart-contract-best-practices#mark-untrusted-contracts) 。同样的情景: `untrustedGetFirstWithdrawalBonus()` 调用`untrustedWithdraw()`, 而后者调用了外部合约,因此在这里`untrustedGetFirstWithdrawalBonus()` 是不安全的。 534 | 535 | 另一个经常被提及的解决办法是(*译者注:像传统多线程编程中一样*)使用[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)。它会"lock" 当前状态,只有锁的当前拥有者能够更改当前状态。一个简单的例子如下: 536 | 537 | ```sh 538 | // Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state 539 | mapping (address => uint) private balances; 540 | bool private lockBalances; 541 | 542 | function deposit() payable public returns (bool) { 543 | if (!lockBalances) { 544 | lockBalances = true; 545 | balances[msg.sender] += msg.value; 546 | lockBalances = false; 547 | return true; 548 | } 549 | throw; 550 | } 551 | 552 | function withdraw(uint amount) payable public returns (bool) { 553 | if (!lockBalances && amount > 0 && balances[msg.sender] >= amount) { 554 | lockBalances = true; 555 | 556 | if (msg.sender.call(amount)()) { // Normally insecure, but the mutex saves it 557 | balances[msg.sender] -= amount; 558 | } 559 | 560 | lockBalances = false; 561 | return true; 562 | } 563 | 564 | throw; 565 | } 566 | ``` 567 | 568 | 如果用户试图在第一次调用结束前第二次调用 `withdraw()`,将会被锁住。 这看上去很有效果,但当你使用多个合约互相交互时问题变得严峻了。 下面是一段不安全的代码: 569 | 570 | ``` 571 | // INSECURE 572 | contract StateHolder { 573 | uint private n; 574 | address private lockHolder; 575 | 576 | function getLock() { 577 | if (lockHolder != 0) { throw; } 578 | lockHolder = msg.sender; 579 | } 580 | 581 | function releaseLock() { 582 | lockHolder = 0; 583 | } 584 | 585 | function set(uint newState) { 586 | if (msg.sender != lockHolder) { throw; } 587 | n = newState; 588 | } 589 | } 590 | ``` 591 | 592 | 攻击者可以只调用`getLock()`,然后就不再调用 `releaseLock()`。如果他们真这样做,那么这个合约将会被永久锁住,任何接下来的操作都不会发生了。如果你使用mutexs来避免竞态,那么一定要确保没有地方能够打断锁的进程或绝不释放锁。(这里还有一个潜在的威胁,比如死锁和活锁。在你决定使用锁之前最好大量阅读相关文献(*译者注:这是真的,传统的在多线程环境下对锁的使用一直是个容易犯错的地方*)) 593 | 594 | 595 | 596 |
* 有些人可能会发反对使用该术语 竞态,因为以太坊并没有真正意思上实现并行执行。然而在逻辑上依然存在对资源的竞争,同样的陷阱和潜在的解决方案。
597 | 598 | 599 | 600 | ### 交易顺序依赖(TOD) / 前面的先运行 601 | 602 | 以上是涉及攻击者在**单个交易**内执行恶意代码产生竞态的示例。接下来演示在区块链本身运作原理导致的竞态:(同一个block内的)交易顺序很容易受到操纵。 603 | 604 | 由于交易在短暂的时间内会先存放到mempool中,所以在矿工将其打包进block之前,是可以知道会发生什么动作的。这对于一个去中心化的市场来说是麻烦的,因为可以查看到代币的交易信息,并且可以在它被打包进block之前改变交易顺序。避免这一点很困难,因为它归结为具体的合同本身。例如,在市场上,最好实施批量拍卖(这也可以防止高频交易问题)。 另一种使用预提交方案的方法(“我稍后会提供详细信息”)。 605 | 606 | 607 | 608 | ### 时间戳依赖 609 | 610 | 请注意,块的时间戳可以由矿工操纵,并且应考虑时间戳的所有直接和间接使用。 **区块数量**和**平均出块时间**可用于估计时间,但这不是区块时间在未来可能改变(例如Casper期望的更改)的证明。 611 | 612 | ```sh 613 | uint someVariable = now + 1; 614 | 615 | if (now % 2 == 0) { // the now can be manipulated by the miner 616 | 617 | } 618 | 619 | if ((someVariable - 100) % 2 == 0) { // someVariable can be manipulated by the miner 620 | 621 | } 622 | ``` 623 | 624 | 625 | 626 | ### 整数上溢和下溢 627 | 628 | 这里大概有 [20关于上溢和下溢的例子](https://github.com/ethereum/solidity/issues/796#issuecomment-253578925)。 629 | 630 | 考虑如下这个简单的转账操作: 631 | 632 | ```sh 633 | mapping (address => uint256) public balanceOf; 634 | 635 | // INSECURE 636 | function transfer(address _to, uint256 _value) { 637 | /* Check if sender has balance */ 638 | if (balanceOf[msg.sender] < _value) 639 | throw; 640 | /* Add and subtract new balances */ 641 | balanceOf[msg.sender] -= _value; 642 | balanceOf[_to] += _value; 643 | } 644 | 645 | // SECURE 646 | function transfer(address _to, uint256 _value) { 647 | /* Check if sender has balance and for overflows */ 648 | if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to]) 649 | throw; 650 | 651 | /* Add and subtract new balances */ 652 | balanceOf[msg.sender] -= _value; 653 | balanceOf[_to] += _value; 654 | } 655 | ``` 656 | 657 | 如果余额到达**uint**的最大值(2^256),便又会变为0。应当检查这里。溢出是否与之相关取决于具体的实施方式。想想uint值是否有机会变得这么大或和谁会改变它的值。如果任何用户都有权利更改uint的值,那么它将更容易受到攻击。如果只有管理员能够改变它的值,那么它可能是安全的,因为没有别的办法可以跨越这个限制。 658 | 659 | 对于下溢同样的道理。如果一个uint别改变后小于0,那么将会导致它下溢并且被设置成为最大值(2^256)。 660 | 661 | 对于较小数字的类型比如uint8、uint16、uint24等也要小心:他们更加容易达到最大值。 662 | 663 | 664 | 665 | ### 通过(Unexpected) Throw发动DoS 666 | 667 | 考虑如下简单的智能合约: 668 | 669 | ```sh 670 | // INSECURE 671 | contract Auction { 672 | address currentLeader; 673 | uint highestBid; 674 | 675 | function bid() payable { 676 | if (msg.value <= highestBid) { throw; } 677 | 678 | if (!currentLeader.send(highestBid)) { throw; } // Refund the old leader, and throw if it fails 679 | 680 | currentLeader = msg.sender; 681 | highestBid = msg.value; 682 | } 683 | } 684 | ``` 685 | 当有更高竞价时,它将试图退款给曾经最高竞价人,如果退款失败则会抛出异常。这意味着,恶意投标人可以成为当前最高竞价人,同时确保对其地址的任何退款**始终**失败。这样就可以阻止任何人调用“bid()”函数,使自己永远保持领先。建议向之前所说的那样建立[基于pull的支付系统](https://github.com/ConsenSys/smart-contract-best-practices/#favor-pull-over-push-payments) 。 686 | 687 | 另一个例子是合约可能通过数组迭代来向用户支付(例如,众筹合约中的支持者)时。 通常要确保每次付款都成功。 如果没有,应该抛出异常。 问题是,如果其中一个支付失败,您将恢复整个支付系统,这意味着该循环将永远不会完成。 因为一个地址没有转账成功导致其他人都没得到报酬。 688 | 689 | ```sh 690 | address[] private refundAddresses; 691 | mapping (address => uint) public refunds; 692 | 693 | // bad 694 | function refundAll() public { 695 | for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated 696 | if(refundAddresses[x].send(refunds[refundAddresses[x]])) { 697 | throw; // doubly bad, now a single failure on send will hold up all funds 698 | } 699 | } 700 | } 701 | ``` 702 | 703 | 再一次强调,同样的解决办法: [优先使用pull 而不是push支付系统](#favor-pull-over-push-payments)。 704 | 705 | 706 | 707 | ### 通过区块Gas Limit发动DoS 708 | 709 | 在先前的例子中你可能已经注意到另一个问题:一次性向所有人转账,很可能会导致达到以太坊区块gas limit的上限。以太坊规定了每一个区块所能花费的gas limit,如果超过你的交易便会失败。 710 | 711 | 即使没有故意的攻击,这也可能导致问题。然而,最为糟糕的是如果gas的花费被攻击者操控。在先前的例子中,如果攻击者增加一部分收款名单,并设置每一个收款地址都接收少量的退款。这样一来,更多的gas将会被花费从而导致达到区块gas limit的上限,整个转账的操作也会以失败告终。 712 | 713 | 又一次证明了 [优先使用pull 而不是push支付系统](#favor-pull-over-push-payments)。 714 | 715 | 如果你实在必须通过遍历一个变长数组来进行转账,最好估计完成它们大概需要多少个区块以及多少笔交易。然后你还必须能够追踪得到当前进行到哪以便当操作失败时从那里开始恢复,举个例子: 716 | 717 | ```sh 718 | struct Payee { 719 | address addr; 720 | uint256 value; 721 | } 722 | Payee payees[]; 723 | uint256 nextPayeeIndex; 724 | 725 | function payOut() { 726 | uint256 i = nextPayeeIndex; 727 | while (i < payees.length && msg.gas > 200000) { 728 | payees[i].addr.send(payees[i].value); 729 | i++; 730 | } 731 | nextPayeeIndex = i; 732 | } 733 | ``` 734 | 735 | 如上所示,你必须确保在下一次执行`payOut()`之前另一些正在执行的交易不会发生任何错误。如果必须,请使用上面这种方式来处理。 736 | 737 | 738 | 739 | ### ~~Call Depth攻击~~ 740 | 741 | 由于[EIP 150](https://github.com/ethereum/EIPs/issues/150) 进行的硬分叉,Call Depth攻击已经无法实施\* (由于以太坊限制了Call Depth最大为1024,确保了在达到最大深度之前gas都能被正确使用) 742 | 743 | 744 | 745 | ## 软件工程开发技巧 746 | 747 | 正如我们先前在[基本理念](#general-philosophy) 章节所讨论的那样,避免自己遭受已知的攻击是不够的。由于在链上遭受攻击损失是巨大的,因此你还必须改变你编写软件的方式来抵御各种攻击。 748 | 749 | 我们倡导“时刻准备失败",提前知道你的代码是否安全是不可能的。然而,我们可以允许合约以可预知的方式失败,然后最小化失败带来的损失。本章将带你了解如何为可预知的失败做准备。 750 | 751 | 注意:当你向你的系统添加新的组件时总是伴随着风险的。一个不良设计本身会成为漏洞-一些精心设计的组件在交互过程中同样会出现漏洞。仔细考虑你在合约里使用的每一项技术,以及如何将它们整合共同创建一个稳定可靠的系统。 752 | 753 | ### 升级有问题的合约 754 | 755 | 如果代码中发现了错误或者需要对某些部分做改进都需要更改代码。在以太坊上发现一个错误却没有办法处理他们是太多意义的。 756 | 757 | 关于如何在以太坊上设计一个合约升级系统是一个正处于积极研究的领域,在这篇文章当中我们没法覆盖所有复杂的领域。然而,这里有两个通用的基本方法。最简单的是专门设计一个注册合约,在注册合约中保存最新版合约的地址。对于合约使用者来说更能实现无缝衔接的方法是设计一个合约,使用它转发调用请求和数据到最新版的合约。 758 | 759 | 无论采用何种技术,组件之间都要进行模块化和良好的分离,由此代码的更改才不会破坏原有的功能,造成孤儿数据,或者带来巨大的成本。 尤其是将复杂的逻辑与数据存储分开,这样你在使用更改后的功能时不必重新创建所有数据。 760 | 761 | 当需要多方参与决定升级代码的方式也是至关重要的。根据你的合约,升级代码可能会需要通过单个或多个受信任方参与投票决定。如果这个过程会持续很长时间,你就必须要考虑是否要换成一种更加高效的方式以防止遭受到攻击,例如[紧急停止或断路器](#circuit-breakers-pause-contract-functionality)。 762 | 763 | **Example 1:使用注册合约存储合约的最新版本** 764 | 765 | 在这个例子中,调用没有被转发,因此用户必须每次在交互之前都先获取最新的合约地址。 766 | 767 | ```sh 768 | contract SomeRegister { 769 | address backendContract; 770 | address[] previousBackends; 771 | address owner; 772 | 773 | function SomeRegister() { 774 | owner = msg.sender; 775 | } 776 | 777 | modifier onlyOwner() { 778 | if (msg.sender != owner) { 779 | throw; 780 | } 781 | _; 782 | } 783 | 784 | function changeBackend(address newBackend) public 785 | onlyOwner() 786 | returns (bool) 787 | { 788 | if(newBackend != backendContract) { 789 | previousBackends.push(backendContract); 790 | backendContract = newBackend; 791 | return true; 792 | } 793 | 794 | return false; 795 | } 796 | } 797 | ``` 798 | 799 | 这种方法有两个主要的缺点: 800 | 801 | 1、用户必须始终查找当前合约地址,否则任何未执行此操作的人都可能会使用旧版本的合约 802 | 2、在你替换了合约后你需要仔细考虑如何处理原合约中的数据 803 | 804 | 另外一种方法是设计一个用来转发调用请求和数据到最新版的合约: 805 | 806 | **例2: [使用`DELEGATECALL`](http://ethereum.stackexchange.com/questions/2404/upgradeable-contracts) 转发数据和调用** 807 | 808 | ``` 809 | contract Relay { 810 | address public currentVersion; 811 | address public owner; 812 | 813 | modifier onlyOwner() { 814 | if (msg.sender != owner) { 815 | throw; 816 | } 817 | _; 818 | } 819 | 820 | function Relay(address initAddr) { 821 | currentVersion = initAddr; 822 | owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner 823 | } 824 | 825 | function changeContract(address newVersion) public 826 | onlyOwner() 827 | { 828 | currentVersion = newVersion; 829 | } 830 | 831 | function() { 832 | if(!currentVersion.delegatecall(msg.data)) throw; 833 | } 834 | } 835 | ``` 836 | 837 | 这种方法避免了先前的问题,但也有自己的问题。它使得你必须在合约里小心的存储数据。如果新的合约和先前的合约有不同的存储层,你的数据可能会被破坏。另外,这个例子中的模式没法从函数里返回值,只负责转发它们,由此限制了它的适用性。(这里有一个[更复杂的实现](https://github.com/ownage-ltd/ether-router) 想通过内联汇编和返回大小的注册表来解决这个问题) 838 | 839 | 无论你的方法如何,重要的是要有一些方法来升级你的合约,否则当被发现不可避免的错误时合约将没法使用。 840 | 841 | ### 断路器(暂停合约功能) 842 | 843 | 由于断路器在满足一定条件时将会停止执行,如果发现错误时可以使用断路器。例如,如果发现错误,大多数操作可能会在合约中被挂起,这是唯一的操作就是撤销。你可以授权给任何你受信任的一方,提供给他们触发断路器的能力,或者设计一个在满足某些条件时自动触发某个断路器的程序规则。 844 | 845 | 例如: 846 | 847 | ``` 848 | bool private stopped = false; 849 | address private owner; 850 | 851 | modifier isAdmin() { 852 | if(msg.sender != owner) { 853 | throw; 854 | } 855 | _; 856 | } 857 | 858 | function toggleContractActive() isAdmin public 859 | { 860 | // You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users 861 | stopped = !stopped; 862 | } 863 | 864 | modifier stopInEmergency { if (!stopped) _; } 865 | modifier onlyInEmergency { if (stopped) _; } 866 | 867 | function deposit() stopInEmergency public 868 | { 869 | // some code 870 | } 871 | 872 | function withdraw() onlyInEmergency public 873 | { 874 | // some code 875 | } 876 | ``` 877 | 878 | ### 速度碰撞(延迟合约动作) 879 | 880 | 速度碰撞使动作变慢,所以如果发生了恶意操作便有时间恢复。例如,[The DAO](https://github.com/slockit/DAO/) 从发起分割DAO请求到真正执行动作需要27天。这样保证了资金在此期间被锁定在合约里,增加了系统的可恢复性。在DAO攻击事件中,虽然在速度碰撞给定的时间段内没有有效的措施可以采取,但结合我们其他的技术,它们是非常有效的。 881 | 882 | 例如: 883 | 884 | ``` 885 | struct RequestedWithdrawal { 886 | uint amount; 887 | uint time; 888 | } 889 | 890 | mapping (address => uint) private balances; 891 | mapping (address => RequestedWithdrawal) private requestedWithdrawals; 892 | uint constant withdrawalWaitPeriod = 28 days; // 4 weeks 893 | 894 | function requestWithdrawal() public { 895 | if (balances[msg.sender] > 0) { 896 | uint amountToWithdraw = balances[msg.sender]; 897 | balances[msg.sender] = 0; // for simplicity, we withdraw everything; 898 | // presumably, the deposit function prevents new deposits when withdrawals are in progress 899 | 900 | requestedWithdrawals[msg.sender] = RequestedWithdrawal({ 901 | amount: amountToWithdraw, 902 | time: now 903 | }); 904 | } 905 | } 906 | 907 | function withdraw() public { 908 | if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) { 909 | uint amountToWithdraw = requestedWithdrawals[msg.sender].amount; 910 | requestedWithdrawals[msg.sender].amount = 0; 911 | 912 | if(!msg.sender.send(amountToWithdraw)) { 913 | throw; 914 | } 915 | } 916 | } 917 | ``` 918 | 919 | ### 速率限制 920 | 921 | 速率限制暂停或需要批准进行实质性更改。 例如,只允许存款人在一段时间内提取总存款的一定数量或百分比(例如,1天内最多100个ether) - 该时间段内的额外提款可能会失败或需要某种特别批准。 或者将速率限制做在合约级别,合约期限内只能发出发送一定数量的代币。 922 | 923 | [浏览例程](https://gist.github.com/PeterBorah/110c331dca7d23236f80e69c83a9d58c#file-circuitbreaker-sol) 924 | 925 | 926 | 927 | ### 合约发布 928 | 929 | 在将大量资金放入合约之前,合约应当进行大量的长时间的测试。 930 | 931 | 至少应该: 932 | 933 | - 拥有100%测试覆盖率的完整测试套件(或接近它) 934 | - 在自己的testnet上部署 935 | - 在公共测试网上部署大量测试和错误奖励 936 | - 彻底的测试应该允许各种玩家与合约进行大规模互动 937 | - 在主网上部署beta版以限制风险总额 938 | 939 | ##### 自动弃用 940 | 941 | 在合约测试期间,你可以在一段时间后强制执行自动弃用以阻止任何操作继续进行。例如,alpha版本的合约工作几周,然后自动关闭所有除最终退出操作的操作。 942 | 943 | ``` 944 | modifier isActive() { 945 | if (block.number > SOME_BLOCK_NUMBER) { 946 | throw; 947 | } 948 | _; 949 | } 950 | 951 | function deposit() public 952 | isActive() { 953 | // some code 954 | } 955 | 956 | function withdraw() public { 957 | // some code 958 | } 959 | 960 | ``` 961 | 962 | ##### 限制每个用户/合约的Ether数量 963 | 964 | 在早期阶段,你可以限制任何用户(或整个合约)的Ether数量 - 以降低风险。 965 | 966 | 967 | 968 | 969 | ### Bug赏金计划 970 | 971 | 运行赏金计划的一些提示: 972 | 973 | - 决定赏金以哪一种代币分配(BTC和/或ETH) 974 | - 决定赏金奖励的预算总额 975 | - 从预算来看,确定三级奖励: 976 | - 你愿意发放的最小奖励 977 | - 通常可发放的最高奖励 978 | - 设置额外的限额以避免非常严重的漏洞被发现 979 | - 确定赏金发放给谁(3是一个典型) 980 | - 核心开发人员应该是赏金评委之一 981 | - 当收到错误报告时,核心开发人员应该评估bug的严重性 982 | - 在这个阶段的工作应该在私有仓库进行,并且在Github上的issue板块提出问题 983 | - 如果这个bug需要被修复,开发人员应该在私有仓库编写测试用例来复现这个bug 984 | - 开发人员需要修复bug并编写额外测试代码进行测试确保所有测试都通过 985 | - 展示赏金猎人的修复;并将修复合并回公共仓库也是一种方式 986 | - 确定赏金猎人是否有任何关于修复的其他反馈 987 | - 赏金评委根据bug的*可能性*和*影响*来确定奖励的大小 988 | - 在整个过程中保持赏金猎人参与讨论,并确保赏金发放不会延迟 989 | 990 | 有关三级奖励的例子,参见 [Ethereum's Bounty Program](https://bounty.ethereum.org): 991 | 992 | > 奖励的价值将根据影响的严重程度而变化。 奖励轻微的“无害”错误从0.05 BTC开始。 主要错误,例如导致协商一致的问题,将获得最多5个BTC的奖励。 在非常严重的漏洞的情况下,更高的奖励是可能的(高达25 BTC)。 993 | 994 | ## 安全相关的文件和程序 995 | 996 | 当发布涉及大量资金或重要任务的合约时,必须包含适当的文档。有关安全性的文档包括: 997 | 998 | **规范和发布计划** 999 | 1000 | - 规格说明文档,图表,状态机,模型和其他文档,帮助审核人员和社区了解系统打算做什么。 1001 | - 许多bug从规格中就能找到,而且它们的修复成本最低。 1002 | - 发布计划所涉及到的参考[这里](#contract-rollout)列出的详细信息和完成日期。 1003 | 1004 | **状态** 1005 | 1006 | - 当前代码被部署到哪里 1007 | - 编译器版本,使用的标志以及用于验证部署的字节码的步骤与源代码匹配 1008 | - 将用于不同阶段的编译器版本和标志 1009 | - 部署代码的当前状态(包括未决问题,性能统计信息等) 1010 | 1011 | **已知问题** 1012 | 1013 | - 合约的主要风险 (例如, 你可能会丢掉所有的钱,黑客可能会通过投票支持某些结果) 1014 | - 所有已知的错误/限制 1015 | - 潜在的攻击和解决办法 1016 | - 潜在的利益冲突(例如,筹集的Ether将纳入自己的腰包,像Slock.it与DAO一样) 1017 | 1018 | **历史记录** 1019 | 1020 | - 测试(包括使用统计,发现的错误,测试时间) 1021 | - 已审核代码的人员(及其关键反馈) 1022 | 1023 | **程序** 1024 | 1025 | - 发现错误的行动计划(例如紧急情况选项,公众通知程序等) 1026 | - 如果出现问题,就可以降级程序(例如,资金拥有者在被攻击之前的剩余资金占现在剩余资金的比例) 1027 | - 负责任的披露政策(例如,在哪里报告发现的bug,任何bug赏金计划的规则) 1028 | - 在失败的情况下的追索权(例如,保险,罚款基金,无追索权) 1029 | 1030 | **联系信息** 1031 | 1032 | - 发现问题后和谁联系 1033 | - 程序员姓名和/或其他重要参与方的名称 1034 | - 可以询问问题的论坛/聊天室 1035 | 1036 | ## 安全工具 1037 | 1038 | - [Oyente](https://github.com/melonproject/oyente) - 根据[这篇文章](http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf)分析Ethereum代码以找到常见的漏洞。 1039 | - [solidity-coverage](https://github.com/sc-forks/solidity-coverage) - Solidity代码覆盖率测试 1040 | - [Solgraph](https://github.com/raineorshine/solgraph) - 生成一个DOT图,显示了Solidity合约的功能控制流程,并highlight了潜在的安全漏洞。 1041 | 1042 | ## Linters 1043 | 1044 | Linters通过约束代码风格和排版来提高代码质量,使代码更容易阅读和查看。 1045 | 1046 | - [Solium](https://github.com/duaraghav8/Solium) - 另一种Solidity linting。 1047 | - [Solint](https://github.com/weifund/solint) - 帮助你实施代码一致性约定来避免你合约中的错误的Solidity linting 1048 | - [Solcheck](https://github.com/federicobond/solcheck) - 用JS写的Solidity linter,(实现上)深受eslint的影响。 1049 | 1050 | ## 将来的改进 1051 | - **编辑器安全警告**:编辑器将很快能够实现醒常见的安全错误,而不仅仅是编译错误。 Solidity浏览器即将推出这些功能。 1052 | - **新的能够被编译成EVM字节码的函数式编程语言**: 像Solidity这种函数式编程语言相比面向过程编程语言能够保证功能的不变性和编译时间检查。通过确定性行为来减少出现错误的风险。(更多相关信息请参阅 [这里](https://plus.google.com/u/0/events/cmqejp6d43n5cqkdl3iu0582f4k), Curry-Howard 一致性和线性逻辑) 1053 | 1054 | 1055 | 1056 | ## 智能合约安全参考书目 1057 | 1058 | 很多包含代码,示例和见解的文档已经由社区编写完成。这里是其中的一些,你可以随意添加更多新的内容。 1059 | 1060 | ##### 来自以太坊核心开发人员 1061 | 1062 | - [How to Write Safe Smart Contracts](https://chriseth.github.io/notes/talks/safe_solidity) (Christian Reitwiessner) 1063 | - [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/) (Christian Reitwiessner) 1064 | - [Thinking about Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/) (Vitalik Buterin) 1065 | - [Solidity](http://solidity.readthedocs.io) 1066 | - [Solidity Security Considerations](http://solidity.readthedocs.io/en/latest/security-considerations.html) 1067 | 1068 | ##### 来自社区 1069 | 1070 | - http://forum.ethereum.org/discussion/1317/reentrant-contracts 1071 | - http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/ 1072 | - http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/ 1073 | - http://hackingdistributed.com/2016/06/22/smart-contract-escape-hatches/ 1074 | - http://martin.swende.se/blog/Devcon1-and-contract-security.html 1075 | - http://publications.lib.chalmers.se/records/fulltext/234939/234939.pdf 1076 | - http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour 1077 | - http://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful 1078 | - http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal 1079 | - https://blog.blockstack.org/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a 1080 | - https://blog.slock.it/deja-vu-dao-smart-contracts-audit-results-d26bc088e32e 1081 | - https://blog.vdice.io/wp-content/uploads/2016/11/vsliceaudit_v1.3.pdf 1082 | - https://eprint.iacr.org/2016/1007.pdf 1083 | - https://github.com/Bunjin/Rouleth/blob/master/Security.md 1084 | - https://github.com/LeastAuthority/ethereum-analyses 1085 | - https://medium.com/@ConsenSys/assert-guards-towards-automated-code-bounties-safe-smart-contract-coding-on-ethereum-8e74364b795c 1086 | - https://medium.com/@coriacetic/in-bits-we-trust-4e464b418f0b 1087 | - https://medium.com/@hrishiolickel/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007 1088 | - https://medium.com/@peterborah/we-need-fault-tolerant-smart-contracts-ec1b56596dbc 1089 | - https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab 1090 | - https://pdaian.com/blog/chasing-the-dao-attackers-wake 1091 | - http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf 1092 | 1093 | ## Reviewers 1094 | 1095 | The following people have reviewed this document (date and commit they reviewed in parentheses): 1096 | Bill Gleim (07/29/2016 3495fb5) 1097 | Bill Gleim (03/15/2017 0244f4e) 1098 | - 1099 | 1100 | ## License 1101 | 1102 | Licensed under [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) 1103 | 1104 | Licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/) 1105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Smart Contract Security Best Practices 2 | 3 | このドキュメントは、[Ethereum Smart Contract Security Best Practices](https://consensys.github.io/smart-contract-best-practices/)を日本語訳したものです。 原文より内容が古かったり、訳に誤りがあったりする可能性があります。必ず原文も併せてご確認ください。PR大歓迎です。 4 | 5 | Visit the documentation site: https://msykd.github.io/smart-contract-best-practices/ 6 | 7 | Read the docs in Chinese: https://github.com/ConsenSys/smart-contract-best-practices/blob/master/README-zh.md 8 | 9 | ## Contributions are welcome! 10 | 11 | Feel free to submit a pull request, with anything from small fixes, to full new sections. If you are writing new content, please reference the [contributing](./docs/about/contributing.md) page for guidance on style. 12 | 13 | See the [issues](https://github.com/ConsenSys/smart-contract-best-practices/issues) for topics that need to be covered or updated. If you have an idea you'd like to discuss, please chat with us in [Gitter](https://gitter.im/ConsenSys/smart-contract-best-practices). 14 | 15 | If you've written an article or blog post, please add it to the [bibliography](./bibliography). 16 | 17 | ## Building the documentation site 18 | 19 | ``` 20 | git clone git@github.com:ConsenSys/smart-contract-best-practices.git 21 | cd smart-contract-best-practices 22 | pip install -r requirements.txt 23 | mkdocs build 24 | ``` 25 | 26 | You can also use the `mkdocs serve` command to view the site on localhost, and live reload whenever you save changes. 27 | 28 | ## Redeploying the documentation site 29 | 30 | ``` 31 | mkdocs gh-deploy 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /docs/about/contributing.md: -------------------------------------------------------------------------------- 1 | We welcome all contributions - send a pull request or open an issue. When possible, send different pull requests by section/topic. 2 | 3 | Feel free to peruse the [open issues](https://github.com/ConsenSys/smart-contract-best-practices/issues) for ideas which need to be expanded on a bit here. 4 | 5 | ## Editing and Submitting changes 6 | 7 | 1. Fork the repo 8 | 2. Make changes to the markdown files in the [docs directory](../../../../tree/master/docs) of the master branch 9 | 3. Submit a Pull Request 10 | 11 | ## Audience 12 | 13 | Write for an intermediate Ethereum developer, they know the basics of Solidity programming and have coded a number of contracts 14 | 15 | ## Style Guidelines 16 | 17 | ### General 18 | 19 | - **Favor succinctness in writing** 20 | - Use max 3-4 sentences in a section (exceptions can be made when critical) 21 | - Show, don’t tell (examples speak more than lengthy exposition) 22 | - Include a simple, illustrative example rather than complex examples that require substantial, extraneous reading 23 | - **Add a source link to the original document when available** 24 | - **Create new sections when warranted** 25 | - **Keep code lines under 80 characters when possible** 26 | - **Mark code** as insecure, bad, good where relevant 27 | - Use the format of the [Airbnb Javascript Style guide](https://github.com/airbnb/javascript) as a starting point 28 | 29 | ### Recommendations Section 30 | 31 | - Always favor a declarative tip starting with a verb for the section title 32 | - Include good and bad examples, when possible 33 | - Ensure each subsection has an anchor tag for future hyperlinking 34 | 35 | ### Attacks Section 36 | 37 | - Provide an example - then point to a recommendation for the solution in the relevant section of the doc 38 | - List first/most visible attack, where possible 39 | - Ensure each subsection has an anchor tag for future hyperlinking 40 | - Mark vulnerable pieces of code as `// INSECURE` 41 | 42 | -------------------------------------------------------------------------------- /docs/about/license.md: -------------------------------------------------------------------------------- 1 | Copyright 2016 Smart Contract Best Practices Authors 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /docs/about/reviewers.md: -------------------------------------------------------------------------------- 1 | The following people have reviewed this document (date and commit they reviewed in parentheses): 2 | Bill Gleim (07/29/2016 3495fb5) 3 | Bill Gleim (03/15/2017 0244f4e) -------------------------------------------------------------------------------- /docs/bibliography.md: -------------------------------------------------------------------------------- 1 | 本ドキュメントの多くは、コミュニティによってすでに発表された様々な知見から得られたコード、事例、見識を含んでいます。 2 | それらのいくつかをここに紹介します。他に紹介すべきものがあれば、気軽に追加してください。 3 | 4 | ##### Ethereumのコアデベロッパーが提供するドキュメント 5 | 6 | - [How to Write Safe Smart Contracts](https://chriseth.github.io/notes/talks/safe_solidity) (Christian Reitwiessner) 7 | - [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/) (Christian Reitwiessner) 8 | - [Thinking about Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/) (Vitalik Buterin) 9 | - [Solidity](http://solidity.readthedocs.io) 10 | - [Solidity Security Considerations](http://solidity.readthedocs.io/en/latest/security-considerations.html) 11 | 12 | ##### コミュニティが提供するドキュメント 13 | 14 | - https://blog.sigmaprime.io/solidity-security.html 15 | - http://forum.ethereum.org/discussion/1317/reentrant-contracts 16 | - http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/ 17 | - http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/ 18 | - http://hackingdistributed.com/2016/06/22/smart-contract-escape-hatches/ 19 | - http://martin.swende.se/blog/Devcon1-and-contract-security.html 20 | - http://publications.lib.chalmers.se/records/fulltext/234939/234939.pdf 21 | - http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour 22 | - http://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful 23 | - http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal 24 | - https://blog.blockstack.org/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a 25 | - https://blog.slock.it/deja-vu-dao-smart-contracts-audit-results-d26bc088e32e 26 | - https://blog.vdice.io/wp-content/uploads/2016/11/vsliceaudit_v1.3.pdf 27 | - https://eprint.iacr.org/2016/1007.pdf 28 | - https://github.com/Bunjin/Rouleth/blob/master/Security.md 29 | - https://github.com/LeastAuthority/ethereum-analyses 30 | - https://github.com/bokkypoobah/ParityMultisigRecoveryReconciliation 31 | - https://medium.com/@ConsenSys/assert-guards-towards-automated-code-bounties-safe-smart-contract-coding-on-ethereum-8e74364b795c 32 | - https://medium.com/@coriacetic/in-bits-we-trust-4e464b418f0b 33 | - https://medium.com/@hrishiolickel/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007 34 | - https://medium.com/@peterborah/we-need-fault-tolerant-smart-contracts-ec1b56596dbc 35 | - https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab 36 | - https://pdaian.com/blog/chasing-the-dao-attackers-wake 37 | - http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf 38 | -------------------------------------------------------------------------------- /docs/bug_bounty_list.md: -------------------------------------------------------------------------------- 1 | 2 | 以下は、現行のバグバウンティプログラムです。バウンティのスコープ内において、スマートコントラクトに焦点を当てたものか、 3 | スマートコントラクトを含んだプログラムとなっています。IssueやPRは歓迎されています。それによって、新たなバウンティを追加したり、無効になったバウンティを取り除いたりができるようになります。 4 | 5 | * [0xProject](https://0xproject.com/wiki#Bug-Bounty): 2018年9月にローンチ、最大支払額$100,000 6 | * [Airswap](https://medium.com/fluidity/smart-contracts-and-bug-bounty-ad75733eb53) 7 | * [Augur](https://www.augur.net/bounty/): 2018年10月にローンチ、最大支払額$250,000 8 | * [Aragon](https://wiki.aragon.org/dev/bug_bounty/): 2018年10月にローンチ、最大支払額$250,000 9 | * [BrickBlock](https://blog.brickblock.io/join-the-brickblock-bug-bounty-program-7b431f2bcc02): 2018年9月にローンチ、最大支払額100ETH 10 | * [Colony.io](https://blog.colony.io/announcing-the-colony-network-bug-bounty-f44cabaca9a3/) 11 | * [Ethereum Foundation](https://bounty.ethereum.org/#bounty-scope): クライアント、solidity、vyper等、広範にわたるプログラム 12 | * [Etherscan.io](https://etherscan.io/bugbounty) 13 | * [Gitcoin Bounties](https://gitcoin.co/explorer): バウンティベースのコラボレーションツール 14 | * [MelonPort](https://melonport.com/bug-bounty) 15 | * [Parity](https://www.parity.io/bug-bounty/): クライアントコードとコントラクトコードを含む 16 | * [Raiden.network](https://raiden.network/bug-bounty.html) 17 | -------------------------------------------------------------------------------- /docs/documentation_procedures.md: -------------------------------------------------------------------------------- 1 | 多額の資金を必要とするコントラクトやミッションクリティカルなコントラクトが必要な場合は、適切なドキュメントを添付することが重要です。 2 | 関連するドキュメントには以下のものがあります。 3 | 4 | ## 仕様とロールアウトの計画 5 | 6 | - 監査員、査読者、およびコミュニティが、システムが意図するものを理解するのに役立つ仕様、図、状態マシン、モデル、およびその他のドキュメンテーション。 7 | - 多くのバグは仕様書から見つけることができ、修正するのが最もコストがかかりません。 8 | - [ここに](https://github.com/ConsenSys/smart-contract-best-practices#contract-rollout)記載されている詳細、及び対象日付を含むロールアウトの計画。 9 | 10 | ## 情報 11 | 12 | - 最新版のコードがデプロイされている場所。 13 | - コンパイラのバージョン、使用されるフラグ、およびデプロイされたバイトコード。 14 | - ロールアウトで使用されるコンパイラのバージョンとフラグ。 15 | - デプロイされたコードの現状(未解決の問題、パフォーマンス統計などを含む) 16 | 17 | ## 既知の問題 18 | 19 | - コントラクト上の主要リスク 20 | - 例えば全てのお金を失う、ハッカーが特定の結果に投票できる 21 | - 既知のバグ、制限事項 22 | - 潜在的な攻撃と緩和策 23 | - 潜在的な利益相反 (例: will be using yourself, like Slock.it did with the DAO) 24 | 25 | ## 歴史 26 | 27 | - テスト(使用統計、発見されたバグ、テストの長さを含む) 28 | - コードをレビューした人(そして主要なフィードバック) 29 | 30 | ## 手順 31 | 32 | - バグが発見された場合の行動計画(例:緊急のオプション、公開通知プロセスなど) 33 | - 何かが間違っている場合、プロセスを終了する(たとえば、資金提供者は、残高から攻撃前に残高の割合を取得します) 34 | - 責任ある開示方針(例えば、発見されたバグを報告する場所、バグバウンティプログラムのルール) 35 | - 失敗の場合の援助(例えば、保険、ペナルティ・ファンド、償還不可) 36 | 37 | ## 問い合わせ先 38 | 39 | - 問題が起こった時の問い合わせ先 40 | - プログラマーやその他の重要な関係者の名前 41 | - 質問が出来るチャットルーム -------------------------------------------------------------------------------- /docs/general_philosophy.md: -------------------------------------------------------------------------------- 1 | Ethereumと複雑なブロックチェーンプログラムは新しく、実験的なものです。 2 | 新しいバグやセキュリティリスクが発見され、新しいベストプラクティスが開発されると、セキュリティの状況に絶え間ない変化が予想されるはずです。 3 | したがって、この文書のセキュリティプラクティスに従うことは、スマートコントラクトエンジニアとして行う必要があるセキュリティ対策の始まりに過ぎません。 4 | 5 | スマートコントラクトプログラミングには、あなたが慣れ親しんでいたのとは異なるエンジニアリングの考え方が必要です。 6 | 障害のコストは高くなる可能性があり、変更は困難な場合があります。Webやモバイル開発よりも、ハードウェアや金融サービスのプログラミングに似ています。 7 | したがって、既知の脆弱性を守るだけでは十分ではありません。開発の新しい哲学を学ぶ必要があります。 8 | 9 | ## 失敗する準備 10 | 11 | 全ての重要なコントラクトには不具合が含まれる可能性があります。 12 | よってコードはバグや脆弱性に正常に対応できる必要があります。 13 | 14 | - 事態が悪化しそうな時にコントラクトを一時停止する('サーキットブレーカー') 15 | - リスクに備えて資金を管理する(レート制限、最大使用量) 16 | - バグ修正と改善のためのアップグレード方法を準備する 17 | 18 | ## 慎重にリリースする 19 | 20 | 本番リリース前にバグを発見できるようにしましょう。 21 | 22 | - コントラクトを徹底的にテストし、新しい攻撃方法が発見された時にテストを追加する 23 | - [バグバウンティプログラム](software_engineering.md#_7)による報奨金制度を提供する 24 | - 段階的にリリースし、各段階で使用量とテストを増やす 25 | 26 | ## コントラクトをシンプルに保つ 27 | 28 | 複雑さはエラーの可能性を高めます。 29 | 30 | - コントラクトのロジックがシンプルであることを確認する。 31 | - コントラクトや関数を小さく保つためにモジュール化する 32 | - 可能なら、既に存在するツールやコードを使用する(例えば、オリジナルの乱数ジェネレーターを書かない) 33 | - 可能な限りパフォーマンスを明確にする 34 | - 分散を必要とする部分にのみブロックチェーンを使用する 35 | 36 | 37 | ## コードをこまめにアップデートする 38 | 39 | [セキュリティに関する情報](security_notifications.md)に記載されている外部リソースを参考に、新しいセキュリティの動向を追い続けましょう。 40 | 41 | - バグが発見され次第、自身のコントラクトに新しいバグがないか確認する 42 | - ツールまたはライブラリの最新バージョンにできるだけ早くアップグレードする 43 | - 有効と思われる新しいセキュリティ技術を採用する 44 | 45 | ## ブロックチェーンのプロパティに注意する 46 | 47 | あなたのプログラミング経験の多くはEthereumプログラミングに役立ちますが、注意すべきいくつかの落とし穴があります。 48 | 49 | - 悪質なコードを実行したり、制御フローを変更したりする可能性のある外部のコントラクトをコールする際は十分注意してください。 50 | - public関数は公開されており、悪意を持って任意の順序で呼び出されることがあることを理解してください。スマートコントラクトにおいてはprivateなデータであっても誰でも見ることができます。 51 | - ガスコストとブロックガスの制限を念頭に置いてください。 52 | - タイムスタンプはブロックチェーンでは不正確であることに注意してください。マイナーは数秒のマージン内でトランザクションの実行時間に影響を与える可能性があります。 53 | - 乱数性はブロックチェーン上では自明ではありませんが、ブロックチェーン上においては、乱数生成へのほとんどのアプローチは不正行為によって影響を受ける場合があります。 54 | 55 | ## 根本的なトレードオフ: シンプルさ vs 複雑さ 56 | 57 | スマートコントラクトの構造とセキュリティを評価する際に考慮すべき複数の根本的なトレードオフがあります。 58 | スマートコントラクトの一般的な推奨事項は、これらの根本的なトレードオフの適切なバランスを特定することです。 59 | 60 | 理想的なスマートコントラクトは、ソフトウェアエンジニアリングによるモジュール化であり、 61 | コードを複製せずに再利用し、アップグレード可能なコンポーネントをサポートします。 62 | 63 | ただし、セキュリティとソフトウェアエンジニアリングのベストプラクティスが一致しない場合があるという重要な例外があります。 64 | いずれの場合も、以下のようなコントラクトシステムの規模に合った最適な特徴の組み合わせを特定することによって、適切なバランスを知ることが出来ます。 65 | 66 | - アップグレード不可 vs アップグレード可能 67 | - モノリシック vs モジュラー 68 | - 重複 vs 再利用 69 | 70 | ### アップグレード不可 vs アップグレード可能 71 | 72 | このような複数のリソースは、キルしやすい、アップグレード可能、または変更可能な可用性を強調しますが、 73 | 可用性とセキュリティの間には根本的なトレードオフがあります。 74 | 75 | 定義による可用性パターンは複雑さと潜在的な攻撃面を追加します。 76 | シンプルさは、スマートコントラクトが管理不要で有効期限のあるトークンセールシステムなど、 77 | あらかじめ定義された期間、限られた機能を実行する場合の複雑さに対して特に効果的です。 78 | 79 | ### モノリシック vs モジュラー 80 | 81 | モノリシックな自己完結型コントラクトは、すべての知識をローカルに識別して読み取り可能にします。 82 | モノリスとして存在する高性能なコントラクトシステムはほとんど存在しませんが、コードレビューの効率を最適化する場合など、 83 | データとフローの極端な地域に対して議論が必要です。 84 | 85 | ここで考慮した他のトレードオフと同様に、セキュリティのベストプラクティスは、単純で短命なコントラクトではソフトウェアエンジニアリングのベストプラクティスから離れ、 86 | より複雑な永続的に使用するコントラクトシステムの場合はソフトウェアエンジニアリングのベストプラクティスに向かう傾向があります。 87 | 88 | 89 | ### 重複 vs 再利用 90 | 91 | ソフトウェアエンジニアリングの観点から見たスマートコントラクトシステムは、合理的なところで再利用を最大限にしようとしています。 92 | Solidityでコントラクトコードを再利用する方法はたくさんあります。 93 | あなたが所有している証明済みの以前にデプロイされたコントラクトを使うことは、一般的にコードの再利用を達成する最も安全な方法です。 94 | 95 | 以前にデプロイした自己所有コントラクトが利用できない場合、複製は頻繁に使用されます。 96 | [Zeppelin Solidity](https://github.com/OpenZeppelin/zeppelin-solidity)のような取り組みは、 97 | 安全なコードを重複することなく再利用することができます。 98 | コントラクト上のセキュリティ分析をする場合には、対象のスマートコントラクトシステムでリスクのある資金と釣り合ったレベルの信頼を以前に 99 | 確立していない再利用されたコードも含まれていなければなりません。 100 | -------------------------------------------------------------------------------- /docs/img/diligence_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msykd/smart-contract-best-practices/c332e06673f1a447d0dc87966804fa5daea59508/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Ethereum Smart Contract Security Best Practices(日本語訳) 2 | 3 | このドキュメントは、[Ethereum Smart Contract Security Best Practices](https://consensys.github.io/smart-contract-best-practices/)を日本語訳したものです。 4 | **内容が古かったり、訳に誤りがあったりする可能性があります。必ず原文も併せてご確認ください。PR大歓迎です。** 5 | 6 | 中級者のSolidityプログラマ向けにセキュリティに関する基礎知識を提供します。 7 | これは[ConsenSys Diligence](https://media.consensys.net/introducing-consensys-diligence-cf38f83948c)とEthereumコミュニティによって維持されています。 8 | 9 | 10 | ## トピックス 11 | 12 | * [基本的な知識](./general_philosophy) スマートコントラクトのセキュリティに対する考え方を説明します。 13 | * [推奨する実装方法](./recommendations) 推奨すべきコードのサンプル集です。 14 | * [既知の攻撃方法](./known_attacks) 回避すべき様々な脆弱性について説明します。 15 | * [リスクの備え方](./software_engineering) リスク軽減の為のアーキテクチャと設計の概要を説明します。 16 | * [ドキュメント化の手法](./documentation_procedures) 他のエンジニアや監査を行う人の為にシステムをドキュメント化する為のベストプラクティスを説明します。 17 | * [便利なツール](./security_tools) コード品質を向上させる為のツールと、脆弱性の検出方法を説明します。 18 | * [セキュリティ関連EIP](./security_eips) セキュリティの問題と脆弱性に関連するEIPを説明します。 19 | * [セキュリティに関する情報](./security_notifications) 最新の情報源を表示します。 20 | * [トークン](./tokens.md) 特にトークンに関連するベストプラクティスの概要を説明します。 21 | 22 | 23 | ## ぜひご協力を! 24 | 25 | 細かな修正から、新しいセクションまで、プルリクエストを自由に送ってください。 26 | 新しいコンテンツを書く場合は、[contributing](./about/contributing)ページでガイダンスを確認してください。 27 | 28 | 更新が必要なトピックについては[issues](https://github.com/ConsenSys/smart-contract-best-practices/issues)を確認してください。 29 | 話したいアイデアがある場合は、[Gitter](https://gitter.im/ConsenSys/smart-contract-best-practices)でチャットしてください。 30 | 31 | もし記事やブログを書いた場合は、[参考文献](./bibliography)に追加してください。 32 | -------------------------------------------------------------------------------- /docs/known_attacks.md: -------------------------------------------------------------------------------- 1 | 以下は、スマートコントラクトを作成する際に注意し、防御する必要がある既知の攻撃の一覧です。 2 | 3 | ## Reentrancy 4 | 5 | 外部コントラクトを呼び出す際の主要な危険の1つは、コントロールフローを引き継ぎ、呼び出し関数が期待していなかったデータを変更できることです。 6 | このクラスのバグはいろいろな形を取ることができ、DAOの崩壊につながった主要なバグの両方がこの種のバグでした。 7 | 8 | ### Reentrancy on a Single Function 9 | 10 | このバグの最初のバージョンには、関数の最初の呼び出しが終了する前に、繰り返し呼び出される可能性のある関数が含まれていました。 11 | これにより、関数のさまざまな呼び出しが破壊的なやり方で相互作用する可能性があります。 12 | 13 | ```sol 14 | // INSECURE 15 | mapping (address => uint) private userBalances; 16 | 17 | function withdrawBalance() public { 18 | uint amountToWithdraw = userBalances[msg.sender]; 19 | require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call withdrawBalance again 20 | userBalances[msg.sender] = 0; 21 | } 22 | ``` 23 | 24 | 関数の最後までユーザーの残高が0に設定されていないため、2回目以降の呼び出しは引き続き成功し、何度も何度も残高を回収します。 25 | 非常によく似たバグが、The DAO攻撃の脆弱性の1つでした。 26 | 27 | この問題を避ける最も良い方法は、[`call.value()()`の代わりに`send()` を使うことです](https://github.com/ConsenSys/smart-contract-best-practices#send-vs-call-value)。 28 | これにより外部コードの実行を防ぎます。 29 | 30 | ただし、外部呼び出しを削除できない場合、この攻撃を防ぐ最も簡単な方法は、必要な内部作業をすべて完了するまで外部関数を呼び出さないようにすることです。 31 | 32 | ```sol 33 | mapping (address => uint) private userBalances; 34 | 35 | function withdrawBalance() public { 36 | uint amountToWithdraw = userBalances[msg.sender]; 37 | userBalances[msg.sender] = 0; 38 | require(msg.sender.call.value(amountToWithdraw)()); // The user's balance is already 0, so future invocations won't withdraw anything 39 | } 40 | ``` 41 | 42 | `withdrawBalance()`と呼ばれる別の関数があった場合、それは潜在的に同じ攻撃の対象となるため、 43 | 信頼できないコントラクトを呼び出す関数を信頼できないものとして扱わなければなりません。 潜在的な解決策の詳細については、以下を参照してください。 44 | 45 | ### Cross-function Reentrancy 46 | 47 | 攻撃者は同じ状態を共有する2つの異なる関数を使用して同様の攻撃を行うこともできます。 48 | 49 | ```sol 50 | // INSECURE 51 | mapping (address => uint) private userBalances; 52 | 53 | function transfer(address to, uint amount) { 54 | if (userBalances[msg.sender] >= amount) { 55 | userBalances[to] += amount; 56 | userBalances[msg.sender] -= amount; 57 | } 58 | } 59 | 60 | function withdrawBalance() public { 61 | uint amountToWithdraw = userBalances[msg.sender]; 62 | require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call transfer() 63 | userBalances[msg.sender] = 0; 64 | } 65 | ``` 66 | 67 | この場合、攻撃者は `withdrawBalance`で外部呼び出しでコードが実行されるときに` transfer() `を呼び出します。 68 | 残高はまだ0に設定されていないので、すでに引き出しを受けていてもトークンを転送することができます。この脆弱性は、The DAOへの攻撃にも使用されていました。 69 | 70 | 同じ解決策が、同じ警告で動作します。 71 | この例では、両方の関数が同じコントラクトの一部であったことにも注意してください。 72 | ただし、同じコントラクトが複数のコントラクトを共有している場合、複数のコントラクトで同じバグが発生する可能性があります。 73 | 74 | ### Pitfalls in Reentrancy Solutions 75 | 76 | Reentrancy(再入可能性)は複数の関数、さらには複数のコントラクトにまたがって発生する可能性があるため、単一の関数で再入可能性を防ぐことを目的とした解決策では不十分です。 77 | 78 | そうではなく、最初にすべての内部作業(つまり、状態の変更)を終了してから、外部関数を呼び出すことを推奨します。 79 | この規則を慎重に守れば、再入可能性による脆弱性を避けることができます。 80 | しかし、外部関数をあまりにも早く呼び出さないようにするだけでなく、外部関数を呼び出す関数を呼び出さないようにする必要があります。 81 | たとえば、以下は安全ではありません。 82 | 83 | ```sol 84 | // INSECURE 85 | mapping (address => uint) private userBalances; 86 | mapping (address => bool) private claimedBonus; 87 | mapping (address => uint) private rewardsForA; 88 | 89 | function withdrawReward(address recipient) public { 90 | uint amountToWithdraw = rewardsForA[recipient]; 91 | rewardsForA[recipient] = 0; 92 | require(recipient.call.value(amountToWithdraw)()); 93 | } 94 | 95 | function getFirstWithdrawalBonus(address recipient) public { 96 | require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once 97 | 98 | rewardsForA[recipient] += 100; 99 | withdrawReward(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again. 100 | claimedBonus[recipient] = true; 101 | } 102 | ``` 103 | 104 | `getFirstWithdrawalBonus()`が外部のコントラクトを直接呼び出すことはありませんが、`withdrawReward()`を呼び出すだけで、再入可能性の影響を受けやすくなります。 105 | したがって、あなたは`withdrawReward()`をあたかもそれが信頼できないものとして扱う必要があります。 106 | 107 | ```sol 108 | mapping (address => uint) private userBalances; 109 | mapping (address => bool) private claimedBonus; 110 | mapping (address => uint) private rewardsForA; 111 | 112 | function untrustedWithdrawReward(address recipient) public { 113 | uint amountToWithdraw = rewardsForA[recipient]; 114 | rewardsForA[recipient] = 0; 115 | require(recipient.call.value(amountToWithdraw)()); 116 | } 117 | 118 | function untrustedGetFirstWithdrawalBonus(address recipient) public { 119 | require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once 120 | 121 | claimedBonus[recipient] = true; 122 | rewardsForA[recipient] += 100; 123 | untrustedWithdrawReward(recipient); // claimedBonus has been set to true, so reentry is impossible 124 | } 125 | ``` 126 | 127 | 再入が不可能であることに加えて、[信頼できない関数がマークされてます。](./recommendations#_5) 128 | この同じパターンは、すべてのレベルで繰り返されます。 129 | `untrustedGetFirstWithdrawalBonus()`は外部コントラクトを呼び出す `untrustedWithdrawReward()`を呼び出すので、 130 | `untrustedGetFirstWithdrawalBonus()`も安全でないものとして扱わなければなりません。 131 | 132 | しばしば提案される別の解決策は、[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)です。 133 | これにより、ロックをしたオーナーだけが変更できるように、状態を「ロック」することができます。 簡単な例は次のようになります。 134 | 135 | ```sol 136 | // Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state 137 | mapping (address => uint) private balances; 138 | bool private lockBalances; 139 | 140 | function deposit() payable public returns (bool) { 141 | require(!lockBalances); 142 | lockBalances = true; 143 | balances[msg.sender] += msg.value; 144 | lockBalances = false; 145 | return true; 146 | } 147 | 148 | function withdraw(uint amount) payable public returns (bool) { 149 | require(!lockBalances && amount > 0 && balances[msg.sender] >= amount); 150 | lockBalances = true; 151 | 152 | if (msg.sender.call(amount)()) { // Normally insecure, but the mutex saves it 153 | balances[msg.sender] -= amount; 154 | } 155 | 156 | lockBalances = false; 157 | return true; 158 | } 159 | ``` 160 | 161 | ユーザーが最初の呼び出しが終了する前に `withdraw()`をもう一度呼び出そうとすると、ロックはそれが効果を発揮するのを防ぎます。 162 | これは効果的なパターンかもしれませんが、協力が必要な複数のコントラクトがある場合は面倒です。 163 | 以下は安全ではありません。 164 | 165 | ```sol 166 | // INSECURE 167 | contract StateHolder { 168 | uint private n; 169 | address private lockHolder; 170 | 171 | function getLock() { 172 | require(lockHolder == address(0)); 173 | lockHolder = msg.sender; 174 | } 175 | 176 | function releaseLock() { 177 | require(msg.sender == lockHolder); 178 | lockHolder = address(0); 179 | } 180 | 181 | function set(uint newState) { 182 | require(msg.sender == lockHolder); 183 | n = newState; 184 | } 185 | } 186 | ``` 187 | 188 | 攻撃者は `getLock()`を呼び出し、 `releaseLock()`を決して呼び出すことはできません。 189 | 彼らがこれをすると、コントラクトは永遠にロックされ、それ以上の変更はできません。 190 | 再入可能性を防ぐためにmutexを使用する場合は、ロックを要求して解放する方法がないことを慎重に確認する必要があります。 191 | (デッドロックやライブロックのようなmutexを使ってプログラミングするときには、他にも潜在的な危険性がありますので、mutexに書かれている大量の文献を参考にしてください。 192 | 193 | 194 | ## Front Running (別名 Transaction-Ordering Dependence) 195 | 196 | 上記は、攻撃者が1つのトランザクション内で悪意のあるコードを実行することを含む再入可能性の例です。 197 | 以下は、ブロックチェーンに固有の異なる種類の攻撃、つまりトランザクション自体の順序(ブロック内)は簡単に操作されるという事実があります。 198 | 199 | トランザクションはしばらくの間mempool内にあるので、ブロックに含まれる前に、どのようなアクションが発生するのかを知ることができます。 200 | これは、いくつかのトークンを購入する取引が見られる分散型マーケットや、他の取引が含まれる前に実行される成行注文のようなものにとっては面倒なことがあります。 201 | 特定のコントラクト自体に起因するため、これに対する保護は困難です。 202 | たとえば、マーケットでは、一括オークションを実装することをお勧めします(これにより、高頻度の取引の懸念からも保護されます)。 203 | 事前コミットスキームを使用する別の方法もあります。後で詳細を記します。 204 | 205 | ## Timestamp Dependence 206 | 207 | ブロックのタイムスタンプはマイナーによって操作される可能性があることに注意してください。そしてタイムスタンプのすべての直接的および間接的な使用が考慮されるべきです。 208 | タイムスタンプの依存関係に関する設計上の考慮事項については、[推奨する実装方法](./recommendations/#_20)セクションを参照してください。 209 | 210 | ## Integer Overflow and Underflow 211 | 212 | 簡単なトークン転送を想定します。 213 | 214 | ```sol 215 | mapping (address => uint256) public balanceOf; 216 | 217 | // INSECURE 218 | function transfer(address _to, uint256 _value) { 219 | /* Check if sender has balance */ 220 | require(balanceOf[msg.sender] >= _value); 221 | /* Add and subtract new balances */ 222 | balanceOf[msg.sender] -= _value; 223 | balanceOf[_to] += _value; 224 | } 225 | 226 | // SECURE 227 | function transfer(address _to, uint256 _value) { 228 | /* Check if sender has balance and for overflows */ 229 | require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]); 230 | 231 | /* Add and subtract new balances */ 232 | balanceOf[msg.sender] -= _value; 233 | balanceOf[_to] += _value; 234 | } 235 | ``` 236 | 237 | 残高が最大uint値(2 ^ 256)に達すると、ゼロに戻ります。これにより、その状態がチェックされます。 238 | これは、実装に応じて適切かどうかは関係ありません。 239 | uint値にこのような大きな値に近づく機会があるかどうかについて考えてみましょう。 240 | uint変数がどのように状態を変え、そのような変更を行う権限を持っているかについて考えます。 241 | uint値を更新する関数を呼び出すことができるユーザーがいる場合、攻撃を受けやすくなります。 242 | 管理者だけが変数の状態を変更するためのアクセス権を持っている場合、あなたは安全かもしれません。 243 | ユーザーが一度に1だけインクリメントできる場合は、この制限に達する実現可能な方法がないため、おそらく安全です。 244 | 245 | アンダーフローについても同様です。 uintが0より小さくなると、アンダーフローが発生し、最大値に設定されます。 246 | uint8、uint16、uint24 ...などの小さなデータ型には注意してください。さらに簡単に最大値に達することができます。 247 | 248 | [オーバーフローとアンダーフローが発生するケースは約20件あります。](https://github.com/ethereum/solidity/issues/796#issuecomment-253578925). 249 | 250 | ### Underflow in Depth: Storage Manipulation 251 | 252 | [Doug Hoyteによる2017年のunderhanded solidity contest(悪意のあるコードを見つけるためのプログラミングコンテスト)への応募](https://github.com/Arachnid/uscc/tree/master/submissions-2017/doughoyte)が、[名誉ある賞を受賞](http://u.solidity.cc/)しました。 253 | このエントリは、CのようなアンダーフローがSolidityストレージにどのように影響するかについての懸念を提起するので、興味深いものです。以下に単純化されたバージョンを示します: 254 | 255 | ```sol 256 | contract UnderflowManipulation { 257 | address public owner; 258 | uint256 public manipulateMe = 10; 259 | function UnderflowManipulation() { 260 | owner = msg.sender; 261 | } 262 | 263 | uint[] public bonusCodes; 264 | 265 | function pushBonusCode(uint code) { 266 | bonusCodes.push(code); 267 | } 268 | 269 | function popBonusCode() { 270 | require(bonusCodes.length >=0); // this is a tautology 271 | bonusCodes.length--; // an underflow can be caused here 272 | } 273 | 274 | function modifyBonusCode(uint index, uint update) { 275 | require(index < bonusCodes.length); 276 | bonusCodes[index] = update; // write to any index less than bonusCodes.length 277 | } 278 | 279 | } 280 | ``` 281 | 282 | 一般的に言って、変数`manipulateMe`の位置は、`keccak256`を通さない限り影響を受けることはできません。影響を及ぼすことは実行不可能です。 283 | ただし、動的配列は順次格納されるため、悪意のある行為者が`manipulateMe`を変更したい場合は、次のようにします: 284 | 285 | * `popBonusCode`をアンダーフローするために呼び出します(注:`array.pop())`メソッドはSolidity 0.5.0で[追加されました](https://github.com/ethereum/solidity/blob/v0.5.0/Changelog.md)) 286 | * `manipulateMe`の保管場所を算出します。 287 | * `modifyBonusCode`を使用して`manipulateMe`の値を変更および更新する 288 | 289 | 実際には、この配列はすぐに厄介であると指摘されます。しかし、より複雑なスマートコントラクトアーキテクチャの下に埋め込まれていると、定数への悪意のある変更を勝手に許可する可能性があります。 290 | 291 | 動的配列の使用を検討する際には、コンテナー・データ構造が良い方法です。 Solidity CRUD [part 1](https://medium.com/@robhitchens/solidity-crud-part-1-824ffa69509a)と[part 2](https://medium.com/@robhitchens/solidity-crud-part-2-ed8d8b4f74ec)の記事は良い情報源です。 292 | 293 | 294 | 295 | ## DoS with (Unexpected) revert 296 | 297 | 簡単なオークションのコントラクトを想定します。 298 | 299 | ```sol 300 | // INSECURE 301 | contract Auction { 302 | address currentLeader; 303 | uint highestBid; 304 | 305 | function bid() payable { 306 | require(msg.value > highestBid); 307 | 308 | require(currentLeader.send(highestBid)); // Refund the old leader, if it fails then revert 309 | 310 | currentLeader = msg.sender; 311 | highestBid = msg.value; 312 | } 313 | } 314 | ``` 315 | 316 | 古いリーダーを払い戻そうとすると、払い戻しが失敗した場合に元のリーダーに戻ります。 317 | これは、悪意のある入札者が、そのアドレスへの払い戻しが常に失敗することを確実にしながら、リーダーになれることを意味します。 318 | このようにして、他の誰かが `bid()`関数を呼び出すのを防ぐことができ、リーダーを永遠にとどめることができます。 319 | 前に説明したように、[pull型の支払い](./recommendations#push-pull)を代わりに設定することをお勧めします。 320 | 321 | もう1つの例は、コントラクトによってユーザー(例えば、クラウドファンディングコントラクトの支援者)に支払うために配列を反復する場合です。 322 | それぞれの支払いが成功することを確認することが一般的です。 323 | そうでない場合は、元に戻す必要があります。問題は、1つの呼び出しが失敗した場合、支払いシステム全体を元に戻すことです。 324 | つまり、ループは完了しません。 1つのアドレスで強制的にエラーが発生するため、誰も支払いを受けません。 325 | 326 | ```sol 327 | address[] private refundAddresses; 328 | mapping (address => uint) public refunds; 329 | 330 | // bad 331 | function refundAll() public { 332 | for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated 333 | require(refundAddresses[x].send(refunds[refundAddresses[x]])) // doubly bad, now a single failure on send will hold up all funds 334 | } 335 | } 336 | ``` 337 | 338 | ここでも推奨の解決策は [push型よりもpull型が望ましい](./recommendations#push-pull)ことになります。 339 | 340 | ## DoS with Block Gas Limit 341 | 342 | 各ブロックには、使用できるガスの量、つまり実行できる量の計算に上限があります。 343 | これがブロックガスリミットです。消費されたガスがこの制限を超えると、トランザクションは失敗します。これは、DoSの可能性へとつながります。 344 | 345 | ### Gas Limit DoS on a Contract via Unbounded Operations 346 | 347 | あなたは前の例に別の問題があることに気づいたかもしれません。一度に全員に払い戻すことで、ブロックガス制限にぶつかる危険性があります。 348 | 349 | これは、意図的な攻撃がなくても問題につながる可能性があります。 350 | しかし、攻撃者が必要なガスの量を操作できるならば、特に悪いことです。 351 | 前の例の場合、攻撃者はアドレスの束を追加することができ、それぞれは非常に小さな払い戻しを必要とします。 352 | したがって、各攻撃者のアドレスを払い戻すためのガスコストは、ガス制限を超えてしまい、払い戻し取引がまったく起こらないようになる可能性があります。 353 | 354 | これが[push型よりもpull型が望ましい](./recommendations#push-pull)ことのもうひとつの理由です。 355 | 356 | 未知のサイズの配列を絶対にループする必要がある場合は、複数のブロックを取る可能性があるため、複数のトランザクションが必要になる可能性があります。 357 | 次の例のように、あなたがどれぐらい離れているかを追跡し、その時点から再開できるようにする必要があります。 358 | 359 | ```sol 360 | struct Payee { 361 | address addr; 362 | uint256 value; 363 | } 364 | 365 | Payee[] payees; 366 | uint256 nextPayeeIndex; 367 | 368 | function payOut() { 369 | uint256 i = nextPayeeIndex; 370 | while (i < payees.length && msg.gas > 200000) { 371 | payees[i].addr.send(payees[i].value); 372 | i++; 373 | } 374 | nextPayeeIndex = i; 375 | } 376 | ``` 377 | 378 | `payOut()`関数の次の反復を待っている間に他のトランザクションが処理されても何の悪いことも起こらないようにする必要があります。 379 | このパターンは、絶対に必要な場合にのみ使用してください。 380 | 381 | 382 | ### Gas Limit DoS on the Network via Block Stuffing 383 | 384 | コントラクトに無限のループが含まれていなくても、攻撃者は、十分に高いガス価格を備えた計算集約的トランザクションを配置することによって、他のトランザクションがブロックに取り込ませないようにすることが可能です。 385 | これを行うために、攻撃者は、ガスリミット全てまでを消費するいくつかのトランザクションを発行することができます。それらのトランザクションは、次のブロックがマイニングされるや否やブロックに取り込まれるほど十分に高いガス価格です。 386 | ガス価格でブロックに含めることを保証することはできませんが、価格が高いほど、チャンスは高くなります。 387 | 388 | 攻撃が成功した場合、他のトランザクションはブロックに含まれません。往々にして、攻撃者の目的は、特定の時間まで、特定のコントラクトに対するトランザクションをブロックすることです。 389 | 390 | この攻撃はギャンブルアプリFomo3Dで[行われました](https://osolmaz.com/2018/10/18/anatomy-block-stuffing)。アプリは「キー」を購入した最後のアドレスに報酬を与えるように設計されました。キーを購入するたびにタイマーが延長され、タイマーが0になるとゲームは終了します。 391 | 攻撃者はキーを購入してから、タイマーがトリガーされて支払いが解放されるまで、連続して13ブロックを詰まらせました。 392 | 攻撃者によって送信されたトランザクションは各ブロックで790万のガスを消費したため、ガスリミットによりいくつかの小さな「送信」トランザクション(それぞれ21,000ガスを消費)だけが許可されました。他方、`buyKey()`関数への呼び出しは許可されませんでした(これには300,000以上のガスが必要です)。 393 | 394 | Block Stuffing attackは、一定期間内に対応が必要なあらゆるコントラクトに対して使用できます。ただし、他の攻撃と同様に、予想される報酬(見返り)がその攻撃コストを超える場合にのみ有益です。 395 | この攻撃のコストは、詰まらせる必要があるブロックの数に正比例します。他の参加者からの行動を防ぐことによって大きな支払いが得られる場合、あなたのコントラクトはそのような攻撃の標的になるでしょう。 396 | 397 | ## Insufficient gas griefing 398 | 399 | この攻撃は、汎用データを受け入れ、それを使って低レベルの`address.call()`関数を介して他のコントラクトを呼び出す(「サブコール」する)際に発生する可能性があります。マルチシグおよびトランザクションリレーのコントラクトではよくあることです。 400 | 401 | 関数コールが失敗した場合、コントラクトには2つの選択肢があります。 402 | 403 | 1. トランザクション全体を元に戻す 404 | 2. 実行を続ける 405 | 406 | サブコールの結果に関係なく実行を継続する単純化した`Relayer`コントラクトの例を取ります。 407 | 408 | ```sol 409 | contract Relayer { 410 | mapping (bytes => bool) executed; 411 | 412 | function relay(bytes _data) public { 413 | // replay protection; do not call the same transaction twice 414 | require(executed[_data] == 0, "Duplicate call"); 415 | executed[_data] = true; 416 | innerContract.call(bytes4(keccak256("execute(bytes)")), _data); 417 | } 418 | } 419 | ``` 420 | 421 | このコントラクトはトランザクションの中継を許可します。トランザクションを発行したいが自分でそれを実行することができない人(例:ガス代のEtherがないため)は、その人が渡したいデータに署名し、任意の媒体を介して自分の署名でデータを転送できます。 422 | その後、第三者の「フォワーダー(forwarder)」がユーザに代わってこのトランザクションをネットワークに送信できます。 423 | 424 | 適切な量​​のガスが与えられた場合、`Relayer`は実行されたマッピングの_data引数を記録して実行を完了しますが、サブコールは実行を完了するのに不十分なガスを受け取ったため失敗します。 425 | 426 | 注:あるコントラクトが別のコントラクトにサブコールをおこなうと、EVMは転送されるガスを[残りのガスの63/64](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md)に制限します。 427 | 428 | 攻撃者はこれを使用してトランザクションを検閲し、少量のガスを使用して送信することでトランザクションを失敗させることができます。この攻撃は「[グリーフィング(griefing)](https://en.wikipedia.org/wiki/Griefer)」の一形態です。 429 | それは直接攻撃者に利益をもたらすわけではありませんが、被害者に不利益をもたらします。 430 | 431 | これに対処する1つの方法は、フォワーダがサブコールを終了するのに十分なガスを提供することを要求するロジックを実装することです。マイナーがこのシナリオで攻撃を行おうとした場合、`require`ステートメントで失敗し、内部呼び出しは元に戻ります。 432 | ユーザーは他のデータと共に最小gasLimitを指定できます(この例では、通常`_gasLimit`値は署名によって検証されますが、この場合は簡略化のため省略されています。)。 433 | 434 | ```sol 435 | // contract called by Relayer 436 | contract Executor { 437 | function execute(bytes _data, uint _gasLimit) { 438 | require(gasleft() >= _gasLimit); 439 | ... 440 | } 441 | } 442 | ``` 443 | 444 | もう1つの解決策は、信頼できるアカウントだけにトランザクションの中継を許可することです。 445 | 446 | 447 | ## Forcibly Sending Ether to a Contract 448 | 449 | Etherをコントラクトに強制的に送信することは、fallback関数をトリガーすることなく可能です。 450 | これは、fallback関数に重要なロジックを配置したり、コントラクトの残高に基づいて計算を行う際に重要な考慮事項です。 451 | 次の例を考えてみましょう。 452 | 453 | ```sol 454 | contract Vulnerable { 455 | function () payable { 456 | revert(); 457 | } 458 | 459 | function somethingBad() { 460 | require(this.balance > 0); 461 | // Do something bad 462 | } 463 | } 464 | ``` 465 | 466 | このロジックはコントラクトへの支払いを拒否し、何か悪いことが起こらないようにしているようです。 467 | しかしながら、Etherを強制的にコントラクトに送り、それゆえ、その残高をゼロより大きくするためのいくつかの方法が存在します。 468 | 469 | `selfdestruct`コントラクトメソッドでは、ユーザーは受取人を指定して余分なEtherを送ることができます。 470 | `selfdestruct`は[コントラクトのfallback関数を起動させません](https://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether)。 471 | 472 | コントラクトをデプロイする前に、コントラクトアドレスを[事前計算](https://github.com/Arachnid/uscc/tree/master/submissions-2017/ricmoo)し、そのアドレスにEtherを送信することもできます。 473 | 474 | コントラクトエンジニアは、Etherを強制的に送ることができることに注意し、それに応じてロジックを設計する必要があります。 475 | 一般に、資金源をあなたのコントラクトに制限することは不可能であると想定してください。 476 | 477 | ## 廃止予定/過去に存在した攻撃 478 | 479 | これは、プロトコルの変更やSolidityの改善により、もはや不可能な攻撃です。後世のためにここに記録されています。 480 | 481 | ### Call Depth Attack (deprecated) 482 | 483 | [EIP 150](https://github.com/ethereum/EIPs/issues/150)ハードフォークの時点では、Call Depth攻撃はもはや意味がありません 484 | \* 485 | (すべてのガスは1024の深さ制限に達する前に十分消費されるでしょう)。 486 | 487 | ### Constantinople Reentrancy Attack 488 | 489 | 2019年1月16日、Constantinopleプロトコルのアップグレードは[EIP 1283](https://eips.ethereum.org/EIPS/eip-1283)によって可能にされたセキュリティ脆弱性のために延期されました。 490 | EIP 1283において、ダーティーマップを使用しないSSTOREの純粋なガス計測では、ダーティーストレージ書き込み時の過剰なガスコストを削減するための変更が提案されています。 491 | 492 | この変更により、特定の状況で以前から知られていた安全なwithdrawパターン(`.send()`および`.transfer()`)を安全でないものにする新たな再入可能性の可能性が生まれました\*。 493 | 攻撃者は制御フローを乗っ取ってEIP 1283によって有効にされている残りのガスを使用する可能性があり、再入可能性による脆弱性をもたらす可能性があります。 494 | 495 | ## その他の脆弱性 496 | 497 | [Smart Contract Weakness Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/)は、既知のスマートコントラクトの脆弱性とアンチパターンに関する完全で最新の一覧として、実例とともに提供されています。 498 | レジストリを参照することは、最新の攻撃情報に触れることができ、情報をアップデートし続ける良い方法です。 499 | -------------------------------------------------------------------------------- /docs/recommendations.md: -------------------------------------------------------------------------------- 1 | このページでは、スマートコントラクトを書くときに一般的に従うべきいくつかのパターンを示しています。 2 | 3 | ## プロトコル固有の推奨事項 4 | 5 | ### 6 | 7 | 以下の推奨事項は、Ethereum上のコントラクトシステムの開発に適用されます。 8 | 9 | ## 外部コール 10 | 11 | ### 外部コールを行う時は注意する 12 | 13 | 信頼されていないコントラクトを呼び出すと、いくつかの予期しないリスクやエラーが発生する可能性があります。 14 | 外部呼び出しは、そのコントラクトで悪意のあるコードを実行する可能性があります。 15 | したがって、すべての外部コールは潜在的なセキュリティリスクとして扱う必要があります。 16 | 外部コールを取り除くことができない、または望ましくない場合は、このセクションの残りのセクションの推奨事項を使用して危険を最小限に抑えてください。 17 | 18 | ### 信用できないコントラクトにマークをつける 19 | 20 | 外部コントラクトと対話するときは、変数、メソッド、およびコントラクト・インターフェースに、それらとの相互作用が潜在的に危険なものであることを明確にするような名前を付けます。 21 | これは、外部コントラクトを呼び出す独自の関数に適用されます。 22 | 23 | ```sol 24 | // bad 25 | Bank.withdraw(100); // Unclear whether trusted or untrusted 26 | 27 | function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe 28 | Bank.withdraw(amount); 29 | } 30 | 31 | // good 32 | UntrustedBank.withdraw(100); // untrusted external call 33 | TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp 34 | 35 | function makeUntrustedWithdrawal(uint amount) { 36 | UntrustedBank.withdraw(amount); 37 | } 38 | ``` 39 | 40 | ### 外部コール後の状態変化は避ける 41 | 42 | *raw calls* (`someAddress.call()`) や *contract calls* (`ExternalContract.someMethod()`)の 43 | どちらを使用する場合でも、悪質なコードが実行される可能性があるとします。外部コントラクトが悪意のあるものではないとしても、それが呼び出すすべてのコントラクトによって悪質なコードが実行される可能性があります。 44 | 45 | 特に危険なのは、悪意のあるコードが制御フローを乗っ取ってリエントラント(再入可能性)による脆弱性を引き起こす可能性があることです。(この問題の詳細な議論については、[Reentrancy](./known_attacks#reentrancy)を参照してください) 46 | 47 | 信頼できない外部コントラクトにコールをする場合は、*コール後の状態変化は避けてください*。このパターンは、[checks-effects-interactions pattern](http://solidity.readthedocs.io/en/develop/security-considerations.html?highlight=check%20effects#use-the-checks-effects-interactions-pattern)とも呼ばれます。 48 | 49 | 50 | ### `send()`, `transfer()`, `call.value()()`の間のトレードオフに注意する 51 | 52 | etherを送信する方法として 53 | `someAddress.send()`, `someAddress.transfer()`, `someAddress.call.value()()`などがあります。 54 | 55 | - `someAddress.send()`と`someAddress.transfer()`は[リエントラント(再入可能性)](https://github.com/ConsenSys/smart-contract-best-practices/known_attacks#reentrancy)に対して安全と見なされます。 56 | これらのメソッドはコードを実行しますが、呼び出されるコントラクトには2,300ガスの義務のみが与えられ、現在はイベントを記録するのに十分です。 57 | 58 | - `x.transfer(y)` は `require(x.send(y));`と同じで、送信が失敗すると元の状態に戻ります。 59 | - `someAddress.call.value(y)()`は、提供されたetherとトリガーコードを送信します。 60 | 実行されたコードには利用できるすべてのガスが与えられ、リエントラント(再入可能性)に対して安全ではありません。 61 | 62 | `send()`や `transfer()`を使うとリエントラントを防ぐことができますが、フォールバック関数が2,300を超えるガスを必要とするコントラクトと互換性がないという犠牲を払って行います。 63 | また、`someAddress.call.value(ethAmount).gas(gasAmount)()`を使ってカスタム量のガスを転送することもできます。 64 | 65 | このトレードオフのバランスをとることを試みる1つのパターンは、[*push* と *pull*](#_8)の両方のメカニズムを実装することです。 66 | *push* コンポーネントには `send()` または `transfer()` を使用し、*pull* コンポーネントには `call.value()()` を使用します。 67 | 68 | valueの送信に `send()`や `transfer()` を排他的に使用しても、リエントラントに対しては安全ではなく、 69 | 特定のvalueを再入可能で安全にするということだけを指摘しておきましょう。 70 | 71 | ### 外部呼び出しのエラー処理 72 | 73 | Solidityは、 `address.call()`、 `address.callcode()`、 `address.delegatecall()`、 `address.send()`のような生のアドレスで動作する低レベル呼び出しメソッドを提供します。 74 | これらの低レベルメソッドは決して例外をスローしませんが、呼び出しが例外を検出した場合は `false`を返します。 75 | 一方、 `ExternalContract.doSomething()`などのコントラクト呼び出しは `doSomething()`がスローされると自動的にスローを伝播します(例えば、 `ExternalContract.doSomething()`も `throw`)。 76 | 77 | 低レベルのコールメソッドを使用する場合は、戻り値をチェックしてコールが失敗する可能性を処理してください。 78 | 79 | ```sol 80 | // bad 81 | someAddress.send(55); 82 | someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result 83 | 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 84 | 85 | // good 86 | if(!someAddress.send(55)) { 87 | // handle failure code 88 | } 89 | 90 | ExternalContract(someAddress).deposit.value(100); 91 | ``` 92 | 93 | 94 | ### 外部呼び出しでは *push* 型よりも *pull* 型が望ましい 95 | 96 | 外部呼び出しが誤ってまたは意図的に失敗する可能性があります。 97 | このような障害によって引き起こされる被害を最小限に抑えるには、各外部呼び出しを、受信者が開始できる独自のトランザクションに分離する方がよい場合があります。 98 | これは、特に支払いに関連します。ユーザーへ自動的に資金を送金(push)するのではなく、資金を引き出してもらう(pullしてもらう)ことをお勧めします。 99 | (これにより、[ガスリミット問題](./known_attacks#dos-with-block-gas-limit)の可能性も減ります。) 100 | 単一のトランザクションで複数の`send()`呼び出しを組み合わせることは避けてください。 101 | 102 | ```sol 103 | // bad 104 | contract auction { 105 | address highestBidder; 106 | uint highestBid; 107 | 108 | function bid() payable { 109 | require(msg.value >= highestBid); 110 | 111 | if (highestBidder != address(0)) { 112 | highestBidder.transfer(highestBid); // if this call consistently fails, no one else can bid 113 | } 114 | 115 | highestBidder = msg.sender; 116 | highestBid = msg.value; 117 | } 118 | } 119 | 120 | // good 121 | contract auction { 122 | address highestBidder; 123 | uint highestBid; 124 | mapping(address => uint) refunds; 125 | 126 | function bid() payable external { 127 | require(msg.value >= highestBid); 128 | 129 | if (highestBidder != address(0)) { 130 | refunds[highestBidder] += highestBid; // record the refund that this user can claim 131 | } 132 | 133 | highestBidder = msg.sender; 134 | highestBid = msg.value; 135 | } 136 | 137 | function withdrawRefund() external { 138 | uint refund = refunds[msg.sender]; 139 | refunds[msg.sender] = 0; 140 | msg.sender.transfer(refund); 141 | } 142 | } 143 | ``` 144 | 145 | ### 信頼できないコードにdelegatecallをしない 146 | 147 | `delegatecall` 関数は、あたかも呼び出し側のコントラクトに属しているかのように、他のコントラクトから関数を呼び出すために使用されます。 148 | したがって、呼び出された側は状態を変更されてしまう可能性があります。これは安全ではないかもしれません。以下の例は、`delegatecall` を使用すると、コントラクトが破棄されて残高が失われる可能性があることを示しています。 149 | 150 | ```sol 151 | contract Destructor 152 | { 153 | function doWork() external 154 | { 155 | selfdestruct(0); 156 | } 157 | } 158 | 159 | contract Worker 160 | { 161 | function doWork(address _internalWorker) public 162 | { 163 | // unsafe 164 | _internalWorker.delegatecall(bytes4(keccak256("doWork()"))); 165 | } 166 | } 167 | ``` 168 | 169 | デプロイされた`Destructor`コントラクトのアドレスを引数として`Worker.doWork()`が呼び出されると、Workerコントラクトはself-destructします。実行を信頼できるコントラクトにのみ委譲し、**ユーザーが指定したアドレスには委譲しないでください**。 170 | 171 | !!! Warning 172 | コントラクトが残高ゼロで作成されていると想定してはいけません。攻撃者はコントラクトが作成される前にコントラクトアドレスにetherを送ることができます。 173 | コントラクトは、その初期状態に残高ゼロであると想定するべきではありません。詳細は、[issue 61](https://github.com/ConsenSys/smart-contract-best-practices/issues/61)を参照してください。 174 | 175 | ## オンチェーンのデータは公開されていることを忘れない 176 | 177 | 多くのアプリケーションでは、データをある時点まで非公開にする必要があります。ゲーム(例、オンチェーン上のじゃんけん)とオークション(例、2位価格封印入札[Vickrey auctions](https://en.wikipedia.org/wiki/Vickrey_auction))は、2つの主要なカテゴリの例です。 178 | プライバシーが問題となるアプリケーションを構築する場合は、ユーザーに情報を早期に公開しないように注意してください。 179 | 最良の戦略は、別々のフェーズで[コミットメントスキーム](https://en.wikipedia.org/wiki/Commitment_scheme)を使用することです。最初に値のハッシュを使用してコミットし、後の段階で値を表示します。 180 | 181 | 例: 182 | 183 | * じゃんけんでは、両方のプレイヤーに、最初に意図した移動のハッシュを提出する必要があります。送信された移動がハッシュと一致しない場合、例外をthrowします。 184 | * オークションでは、プレイヤーに初期段階で入札額のハッシュ値を提示し(入札額を超える入金額とともに)、第2段階でアクション入札額を提示する必要があります。 185 | * 乱数ジェネレータに依存するアプリケーションを開発する場合は、(1)プレイヤーが手を加える、(2)乱数が生成される、(3)プレイヤーが支払う、という順序が常に必要です。乱数が生成される方法は、それ自体が活発な研究の領域です。 186 | 現在のクラス最高のソリューションには、Bitcoinブロックヘッダー(http://btcrelay.org で検証済み)、ハッシュコミット公開スキーム(すなわち、一方の当事者が数値を生成し、そのハッシュを公開して値にコミットし、 後でその値を明らかにする)そして[RANDAO](http://github.com/randao/randao)です。Ethereumは確定的プロトコルであるため、プロトコル内の変数を予測不可能な乱数として使用することはできません。マイナーはある程度`block.blockhash()`値を管理していることにも注意してください\*。 187 | 188 | ## 2者コントラクトまたはN者コントラクトでは、一部の参加者が「オフラインになる」可能性があることに注意する 189 | 190 | 他の方法で資金を引き出すことなく、特定の行動を実行している特定の関係者に返金または請求プロセスを依存させないでください。 191 | たとえば、じゃんけんゲームでよくある間違いの1つは、両方のプレーヤーが自分の手を出すまで支払いを行わないことです。 192 | しかし、悪意のあるプレイヤーは、決して動きを決めないことによって他のプレイヤーを「悲しませる」ことができます。 193 | 実際、プレイヤーが他のプレイヤーの明らかにされた動きを見て、彼らが負けたと判断した場合、彼らは自分の動きを提出する理由が全くありません。この問題は、ステートチャネルの決済の状況でも発生する可能性があります 194 | そのような状況が問題である場合、(1)おそらく期限を過ぎて参加していない参加者を迂回させる方法を提供する、(2)参加者がそうすることが想定されているすべての状況で情報を送信するための追加の経済的インセンティブを追加することを検討してください。 195 | 196 | ## 最大の負の数の符号付き整数否定に注意する 197 | 198 | Solidityは符号付き整数を扱うためのいくつかの型を提供します。ほとんどのプログラミング言語のように、Solidityでは`N`ビットの符号付き整数は`-2^(N-1)`から`2^(N-1)-1`までの値を表すことができます。これは、`MIN_INT`に正の等価物がないことを意味します。 199 | 否定は、2つの補数を見つけることで実装されます。 そのため、最も負の数の否定は[同じ数になります](https://en.wikipedia.org/wiki/Two%27s_complement#Most_negative_number)。 200 | 201 | これはSolidityの全ての符号付き整数型(`int8`, `int16`, ..., `int256`)に当てはまります。 202 | 203 | ```sol 204 | contract Negation { 205 | function negate8(int8 _i) public pure returns(int8) { 206 | return -_i; 207 | } 208 | 209 | function negate16(int16 _i) public pure returns(int16) { 210 | return -_i; 211 | } 212 | 213 | int8 public a = negate8(-128); // -128 214 | int16 public b = negate16(-128); // 128 215 | int16 public c = negate16(-32768); // -32768 216 | } 217 | ``` 218 | 219 | これを処理する1つの方法は、否定の前に変数の値をチェックし、それが`MIN_INT`と等しい場合に例外をスローすることです。 220 | もう1つの選択肢は、最大の負の数が、より大きな容量を持つ型(たとえば、`int16`ではなく`int32`)を使用して達成されないようにすることです。 221 | 222 | `int`型に関する同様の問題は、`MIN_INT`が`-1`で乗算または除算されたときにも発生します。 223 | 224 | ## Solidity特有の推奨事項 225 | 226 | ### 227 | 228 | 以下の推奨事項は、Solidity固有のものですが、他の言語でスマートコントラクトを作成するための参考にもなります。 229 | 230 | ## `assert()`で不変条件を強制する 231 | 232 | アサーションが失敗した場合(不変プロパティの変更など)、アサートガードがトリガーされます。 233 | 例えば、トークン発行コントラクトにおけるトークンとetherの発行比率が固定されている場合です。 234 | これが常に `assert()`の場合に当てはまることを確認することができます。 235 | アサートガードは、コントラクトを一時停止し、アップグレードを許可するなど、他の手法と組み合わせることがよくあります。 236 | (でなければあなたはいつも失敗しているアサーションで立ち往生するかもしれません。) 237 | 238 | 例: 239 | 240 | ```sol 241 | contract Token { 242 | mapping(address => uint) public balanceOf; 243 | uint public totalSupply; 244 | 245 | function deposit() public payable { 246 | balanceOf[msg.sender] += msg.value; 247 | totalSupply += msg.value; 248 | assert(this.balance >= totalSupply); 249 | } 250 | } 251 | ``` 252 | 253 | アサーションは、コントラクトが `deposit()`関数を経由せずに強制的にetherを送信することができるので、残高が厳密にイコールではないことに注意してください。 254 | 255 | ## `assert()`, `require()`, `revert()` を適切に使う 256 | 257 | Solidity 0.4.10から`assert()`と`require()`が導入されました。 258 | 259 | > 便利な関数 **assert** および **require** は、条件をチェックし、条件が満たされない場合は例外をスローするために使用できます。 260 | 261 | > **assert** 関数は、内部エラーをテストし、不変条件をチェックするためにのみ使用されるべきです。 262 | 263 | > **require** 関数は有効な条件を保証するために使われるべきです。たとえば、入力値、あるいはコントラクトの状態変数の条件が合致している、または外部コントラクトへの呼び出しからの戻り値を検証するなどです。\* 264 | 265 | このパラダイムに従うことで、正式な分析ツールは無効なオペコードに決して到達できないことを検証することができます。つまり、コード内の不変条件に違反していないこと、およびコードが正式に検証されていることを意味します。 266 | 267 | ```sol 268 | pragma solidity ^0.5.0; 269 | 270 | contract Sharer { 271 | function sendHalf(address payable addr) public payable returns (uint balance) { 272 | require(msg.value % 2 == 0, "Even value required."); //Require() can have an optional message string 273 | uint balanceBeforeTransfer = address(this).balance; 274 | addr.transfer(msg.value / 2); 275 | // Since transfer throws an exception on failure and 276 | // cannot call back here, there should be no way for us to 277 | // still have half of the money. 278 | assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); // used for internal error checking 279 | return address(this).balance; 280 | } 281 | } 282 | ``` 283 | 284 | ## アサーションにのみ修飾子を使う 285 | 286 | 修飾子の内側のコードは通常関数本体の前に実行されるので、状態の変化や外部呼び出しは[Checks-Effects-Interactions](https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern)パターンに違反します。 287 | さらに、修飾子のコードは関数宣言からかけ離れているため、これらのステートメントも開発者には気付かれないままになることがあります。 288 | たとえば、修飾子での外部呼び出しはリエントラント攻撃につながる可能性があります。 289 | 290 | ```sol 291 | contract Registry { 292 | address owner; 293 | 294 | function isVoter(address _addr) external returns(bool) { 295 | // Code 296 | } 297 | } 298 | 299 | contract Election { 300 | Registry registry; 301 | 302 | modifier isEligible(address _addr) { 303 | require(registry.isVoter(_addr)); 304 | _; 305 | } 306 | 307 | function vote() isEligible(msg.sender) public { 308 | // Code 309 | } 310 | } 311 | ``` 312 | 313 | この場合、`Registry`コントラクトは`isVoter()`の中で`Election.vote()`を呼び出すことによってリエントラント攻撃を仕掛けることができます。 314 | 315 | `isOwner()`などの複数の関数で重複する条件チェックを置き換えるには[修飾子](https://solidity.readthedocs.io/en/develop/contracts.html#function-modifiers)を使用します。 316 | それ以外の場合は、関数内で`require`または`revert`を使用します。これにより、スマートコントラクトコードが読みやすくなり、監査が容易になります。 317 | 318 | 319 | ## 整数除算での丸めに注意する 320 | 321 | すべての整数の除算は、最も近い整数に切り下げられます。さらに精度が必要な場合は、乗数を使用するか、分子と分母の両方を格納することを検討してください。 322 | 323 | (将来的には、Solidityは[固定小数点型](https://solidity.readthedocs.io/en/develop/types.html#fixed-point-numbers)を使用するため、これは簡単になります。) 324 | 325 | ```sol 326 | // bad 327 | uint x = 5 / 2; // 結果は2で、すべての整数除算がDOWNから最も近い整数に丸められます。 328 | ``` 329 | 330 | 乗数を使用すると四捨五入を防ぐことができます。この乗数は、将来xで作業するときに考慮する必要があります。 331 | 332 | ```sol 333 | // good 334 | uint multiplier = 10; 335 | uint x = (5 * multiplier) / 2; 336 | ``` 337 | 338 | 分子と分母を格納することは、 `分子/分母`の結果をオフチェーンで計算できることを意味します: 339 | ```sol 340 | // good 341 | uint numerator = 5; 342 | uint denominator = 2; 343 | ``` 344 | 345 | ## Etherは強制的にアカウントに送ることが出来る 346 | 347 | 厳密にコントラクトの残高をチェックする不変条件をコーディングすることに注意してください。 348 | 349 | 攻撃者は任意のアカウントにweiを強制的に送ることができ、これは防ぐことができません( `revert()`を実行するフォールバック関数でさえもそうではありません)。 350 | 351 | 攻撃者はコントラクトを作成し、1weiで資金を調達し、 `selfdestruct(victimAddress)`を呼び出すことでこれを行うことができます。 352 | `victimAddress`ではコードが呼び出されないので、防ぐことはできません。 353 | 354 | ## 抽象的なコントラクトとインタフェースの間のトレードオフに注意してください 355 | 356 | インタフェースと抽象コントラクトの両方は、スマートコントラクトのためのカスタマイズ可能で再利用可能なアプローチを提供します。 357 | Solidity 0.4.11で導入されたインタフェースは、抽象的なコントラクトに似ていますが、実装されている機能を持つことはできません。 358 | インタフェースには、ストレージにアクセスできない、または抽象コントラクトをより実用的にする他のインタフェースから継承するなどの制限もあります。 359 | しかし、インタフェースは実装前にコントラクトを設計する上では確かに有用です。 360 | さらに、コントラクトが抽象コントラクトから継承する場合は、オーバーライドを使用して実装されていないすべての機能を実装する必要があること、または抽象的であることにも留意することが重要です。 361 | 362 | ## fallback関数をシンプルに保つ 363 | 364 | [Fallback functions](http://solidity.readthedocs.io/en/latest/contracts.html#fallback-function)は、引数に引数なしのメッセージが送信されたとき(または関数が一致しないとき)に呼び出され、 `.send()`または `.transfer()`から呼び出されたとき2,300個のガスにアクセスできます。 365 | あなたが `.send()`や `.transfer()`からEtherを受信できるようにしたいのであれば、fallback関数でできるのはイベントを記録することです。 366 | 計算以上のガスが必要な場合は、適切な関数を使用してください。 367 | 368 | ```sol 369 | // bad 370 | function() payable { balances[msg.sender] += msg.value; } 371 | 372 | // good 373 | function deposit() payable external { balances[msg.sender] += msg.value; } 374 | 375 | function() payable { LogDepositReceived(msg.sender); } 376 | ``` 377 | 378 | ## fallback関数でデータ長をチェックする 379 | 380 | [fallback functions](http://solidity.readthedocs.io/en/latest/contracts.html#fallback-function)はEther転送のために呼び出されるだけでなく、他の関数が一致しないときにも呼び出されます。 381 | そのため、fallback関数が受信されたEtherのログ記録の目的にのみ使用されることを意図している場合は、データが空であることを確認する必要があります。 382 | そうでなければ、あなたのコントラクトが誤って使用されていて、存在しない機能が呼び出されても、呼び出し側は気付かないでしょう。 383 | 384 | ```sol 385 | // bad 386 | function() payable { LogDepositReceived(msg.sender); } 387 | 388 | // good 389 | function() payable { require(msg.data.length == 0); LogDepositReceived(msg.sender); } 390 | ``` 391 | 392 | ## payable関数と状態変数を明示的にマークする 393 | Solidity `0.4.0`からは、etherを受け取っているすべての関数は`payable`修飾子を使わなければなりません。そうでなければ、トランザクションが`msg.value> 0`の場合は([こちらの例の場合を除いて](./recommendations/#ether))リバートされます。 394 | 395 | あなたが関数に `transfer` を呼び出したい場合は、変数、特に関数の引数を`address payable`として宣言してください。 396 | `address payable`に`.transfer(..)`および`.send(..)`を使用できますが、`address`は使用できません。 397 | 関数にEther値を紐づけていたとしても、`address`と`address payable`の両方に低レベルの`.call(..)`を使うことができます。ただ推奨はされません。\* 398 | 399 | !!! Note 400 | 401 | ひょっとしたら明白ではないこととして下記のことがあります。`payable`修飾子は、*external* なコントラクトからの呼び出しにのみ適用されます。同じコントラクトの中でpayable関数の中で非payable関数をコールした場合、msg.valueは設定されていますが、非payable関数は失敗しません。 402 | 403 | 404 | 405 | 406 | ## 関数と状態変数の可視性を明示的にマークする 407 | 408 | 関数と状態変数の可視性を明示的にラベル付けします。関数は `external`、`public`、 `internal`、`private`のように指定できます。 409 | それらの違いを理解してください。たとえば、`public`ではなく`external`で十分でしょう。 410 | 状態変数の場合は、`external`は不可能である。可視性を明示的にラベルすると、関数を呼び出すことができるか、変数にアクセスできるかについての間違った前提を簡単にキャッチできます。 411 | 412 | * `External` 関数はコントラクトインタフェースの一部です。External関数`f`を内部的に呼び出すことはできません(つまり、`f()`は機能しませんが、`this.f()` は機能します)。大量のデータを受け取る場合、External関数はより効率的な場合があります。 413 | * `Public` 関数はコントラクトインタフェースの一部であり、内部的にまたはメッセージを介して呼び出すことができます。パブリック状態変数の場合、自動的にgetter関数(下記参照)が生成されます。 414 | * `Internal` 関数と状態変数は、`this`を使用せずに内部のみにアクセスできます。 415 | * `Private` 関数と状態変数は、それらが定義されているコントラクトから参照できますが、派生されたコントラクトからは参照できません。**注**:プライベート変数を含めてコントラクト内にあるものはすべて、ブロックチェーンの外部のすべての閲覧者が見ることができます。\* 416 | 417 | 418 | ```sol 419 | // bad 420 | uint x; // the default is internal for state variables, but it should be made explicit 421 | function buy() { // the default is public 422 | // public code 423 | } 424 | 425 | // good 426 | uint private y; 427 | function buy() external { 428 | // only callable externally or using this.buy() 429 | } 430 | 431 | function utility() public { 432 | // callable externally, as well as internally: changing this code requires thinking about both cases. 433 | } 434 | 435 | function internalAction() internal { 436 | // internal code 437 | } 438 | ``` 439 | 440 | ## プラグマを特定のコンパイラのバージョンにロックする 441 | 442 | コントラクトは、最もよくテストされたものと同じコンパイラ・バージョンとフラグでデプロイする必要があります。 443 | プラグマをロックすると、未知のバグのリスクが高い最新のコンパイラなどを使用して、コントラクトが誤ってデプロイされないようになります。 444 | コントラクトは他の人によってもデプロイされる可能性があり、プラグマはオリジナルの著者が意図したコンパイラのバージョンを示します。 445 | 446 | ```sol 447 | // bad 448 | pragma solidity ^0.4.4; 449 | 450 | 451 | // good 452 | pragma solidity 0.4.4; 453 | ``` 454 | 455 | 注:フローティングプラグマバージョン(例: `^0.4.25`)は`0.4.26-nightly.2018.9.25`で問題なくコンパイルできますが、本番用のコードをコンパイルするためにnightly buildを使用してはいけません。 456 | 457 | ### 例外 458 | 459 | ライブラリまたはEthPMパッケージのコントラクトのように、コントラクトが他の開発者による使用を目的としている場合は、プラグマステートメントをフローティングさせることができます。そうでなければ、開発者はローカルにコンパイルするためにプラグマを手動で更新する必要があるでしょう。 460 | 461 | ## イベントを使用してコントラクトの活動を監視する 462 | 463 | コントラクトのデプロイ後にコントラクトの活動を監視する方法があると便利です。これを実現する1つの方法は、コントラクトのすべてのトランザクションを調べることですが、コントラクト間のメッセージ呼び出しはブロックチェーンに記録されないため、これでは不十分な場合があります。さらに、入力パラメーターのみが表示され、実際の状態の変更は表示されません。また、イベントを使用してユーザーインターフェイスの関数を起動することもできます。 464 | 465 | ```sol 466 | contract Charity { 467 | mapping(address => uint) balances; 468 | 469 | function donate() payable public { 470 | balances[msg.sender] += msg.value; 471 | } 472 | } 473 | 474 | contract Game { 475 | function buyCoins() payable public { 476 | // 5% goes to charity 477 | charity.donate.value(msg.value / 20)(); 478 | } 479 | } 480 | ``` 481 | 482 | ここでは、`Game`コントラクトは`Charity.donate()`への内部呼び出しを行います。このトランザクションは`Charity`の外部トランザクションリストには表示されず、内部トランザクションにのみ表示されます。 483 | たとえ`Game`のトランザクションを見ても、プレイヤーがコインを購入するために費やした金額のみが表示され、`Charity`コントラクトに使用された金額は表示されません。 484 | 485 | イベントは、コントラクトで発生したことを記録するための便利な方法です。発生したイベントは他のコントラクトデータとともにブロックチェーンに残り、将来の監査に使用できます。これは、チャリティの寄付の履歴を提供するためのイベントを使った、上記の例の改良です。 486 | 487 | ```sol 488 | contract Charity { 489 | // define event 490 | event LogDonate(uint _amount); 491 | 492 | mapping(address => uint) balances; 493 | 494 | function donate() payable public { 495 | balances[msg.sender] += msg.value; 496 | // emit event 497 | emit LogDonate(msg.value); 498 | } 499 | } 500 | 501 | contract Game { 502 | function buyCoins() payable public { 503 | // 5% goes to charity 504 | charity.donate.value(msg.value / 20)(); 505 | } 506 | } 507 | 508 | ``` 509 | 510 | ここでは、直接またはそうでないかにかかわらず、`Charity`コントラクトを通過するすべての取引が寄付金額とともにそのコントラクトのイベントリストに表示されます。 511 | 512 | 513 | ## より新しいSolidity構造を用いる 514 | 515 | `selfdestruct`(` suicide`)と `keccak256`(` sha3`以上)のような構造体/エイリアスが好ましいです。 516 | `require(msg.sender.send(1 ether))`のようなパターンは `msg.sender.transfer(1 ether)`のように `transfer()`を使って単純化することもできます。 517 | 同様の変更については[Solidity Change log](https://github.com/ethereum/solidity/blob/develop/Changelog.md)を調べてください。 518 | 519 | ## 「ビルトイン」はシャドーイング出来ることに注意する 520 | 521 | Solidityの組み込みグローバルを[シャドーイング](https://en.wikipedia.org/wiki/Variable_shadowing)することは現在可能です。 522 | これにより、コントラクトは `msg`や` revert() `などの組み込み関数の機能をオーバーライドすることができます。 523 | これは[意図されてのことですが](https://github.com/ethereum/solidity/issues/1249)、コントラクトの真の振る舞いに関してユーザーを誤解させる可能性があります。 524 | 525 | ```sol 526 | contract PretendingToRevert { 527 | function revert() internal constant {} 528 | } 529 | 530 | contract ExampleContract is PretendingToRevert { 531 | function somethingBad() public { 532 | revert(); 533 | } 534 | } 535 | ``` 536 | 537 | コントラクトユーザー(および監査人)は、使用するアプリケーションのスマートコントラクトのソースコード全体を知っておく必要があります。 538 | 539 | ## `tx.origin`を使わない 540 | 541 | `tx.origin`を絶対に使用しないでください。 542 | あなたのコントラクトにcallする方法(例えば、ユーザーに資金がある場合)と、 543 | あなたのアドレスが`tx.origin`にあるようにトランザクションを承認する方法があります。 544 | 545 | ```sol 546 | contract MyContract { 547 | 548 | address owner; 549 | 550 | function MyContract() public { 551 | owner = msg.sender; 552 | } 553 | 554 | function sendTo(address receiver, uint amount) public { 555 | require(tx.origin == owner); 556 | receiver.transfer(amount); 557 | } 558 | 559 | } 560 | 561 | contract AttackingContract { 562 | 563 | MyContract myContract; 564 | address attacker; 565 | 566 | function AttackingContract(address myContractAddress) public { 567 | myContract = MyContract(myContractAddress); 568 | attacker = msg.sender; 569 | } 570 | 571 | function() public { 572 | myContract.sendTo(attacker, msg.sender.balance); 573 | } 574 | 575 | } 576 | ``` 577 | 578 | 承認のために `msg.sender`を使用するべきです 579 | (別のコントラクトがあなたのコントラクトを呼び出す場合、` msg.sender`はコントラクトのアドレスであり、ユーザーのアドレスではありません)。 580 | 581 | 詳細はこちら: [Solidity docs](https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin) 582 | 583 | また、将来 `tx.origin` がEthereumプロトコルから削除される可能性があるので、 `tx.origin`を使用するコードは将来のリリースと互換性がありません。 584 | [Vitalik:'tx.originは引き続き使用可能または意味のあるものと仮定しないでください。](https://ethereum.stackexchange.com/questions/196/how-do-i-make-my-dapp-serenity-proof/200#200) 585 | 586 | `tx.origin`を使うコントラクトは他のコントラクトで使うことができないので、 587 | コントラクト間の相互運用性を制限していることにも言及する価値があります。 588 | 589 | ## タイムスタンプ依存 590 | 591 | タイムスタンプを使用してコントラクト内の重要な関数を実行する場合、特にアクションに資金移動が含まれる場合は、3つの主な考慮事項があります。 592 | 593 | ### タイムスタンプ操作 594 | 595 | ブロックのタイムスタンプはマイナーによって操作される可能性があることに注意してください。こちらの[コントラクト](https://etherscan.io/address/0xcac337492149bdb66b088bf5914bedfbf78ccc18#code)を見てみましょう: 596 | 597 | ```sol 598 | 599 | uint256 constant private salt = block.timestamp; 600 | 601 | function random(uint Max) constant private returns (uint256 result){ 602 | //get the best seed for randomness 603 | uint256 x = salt * 100/Max; 604 | uint256 y = salt * block.number/(salt % 5) ; 605 | uint256 seed = block.number/3 + (salt % 300) + Last_Payout + y; 606 | uint256 h = uint256(block.blockhash(seed)); 607 | 608 | return uint256((h / x)) % Max + 1; //random number between 1 and Max 609 | } 610 | ``` 611 | 612 | コントラクトがタイムスタンプを使用して乱数をシードする場合、マイナーはブロックが検証されてから15秒以内に実際にタイムスタンプをポストできます。これにより、マイナーは宝くじのチャンスに有利なオプションを事前計算することができます。タイムスタンプはランダムではないので、その場合は使用しないでください。 613 | 614 | ### 15秒ルール 615 | 616 | [Yellow Paper](http://yellowpaper.io/) (Ethereum参照仕様)では、時間内にブロックがどれだけ漂う可能性があるかについての制約は規定されていません。 617 | しかし、Yellow Paperでは各タイムスタンプがその親のタイムスタンプよりも大きくなければならないことを[規定しています](https://ethereum.stackexchange.com/a/5926/46821)。 618 | 一般的なEthereumプロトコルの実装である[Geth](https://github.com/ethereum/go-ethereum/blob/4e474c74dc2ac1d26b339c32064d0bac98775e77/consensus/ethash/consensus.go#L45)と[Parity](https://github.com/paritytech/parity-ethereum/blob/73db5dda8c0109bb6bc1392624875078f973be14/ethcore/src/verification/verification.rs#L296-L307)は、どちらも未来において15秒を超えたのタイムスタンプを持つブロックを拒否します。 619 | > コントラクト関数がブロックが15秒間漂うことを許容できる場合は、`block.timestamp`を使用するのが安全です。 620 | 621 | 時間に依存するイベントの規模が15秒変化して整合性を維持できる場合は、タイムスタンプを使用しても安全です。 622 | 623 | ### タイムスタンプとして`block.number`を使用しない 624 | 625 | `block.number`プロパティと[平均ブロック時間](https://etherscan.io/chart/blocktime)を使用して時間差を見積もることは可能ですが、ブロック時間が変わる可能性があるのでこれは将来にわたる証拠とはなりません([fork reorganisations](https://blog.ethereum.org/2015/08/08/chain-reorganisation-depth-expectations/)や[difficulty bomb](https://github.com/ethereum/EIPs/issues/649)など)。 626 | 販売期間中は、15秒のルールにより、より信頼性の高い時間の見積もりを達成できます。 627 | 628 | 629 | ## 多重継承に注意 630 | 631 | Solidityで多重継承を利用するときは、コンパイラが継承グラフをどのように構成するかを理解することが重要です。 632 | 633 | ```sol 634 | 635 | contract Final { 636 | uint public a; 637 | function Final(uint f) public { 638 | a = f; 639 | } 640 | } 641 | 642 | contract B is Final { 643 | int public fee; 644 | 645 | function B(uint f) Final(f) public { 646 | } 647 | function setFee() public { 648 | fee = 3; 649 | } 650 | } 651 | 652 | contract C is Final { 653 | int public fee; 654 | 655 | function C(uint f) Final(f) public { 656 | } 657 | function setFee() public { 658 | fee = 5; 659 | } 660 | } 661 | 662 | contract A is B, C { 663 | function A() public B(3) C(5) { 664 | setFee(); 665 | } 666 | } 667 | ``` 668 | コントラクトがデプロイされると、コンパイラは継承を右から左に *線形化* します(_is_ キーワードに続いて、親コントラクトは最も基底となるものから、派生されているコントラクトまでリスト化されます。)。 669 | これがコントラクトAの線形化です。: 670 | 671 | **Final <- B <- C <- A** 672 | 673 | Cが最も派生したコントラクトであるため、線形化の結果、`fee` の値は5をもたらします。 674 | これは明らかに思われるかもしれませんが、Cが重要な関数をシャドーイングし、ブール項を並べ替え、そして開発者に悪用可能なコントラクトを書かせることができてしまうシナリオを想像してください。 675 | 静的解析は現在、シャドーイング関数に関して問題を提起していないので、手動で検査しなければなりません。 676 | 677 | セキュリティと継承の詳細については、この[記事](https://pdaian.com/blog/solidity-anti-patterns-fun-with-inheritance-dag-abuse/)をチェックしてください。 678 | 679 | コントリビューションの一助となるよう、SolidityのGithubはすべての継承関連の問題を含む[プロジェクト](https://github.com/ethereum/solidity/projects/9#card-8027020) を持っています。 680 | 681 | ## 型安全のためにアドレス型の代わりにインターフェース型を使用する 682 | 683 | 関数が引数としてコントラクトアドレスを取るとき、生の`address`ではなく、インターフェースまたはコントラクトタイプを渡すことをお勧めします。 684 | この関数がソースコード内の別の場所で呼び出された場合、コンパイラは型の安全性をさらに保証します。 685 | 686 | これには2つの選択肢があります。 687 | 688 | ```sol 689 | contract Validator { 690 | function validate(uint) external returns(bool); 691 | } 692 | 693 | contract TypeSafeAuction { 694 | // good 695 | function validateBet(Validator _validator, uint _value) internal returns(bool) { 696 | bool valid = _validator.validate(_value); 697 | return valid; 698 | } 699 | } 700 | 701 | contract TypeUnsafeAuction { 702 | // bad 703 | function validateBet(address _addr, uint _value) internal returns(bool) { 704 | Validator validator = Validator(_addr); 705 | bool valid = validator.validate(_value); 706 | return valid; 707 | } 708 | } 709 | ``` 710 | 711 | 上記の`TypeSafeAuction`コントラクトを使用する利点は、次の例からわかります。 712 | `validateBet()`が`address`引数、またはValidator以外のコントラクトタイプで呼び出された場合、コンパイラは次のエラーをthrowします。 713 | 714 | ```sol 715 | contract NonValidator{} 716 | 717 | contract Auction is TypeSafeAuction { 718 | NonValidator nonValidator; 719 | 720 | function bet(uint _value) { 721 | bool valid = validateBet(nonValidator, _value); // TypeError: Invalid type for argument in function call. 722 | // Invalid implicit conversion from contract NonValidator 723 | // to contract Validator requested. 724 | } 725 | } 726 | ``` 727 | 728 | ## 外部所有アカウントの確認に`extcodesize`を使用しない 729 | 730 | 次の修飾子(または同様のチェック)は、関数呼び出しが外部所有アカウント(EOA)または契約アカウントのどちらから行われたのかを確認するためによく使用されます。 731 | 732 | ```sol 733 | // bad 734 | modifier isNotContract(address _a) { 735 | uint size; 736 | assembly { 737 | size := extcodesize(_a) 738 | } 739 | require(size == 0); 740 | _; 741 | } 742 | ``` 743 | 744 | 考え方は簡単です。アドレスにコードが含まれている場合、それはEOAではなくコントラクトアカウントです。しかし、**コントラクトは自身を構築中に利用可能なソースコードはありません**。 745 | これは、コンストラクタが実行されている間に、他のコントラクトを呼び出すことができますが、そのアドレスに対する`extcodesize`はゼロを返します。以下は、このチェックを回避する方法を示す最小限の例です。 746 | 747 | ```sol 748 | contract OnlyForEOA { 749 | uint public flag; 750 | 751 | // bad 752 | modifier isNotContract(address _a){ 753 | uint len; 754 | assembly { len := extcodesize(_a) } 755 | require(len == 0); 756 | _; 757 | } 758 | 759 | function setFlag(uint i) public isNotContract(msg.sender){ 760 | flag = i; 761 | } 762 | } 763 | 764 | contract FakeEOA { 765 | constructor(address _a) public { 766 | OnlyForEOA c = OnlyForEOA(_a); 767 | c.setFlag(1); 768 | } 769 | } 770 | ``` 771 | 772 | コントラクトアドレスは事前計算できるため、ブロック`n`では空であるが、`n`より大きいあるブロックでコントラクトがデプロイされているアドレスをチェックする場合、このチェックも失敗する可能性があります。 773 | 774 | !!! warning 775 | 776 | 本項は微妙な問題となっています。 777 | 778 | 他のコントラクトが自分のコントラクトにコールできないようにすることを目的としている場合は、おそらく`extcodesize`チェックで十分です。 779 | 別の方法は`(tx.origin == msg.sender)`の値をチェックすることですが、これには[欠点](recommendations/#avoid-using-txorigin)もあります。 780 | 781 | `extcodesize`チェックがあなたの目的にかなう他の状況があるかもしれません。ここでそれらすべてを記述することは範囲外です。ここでそれらすべてを記述することは範囲外です。 782 | EVMの根本的な振る舞いを理解し、ご自身で判断をしてください。 783 | 784 | 785 | 786 | ## 廃止予定/過去の推奨事項 787 | 788 | これらは、プロトコルの変更やsolidityの改善により、もはや関連性のない推奨事項です。後世のためにここに記録されています。 789 | 790 | ### ゼロによる除算に注意 (Solidity < 0.4) 791 | 792 | バージョン0.4より前では、Solidityは[ゼロを返し](https://github.com/ethereum/solidity/issues/670)、数値をゼロで割ったときに例外を`throw`しません。少なくともバージョン0.4を実行していることを確認してください。 793 | 794 | ### 関数とイベントを区別する (Solidity < 0.4.21) 795 | 796 | [v0.4.21](https://github.com/ethereum/solidity/blob/develop/Changelog.md#0421-2018-03-07) において、Solidityは、`emit`キーワードを導入しました。以後は、`emit EventName();`のようにイベントを明示します。0.5.0以降は必須です。 797 | 798 | 関数とイベントの混同のリスクを防ぐために、大文字の使用とイベントの前の接頭辞(*Log*)を推奨します。関数の場合は、コンストラクタを除いて、常に小文字で始めます。 799 | 800 | ```sol 801 | // bad 802 | event Transfer() {} 803 | function transfer() {} 804 | 805 | // good 806 | event LogTransfer() {} 807 | function transfer() external {} 808 | ``` 809 | -------------------------------------------------------------------------------- /docs/security_eips.md: -------------------------------------------------------------------------------- 1 | ### セキュリティ関連EIP 2 | 3 | EVMがどのように機能するかを理解するためであったり、スマートコントラクトシステムを開発する際に適切なベストプラクティスを教示してくれたりする点において、次のEIPを踏まえておくことは大切なことです。 4 | 5 | ただし、下記が網羅しているリストであるとは思わないでください。 6 | 7 | ### Final(正式採用) 8 | 9 | - [EIP 155](https://eips.ethereum.org/EIPS/eip-155) Simple replay attack protection - Ethereum Classicなどの他のチェーン、さまざまなテストネット、またはコンソーシアムチェーンを操作せずに、Ethereumメインネットで機能するトランザクションを送信する方法を提供します。 10 | - [EIP 214](https://eips.ethereum.org/EIPS/eip-214) コントラクトを呼び出している間の状態の変更を禁止しつつ、別のコントラクト(またはそれ自体)を呼び出すために使用できる新しいオペコードを追加します。 11 | - [EIP 607](https://eips.ethereum.org/EIPS/eip-607) Hardfork Meta: Spurious Dragon - EIP 155からSimple Replay Attack Protectionを実装しました。 12 | - [EIP 779](https://eips.ethereum.org/EIPS/eip-779) Hardfork Meta: DAO Fork - 「DAO Fork」という名前のハードフォークに含まれる変更を文書化しています。他のハードフォークとは異なり、DAO Forkはプロトコルを変更しませんでした。 13 | というのは、DAOフォークは、アカウント(「child DAO」コントラクト)のether残高を特定のアカウント(「WithdrawDAO」コントラクト)に移す「イレギュラーな状態変更」でした。 14 | 15 | ### Draft(検討段階) 16 | 17 | - [EIP 1470](https://eips.ethereum.org/EIPS/eip-1470) Smart Contract Weakness Classification - Ethereumスマートコントラクトのセキュリティ上の弱点に対する分類スキームを提案します(SWC) 18 | - [EIP 1051](https://eips.ethereum.org/EIPS/eip-1051) Overflow checking for the EVM - 効率的な検出とオーバーフローの防止を可能にする2つの新しいオペコードを追加します。 19 | - [EIP 1271](https://eips.ethereum.org/EIPS/eip-1271) Standard Signature Validation Method for Contracts - 多くのスマートコントラクトの現在の設計では、 20 | コントラクトがプライベートキーを持たず、したがってメッセージに直接署名することができないため、コントラクトアカウントがスマートコントラクトとやり取りすることができません。 21 | ここでの提案は、アカウントがコントラクトであるときに提供された署名が有効であるかどうかをコントラクトが検証するための標準的な方法の概要を示しています。 22 | -------------------------------------------------------------------------------- /docs/security_notifications.md: -------------------------------------------------------------------------------- 1 | これは、EthereumまたはSolidityで発見されたエクスプロイトを強調するリソースのリストです。 2 | 正式なセキュリティ通知のソースはEthereum Blogですが、多くの場合、脆弱性は他の場所で以前に公開され、議論されます。 3 | 4 | - [Ethereum Blog](https://blog.ethereum.org/): Ethereum公式ブログ 5 | - [Ethereum Blog - Security only](https://blog.ethereum.org/category/security/): *Security* とタグ付けされた全てのブログポスト 6 | - [Ethereum Gitter](https://gitter.im/orgs/ethereum/rooms) チャットルーム 7 | - [Solidity](https://gitter.im/ethereum/solidity) 8 | - [Go-Ethereum](https://gitter.im/ethereum/go-ethereum) 9 | - [CPP-Ethereum](https://gitter.im/ethereum/cpp-ethereum) 10 | - [Research](https://gitter.im/ethereum/research) 11 | - [Smart Contract Security Weekly](https://tinyletter.com/smart-contract-security): Ethereum Smart Contractおよびインフラストラクチャセキュリティについて毎週更新 ([過去の記事](https://tinyletter.com/smart-contract-security/archive)) 12 | - [Reddit](https://www.reddit.com/r/ethereum) 13 | - [Network Stats](https://ethstats.net/) 14 | 15 | これらのソースはすべてコントラクトに影響を与える可能性があるため、定期的にすべてのソースを読むことを強くお勧めします。 16 | 17 | さらに、セキュリティについて書いているかもしれないEthereumコア開発者のリストがあります。 18 | [参考文献](https://github.com/ConsenSys/smart-contract-best-practices#smart-contract-security-bibliography)を参照してください。 19 | 20 | - **Vitalik Buterin**: [Twitter](https://twitter.com/vitalikbuterin), [Github](https://github.com/vbuterin), [Reddit](https://www.reddit.com/user/vbuterin), [Ethereum Blog](https://blog.ethereum.org/author/vitalik-buterin/) 21 | - **Dr. Christian Reitwiessner**: [Twitter](https://twitter.com/ethchris), [Github](https://github.com/chriseth), [Ethereum Blog](https://blog.ethereum.org/author/christian_r/) 22 | - **Dr. Gavin Wood**: [Twitter](https://twitter.com/gavofyork), [Blog](http://gavwood.com/), [Github](https://github.com/gavofyork) 23 | - **Vlad Zamfir**: [Twitter](https://twitter.com/vladzamfir), [Github](https://github.com/vladzamfir), [Ethereum Blog](https://blog.ethereum.org/author/vlad/) 24 | 25 | コア開発者にとどまらず、より広範なブロックチェーン関連のセキュリティコミュニティに参加することが重要です。 26 | セキュリティの開示や観察をさまざまな関係者が行うためです。 27 | -------------------------------------------------------------------------------- /docs/security_tools.md: -------------------------------------------------------------------------------- 1 | ### ヴィジュアライゼーション 2 | 3 | - [Sūrya](https://github.com/ConsenSys/surya) - スマートコントラクトシステムのためのユーティリティツール。コントラクトの構造に関する多数の視覚的な出力と情報を提供します。関数呼び出しグラフの照会もサポートします。 4 | - [Solgraph](https://github.com/raineorshine/solgraph) - Solidityコントラクトの関数の制御フローを視覚化し、潜在的なセキュリティ脆弱性を強調表示するDOTグラフを生成します。 5 | - [EVM Lab](https://github.com/ethereum/evmlab) - EVMと対話するための豊富なツールパッケージ。 VM、Etherchain API、およびトレースビューアを含みます。 6 | - [ethereum-graph-debugger](https://github.com/fergarrui/ethereum-graph-debugger) - グラフィカルEVMデバッガ。プログラム制御フローグラフ全体を表示します。 7 | 8 | ### 静的・動的解析 9 | 10 | - [Mythril Classic](https://github.com/ConsenSys/mythril-classic) - スマートコントラクトセキュリティ用のスイスアーミーナイフ。 11 | - [MythX](https://mythx.io) - EthereumスマートコントラクトSDLC用のセキュリティツール。 12 | - [Slither](https://github.com/trailofbits/slither) - 多くの一般的なSolidity問題の検出機能を備えた静的解析フレームワーク。汚染と値のトラッキングが可能。Pythonで書かれています。 13 | - [Echidna](https://github.com/trailofbits/echidna) - Ethereumソフトウェア用の唯一の利用可能なFuzzer(テストデータの自動生成機能)。プロパティテストを使用して、スマートコントラクトを破る悪意のある入力情報を生成します。 14 | - [Manticore](https://github.com/trailofbits/manticore) - [EVMをサポート](https://asciinema.org/a/haJU2cl0R0Q3jB9wd733LVosL)する動的バイナリ解析ツール。 15 | - [Oyente](https://github.com/melonproject/oyente) - [こちらのホワイトペーパー](http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf)に基づいて、Ethereumコードを分析して一般的な脆弱性を見つけます。 16 | - [Securify](https://securify.chainsecurity.com/) - スマートコントラクト用の完全自動化されたオンラインスタティックアナライザー。脆弱性パターンに基づくセキュリティレポートを提供します。 17 | - [SmartCheck](https://tool.smartdec.net) - セキュリティの脆弱性とベストプラクティスに関するSolidityのソースコードの静的解析。 18 | - [Octopus](https://github.com/quoscient/octopus) - EVMと(e)WASMをサポートするブロックチェーンスマートコントラクト用のセキュリティ解析ツール。 19 | 20 | ### 脆弱性の分類とテストケース 21 | 22 | - [SWC-registry](https://github.com/SmartContractSecurity/SWC-registry/) - SWC(Smart Contract Weakness Classification)の定義と脆弱性を持ったスマートコントラクトの実際のサンプルの大規模なリポジトリ。 23 | - [SWC Pages](https://smartcontractsecurity.github.io/SWC-registry/) - Github Pagesに掲載されたSWCレジストリリポジトリ。 24 | 25 | ### テストカバレッジ 26 | 27 | - [solidity-coverage](https://github.com/sc-forks/solidity-coverage) - Solidityテストのためのコードカバレッジ。 28 | 29 | ### リンター 30 | 31 | リンターは、コードにスタイルと構成規則を適用することでコードの品質を向上させ、コードを読みやすくレビューしやすくします。 32 | 33 | - [Solcheck](https://github.com/federicobond/solcheck) - JSで書かれ、eslintに強くインスパイアされたSolidityのリンター。 34 | - [Solint](https://github.com/weifund/solint) - 一貫性のある規則を守り、Solidityスマートコントラクトのエラーを回避するのに役立くSolidityのリンター。 35 | - [Solium](https://github.com/duaraghav8/Solium) - Solidityのリンター。 36 | - [Solhint](https://github.com/protofire/solhint) - セキュリティとスタイルガイドの両方の検証を提供するSolidityのリンター。 37 | -------------------------------------------------------------------------------- /docs/software_engineering.md: -------------------------------------------------------------------------------- 1 | [基本的な知識](/general_philosophy/)の節で議論したように、既知の攻撃を防ぐだけでは不十分です。 2 | ブロックチェーン上の障害のコストは非常に高くなる可能性があるため、そのリスクを考慮に入れてソフトウェアの書き方に適用する必要があります。 3 | 4 | 私たちが提唱するアプローチは、「失敗に備える」ことです。 5 | コードが安全かどうかを事前に知ることは不可能です。 6 | ただしコントラクトを、失敗しても最小限の損害を与えるように設計することはできます。 7 | このセクションでは、障害の準備に役立つさまざまなテクニックを紹介します。 8 | 9 | 注:システムに新しいコンポーネントを追加すると、常に危険が生じます。 10 | 誤って設計されたフェールセーフが脆弱性になる可能性があります。 11 | 堅牢なシステムを構築するために、コントラクトで使用するそれぞれのテクニックをどう連携するか注意深く検討してください。 12 | 13 | ### 壊れたコントラクトのアップグレード 14 | 15 | !!! warning 16 | このセクションは古くなっています。スマートコントラクトのアップグレード可能性に関連して、多くの重要な問題とリスクがあります。最先端の研究をしてください。私達は[これに関係するissue](https://github.com/ConsenSys/smart-contract-best-practices/issues/164)についての議論を歓迎します。 17 | 18 | エラーが発見された場合、または改善が必要な場合は、コードを変更する必要があります。 バグを発見するのは良いことではありませんが、対処する方法はありません。 19 | 20 | スマートコントラクトのための効果的なアップグレードシステムを設計することは積極的な研究の領域であり、このドキュメントではすべての複雑な問題をカバーすることはできません。 21 | しかし、最も一般的に使用される2つの基本的なアプローチがあります。 22 | 2つのうちの単純なものは、コントラクトの最新バージョンのアドレスを保持するレジストリコントラクトを持つことです。 23 | ユーザーにとってよりシームレスなアプローチは、最新のコントラクトにデータを転送することです。 24 | 25 | どんな技術であれ、コンポーネントのモジュール化と適切な分離が重要であり、コードの変更によって機能が損なわれたり、データが孤立したり、 26 | 移植に多額のコストがかかることはありません。特に、複雑なロジックをデータストレージから分離すると、機能を変更するためにすべてのデータを再作成する必要はありません。 27 | 28 | また、当事者がコードをアップグレードすることを決定する安全な方法を持つことも重要です。 29 | 契約に応じて、コード変更は、単一の信頼できる当事者、メンバーのグループ、または一連のステークホルダーの投票によって承認される必要があります。 30 | このプロセスに時間がかかる場合は、 31 | [緊急停止またはサーキットブレーカー](#_2)など、 32 | 攻撃の際により迅速に対応する他の方法があるかどうかを検討する必要があります。 33 | 34 | **例1: レジストリコントラクトを使用して最新バージョンのコントラクトを保存する** 35 | 36 | この例では、callは転送されないため、ユーザーはcallする前に現在のアドレスを取得する必要があります。 37 | 38 | ```sol 39 | contract SomeRegister { 40 | address backendContract; 41 | address[] previousBackends; 42 | address owner; 43 | 44 | function SomeRegister() { 45 | owner = msg.sender; 46 | } 47 | 48 | modifier onlyOwner() { 49 | require(msg.sender == owner) 50 | _; 51 | } 52 | 53 | function changeBackend(address newBackend) public 54 | onlyOwner() 55 | returns (bool) 56 | { 57 | if(newBackend != backendContract) { 58 | previousBackends.push(backendContract); 59 | backendContract = newBackend; 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | } 66 | ``` 67 | 68 | このアプローチには主に2つの欠点があります。 69 | 70 | 1.ユーザーは常に現在のアドレスを検索しなければなりません。そうしないと、古いバージョンのコントラクトを使用するリスクが発生します。 71 | 2.コントラクトを取り替えるときにデータをどのように扱うかについては、注意深く考える必要があります。 72 | 73 | 別のアプローチでは、最新のコントラクトにデータを転送する必要があります。 74 | 75 | **例2: [Use a `DELEGATECALL`](http://ethereum.stackexchange.com/questions/2404/upgradeable-contracts)を使用してデータを転送する** 76 | 77 | ```sol 78 | contract Relay { 79 | address public currentVersion; 80 | address public owner; 81 | 82 | modifier onlyOwner() { 83 | require(msg.sender == owner); 84 | _; 85 | } 86 | 87 | function Relay(address initAddr) { 88 | currentVersion = initAddr; 89 | owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner 90 | } 91 | 92 | function changeContract(address newVersion) public 93 | onlyOwner() 94 | { 95 | currentVersion = newVersion; 96 | } 97 | 98 | function() { 99 | require(currentVersion.delegatecall(msg.data)); 100 | } 101 | } 102 | ``` 103 | 104 | このアプローチは、以前の問題を回避するが、それ自体の問題を有する。 105 | このコントラクトにどのようにデータを格納するかについては、非常に注意する必要があります。 106 | 新規コントラクトのストレージレイアウトが最初のものと異なる場合、データが破損する可能性があります。 107 | さらに、このシンプルなバージョンのパターンでは、関数から値を返すことはできません。 108 | それらを転送するだけで、その適用性が制限されます。 109 | ([もっと複雑な実装](https://github.com/ownage-ltd/ether-router)は、インラインアセンブリコードと戻り値のレジストリでこれを解決しようとしています。) 110 | 111 | あなたのアプローチにかかわらず、あなたのコントラクトをアップグレードするには何らかの方法があることが重要です。 112 | そうでなければ、不可避のバグが発見されたときには使用できなくなります。 113 | 114 | ### サーキットブレーカー(コントラクトの一時停止) 115 | 116 | サーキットブレーカーは、特定の条件が満たされると実行を停止し、新しいエラーが検出されたときに役立ちます。 117 | たとえばバグが発見された場合、ほとんどのアクションは中断され、現在アクティブなアクションは取り消します。 118 | 特定の関係者にサーキットブレーカーをトリガーする機能を与えるか、 119 | 特定の条件が満たされたときに特定のブレーカーを自動的にトリガーするプログラムルールを与えることができます。 120 | 121 | 例: 122 | 123 | ```sol 124 | bool private stopped = false; 125 | address private owner; 126 | 127 | modifier isAdmin() { 128 | require(msg.sender == owner); 129 | _; 130 | } 131 | 132 | function toggleContractActive() isAdmin public { 133 | // You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users 134 | stopped = !stopped; 135 | } 136 | 137 | modifier stopInEmergency { if (!stopped) _; } 138 | modifier onlyInEmergency { if (stopped) _; } 139 | 140 | function deposit() stopInEmergency public { 141 | // some code 142 | } 143 | 144 | function withdraw() onlyInEmergency public { 145 | // some code 146 | } 147 | ``` 148 | 149 | ### スピードバンプ (遅延契約アクション) 150 | 151 | スピードバンプはアクションを遅くするので、悪意のあるアクションが発生した場合、回復する時間があります。 152 | 例えば、[The DAO](https://github.com/slockit/DAO/)は、DAOを分割する要求が成功してからその能力を得るまでに27日間必要でした。 153 | これにより、資金が契約内に保たれ、回復の可能性が高められました。 154 | DAOの場合、スピードバンプによって与えられた時間の間に取ることができる効果的なアクションはありませんでしたが、他のテクニックと組み合わせて、非常に効果的です。 155 | 156 | Example: 157 | 158 | ```sol 159 | struct RequestedWithdrawal { 160 | uint amount; 161 | uint time; 162 | } 163 | 164 | mapping (address => uint) private balances; 165 | mapping (address => RequestedWithdrawal) private requestedWithdrawals; 166 | uint constant withdrawalWaitPeriod = 28 days; // 4 weeks 167 | 168 | function requestWithdrawal() public { 169 | if (balances[msg.sender] > 0) { 170 | uint amountToWithdraw = balances[msg.sender]; 171 | balances[msg.sender] = 0; // for simplicity, we withdraw everything; 172 | // presumably, the deposit function prevents new deposits when withdrawals are in progress 173 | 174 | requestedWithdrawals[msg.sender] = RequestedWithdrawal({ 175 | amount: amountToWithdraw, 176 | time: now 177 | }); 178 | } 179 | } 180 | 181 | function withdraw() public { 182 | if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) { 183 | uint amountToWithdraw = requestedWithdrawals[msg.sender].amount; 184 | requestedWithdrawals[msg.sender].amount = 0; 185 | 186 | require(msg.sender.send(amountToWithdraw)); 187 | } 188 | } 189 | ``` 190 | 191 | ### レート制限 192 | 193 | レート制限は、実質的な変更を停止するか、または承認を必要とします。 194 | 例えば、預金者は一定期間(例えば、1日に最大100ether)の一定の預金額または一定割合の預金を引き出すことが許可されているだけで、 195 | その期間の追加引き出しは失敗するか、何らかの特別な承認が必要となる 。 196 | またはレート制限は、期間内に発行された一定量のトークンのみで、コントラクトレベルで行うことができます。 197 | 198 | [例](https://gist.github.com/PeterBorah/110c331dca7d23236f80e69c83a9d58c#file-circuitbreaker-sol) 199 | 200 | ### コントラクトロールアウト 201 | 202 | 資金が危険にさらされる前に、コントラクトには相当の長期間のテストが必要です。 203 | 204 | 最低限やるべきこと: 205 | 206 | - 100%のテストカバレッジを持つ完全なテストスイートの作成 207 | - 独自のテストネットに展開する 208 | - テストとバグバウンティプログラムを組みパブリックなテストネットにデプロイ 209 | - 徹底的なテストは、様々なプレイヤーがコントラクトとやりとりできるようにすべきである 210 | - ベータ版でメインネット上に展開し、リスクの大きさ制限する 211 | 212 | ##### 自動廃止 213 | 214 | テスト中は、特定の時間が経過した後で、アクションを防止して自動的に非推奨にすることができます。 215 | たとえば、アルファコントラクトは数週間働いてから、最後の取り消しを除いてすべてのアクションを自動的に停止します。 216 | 217 | ```sol 218 | modifier isActive() { 219 | require(block.number <= SOME_BLOCK_NUMBER); 220 | _; 221 | } 222 | 223 | function deposit() public isActive { 224 | // some code 225 | } 226 | 227 | function withdraw() public { 228 | // some code 229 | } 230 | 231 | ``` 232 | ##### ユーザー/コントラクト毎のEtherの量を制限する 233 | 234 | 初期段階では、あらゆるユーザー(またはコントラクト全体)のEtherの量を制限して、リスクを削減することができます。 235 | 236 | ### バグバウンティプログラム 237 | 238 | バウンティプログラムを実行するためのヒント: 239 | 240 | - どの通貨(BTC,ETHなど)の奨励金が分配されるかを決定する 241 | - バウンティ報酬の推定総予算を決定する 242 | - 予算から、3段階の報酬を決定します。 243 | - あなたが喜んで提供している最小の報酬 244 | - 通常は報酬の高い報酬 245 | - 非常に重大な脆弱性の場合に付与される余分な範囲 246 | - 賞金の裁判官が誰であるかを決定する(3つは理想的かもしれない) 247 | - 鉛の開発者はおそらく賞金の裁判官の1人であるべきです 248 | - バグレポートが受信されると、主任開発者は、審査員の助言を得て、バグの重大度を評価する必要があります 249 | - この段階での作業はプライベートレポにする必要があり、問題はGithub 250 | - 修正する必要があるバグの場合、プライベートレポでは、開発者はテストケースを作成する必要があります。テストケースは失敗し、バグを確認する必要があります 251 | - 開発者は修正プログラムを実装し、今すぐテストに合格する必要があります。必要に応じて追加のテストを書く 252 | - バウンティハンターに修正を表示する。パブリック・リポジトリに修正をマージすることは1つの方法です 253 | - バウンティハンターが修正に関する他のフィードバックを持っているかどうかを判断する 254 | - 賞金裁判官は、バグの *可能性* と *影響* の両方の評価に基づいて、報酬のサイズを決定します。 255 | - 賞金を受け取った参加者にその過程を通して情報を提供し、報酬を送ることの遅れを避けるために努力する 256 | 257 | 報酬の3段階の例については、[Ethereum's Bounty Program](https://bounty.ethereum.org)を参照してください。 258 | 259 | > 払い出される報酬の価値は、影響の重大さによって異なります。 マイナーな「無害」バグの報酬は0.05BTCから始まります。 例えばコンセンサスの問題につながる主要なバグは、最大で5 BTCまで報酬を受けるでしょう。 非常に重大な脆弱性の場合、より高い報酬(最大25 BTC)が可能です。 260 | -------------------------------------------------------------------------------- /docs/stylesheets/extra.css: -------------------------------------------------------------------------------- 1 | .md-header { 2 | background-color: #1F507A; 3 | } 4 | -------------------------------------------------------------------------------- /docs/tokens.md: -------------------------------------------------------------------------------- 1 | 2 | # トークン実装のベストプラクティス 3 | 4 | トークンを実装する際は、他のセキュリティベストプラクティスに準拠する必要がありますが、独自の考慮事項もあります。 5 | 6 | ## 最新の標準規格に準拠する 7 | 8 | 概して、トークンのスマートスマートコントラクトは、広く受け入れられている安定した標準規格に従うべきです。 9 | 10 | 現在受け入れられている標準規格の例は、 11 | 12 | * [EIP20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) 13 | * [EIP721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md) (non-fungibleトークン) 14 | 15 | ## EIP-20に対するフロントランニング攻撃に注意する 16 | 17 | EIP-20トークンの `approve()` 関数は、トークンの消費を承認をされた側が、意図した額を超える額を消費してしまう潜在的リスクをはらんでいます。 18 | [フロントランニング攻撃](./known_attacks/#transaction-ordering-dependence-tod-front-running)を使用することで、トークンの消費を承認をされた側が `Approve()` 呼び出しが処理される前と後の双方において `transferFrom()` を呼び出すことができます。 19 | 詳細については、[EIP](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve)および[このドキュメント](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit)で参照できます。 20 | 21 | ## 0x0アドレスへのトークン転送を禁止する 22 | 23 | 本稿執筆時点では、「ゼロ」アドレス([0x0000000000000000000000000000000000000000](https://etherscan.io/address/0x0000000000000000000000000000000000000000))は、80万ドルを超える値のトークンを保持しています。 24 | 25 | 26 | ## コントラクトアドレスへのトークン転送を禁止する 27 | 28 | スマートコントラクトのアドレスへのトークンの転送もしないようにしてください。 29 | 30 | この脆弱性をオープンのままにしておくことによる損失の可能性の例は、[EOS token smart contract](https://etherscan.io/address/0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0)で、9万トークン以上がコントラクトアドレスに溜まったままになってしまっています。 31 | 32 | 33 | ### 実装例 34 | 35 | 上記の推奨事項を実装する例として、次の修飾子を作成します。 "to"アドレスが0x0でもスマートコントラクト自身のアドレスでもないことを検証します。 36 | 37 | ```sol 38 | modifier validDestination( address to ) { 39 | require(to != address(0x0)); 40 | require(to != address(this) ); 41 | _; 42 | } 43 | ``` 44 | 45 | 修飾子は "transfer"と "transferFrom"メソッドに適用されるべきです: 46 | 47 | ```sol 48 | function transfer(address _to, uint _value) 49 | validDestination(_to) 50 | returns (bool) 51 | { 52 | (... your logic ...) 53 | } 54 | 55 | function transferFrom(address _from, address _to, uint _value) 56 | validDestination(_to) 57 | returns (bool) 58 | { 59 | (... your logic ...) 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Refer to https://github.com/mkdocs/mkdocs/blob/master/mkdocs.yml for a working example 2 | site_name: Ethereum Smart Contract Best Practices(日本語訳) 3 | site_url: https://github.com/msykd/smart-contract-best-practices/ 4 | site_description: 5 | site_author: msykd 6 | repo_url: https://github.com/msykd/smart-contract-best-practices/ 7 | 8 | pages: 9 | - ホーム: index.md 10 | - 基本的な知識: general_philosophy.md 11 | - 推奨する実装方法: recommendations.md 12 | - 既知の攻撃方法: known_attacks.md 13 | - リスクの備え方: software_engineering.md 14 | - トークン固有の推奨事項: tokens.md 15 | - ドキュメント化の手法: documentation_procedures.md 16 | - 便利なツール: security_tools.md 17 | - セキュリティ関連EIP: security_eips.md 18 | - セキュリティに関する情報: security_notifications.md 19 | - スマートコントラクトのセキュリティに関する参考文献: bibliography.md 20 | - スマートコントラクトのバグバウンティプログラム: bug_bounty_list.md 21 | - About: 22 | - Reviewers: about/reviewers.md 23 | - License: about/license.md 24 | - Contributing: about/contributing.md 25 | 26 | theme: 'material' 27 | extra_css: 28 | - 'stylesheets/extra.css' 29 | site_favicon: 'images/favicon.ico' 30 | 31 | 32 | 33 | markdown_extensions: 34 | - markdown.extensions.admonition 35 | - markdown.extensions.def_list 36 | - markdown.extensions.footnotes 37 | - markdown.extensions.meta 38 | - markdown.extensions.toc(permalink=true) 39 | - pymdownx.highlight 40 | - pymdownx.inlinehilite 41 | - pymdownx.arithmatex 42 | - pymdownx.betterem(smart_enable=all) 43 | - pymdownx.caret 44 | - pymdownx.critic 45 | - pymdownx.details 46 | - pymdownx.magiclink 47 | - pymdownx.mark 48 | - pymdownx.smartsymbols 49 | - pymdownx.superfences 50 | - pymdownx.tasklist(custom_checkbox=true) 51 | - pymdownx.tilde 52 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Jinja2==2.9.6 3 | livereload==2.5.1 4 | Markdown==2.6.10 5 | MarkupSafe==1.0 6 | mkdocs==0.17.2 7 | mkdocs-material==2.2.5 8 | Pygments==2.2.0 9 | pygments-lexer-solidity==0.0.2 10 | pymdown-extensions==4.6 11 | PyYAML>=4.2b1 12 | six==1.11.0 13 | tornado==4.5.2 14 | markdown-include 15 | -------------------------------------------------------------------------------- /theme/assets/images/diligence_logo-02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /theme/assets/images/diligence_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /theme/partials/footer.html: -------------------------------------------------------------------------------- 1 | 22 | 23 | {% import "partials/language.html" as lang with context %} 24 | 25 | 26 | --------------------------------------------------------------------------------