diff options
author | Nemil Dalal <nemild@gmail.com> | 2016-06-20 14:56:44 -0400 |
---|---|---|
committer | ven <vendethiel@hotmail.fr> | 2016-06-20 20:56:44 +0200 |
commit | eaf0307775733fddebe308a5a98b7b386f3820cf (patch) | |
tree | 7c303d4b3ae2164ed8b31da4f7755ecf51c8843a /solidity.html.markdown | |
parent | bf32d58d75f640666d67ff3c07b618a5dd83a493 (diff) |
Security additions + edits (#2281)
* Edits with a focus on security risks
* Modified example
* Flip sign - example of the risk of Solidity code when published to an append only ledger
* Fixed formatting
Diffstat (limited to 'solidity.html.markdown')
-rw-r--r-- | solidity.html.markdown | 133 |
1 files changed, 97 insertions, 36 deletions
diff --git a/solidity.html.markdown b/solidity.html.markdown index a511bbb3..9bf5bf4d 100644 --- a/solidity.html.markdown +++ b/solidity.html.markdown @@ -8,7 +8,7 @@ contributors: Solidity lets you program on [Ethereum](https://www.ethereum.org/), a blockchain-based virtual machine that allows the creation and -execution of smart contracts, without needing centralized or trusted parties. +execution of smart contracts, without requiring centralized or trusted parties. Solidity is a statically typed, contract programming language that has similarities to Javascript and C. Like objects in OOP, each contract contains @@ -18,8 +18,17 @@ global variables. Some Ethereum contract examples include crowdfunding, voting, and blind auctions. +There is a high risk and high cost of errors in Solidity code, so you must be very careful to test +and slowly rollout. WITH THE RAPID CHANGES IN ETHEREUM, THIS DOCUMENT IS UNLIKELY TO STAY UP TO +DATE, SO YOU SHOULD FOLLOW THE SOLIDITY CHAT ROOM AND ETHEREUM BLOG FOR THE LATEST. ALL CODE HERE IS +PROVIDED AS IS, WITH SUBSTANTIAL RISK OF ERRORS OR DEPRECATED CODE PATTERNS. + +Unlike other code, you may also need to add in design patterns like pausing, deprecation, and +throttling usage to reduce risk. This document primarily discusses syntax, and so excludes many +popular design patterns. + As Solidity and Ethereum are under active development, experimental or beta -features are explicitly marked, and subject to change. Pull requests welcome. +features are typically marked, and subject to change. Pull requests welcome. ```javascript // First, a simple Bank contract @@ -40,6 +49,7 @@ contract SimpleBank { // CamelCase // Declare state variables outside function, persist through life of contract // dictionary that maps addresses to balances + // always be careful about overflow attacks with numbers mapping (address => uint) private balances; // "private" means that other contracts can't directly query balances @@ -49,7 +59,7 @@ contract SimpleBank { // CamelCase // 'public' makes externally readable (not writeable) by users or contracts // Events - publicize actions to external listeners - event DepositMade(address accountAddress, uint amount); + event LogDepositMade(address accountAddress, uint amount); // Constructor, can receive one or many variables here; only one allowed function AcmeBank() { @@ -65,7 +75,7 @@ contract SimpleBank { // CamelCase // no "this." or "self." required with state variable // all values set to data type's initial value by default - DepositMade(msg.sender, msg.value); // fire event + LogDepositMade(msg.sender, msg.value); // fire event return balances[msg.sender]; } @@ -76,11 +86,14 @@ contract SimpleBank { // CamelCase /// @return The balance remaining for the user function withdraw(uint withdrawAmount) public returns (uint remainingBal) { if(balances[msg.sender] >= withdrawAmount) { + // Note the way we deduct the balance right away, before sending - due to + // the risk of a recursive call that allows the caller to request an amount greater + // than their balance balances[msg.sender] -= withdrawAmount; if (!msg.sender.send(withdrawAmount)) { - // to be safe, may be sending to contract that - // has overridden 'send' which may then fail + // increment back only on fail, as may be sending to contract that + // has overridden 'send' on the receipt end balances[msg.sender] += withdrawAmount; } } @@ -150,8 +163,10 @@ address public owner; // All addresses can be sent ether owner.send(SOME_BALANCE); // returns false on failure -if (owner.send) {} // typically wrap in 'if', as contract addresses have -// functions have executed on send and can fail +if (owner.send) {} // REMEMBER: wrap in 'if', as contract addresses have +// functions executed on send and these can fail +// Also, make sure to deduct balances BEFORE attempting a send, as there is a risk of a recursive +// call that can drain the contract // can override send by defining your own @@ -351,8 +366,11 @@ function b() { // access events from outside blockchain (with lightweight clients) // typically declare after contract parameters +// Typically, capitalized - and add Log in front to be explicit and prevent confusion +// with a function call + // Declare -event Sent(address from, address to, uint amount); // note capital first letter +event LogSent(address indexed from, address indexed to, uint amount); // note capital first letter // Call Sent(from, to, amount); @@ -396,7 +414,10 @@ onlyIfState(State.A) modifier checkValue(uint amount) { _ if (msg.value > amount) { - msg.sender.send(amount - msg.value); + uint amountToRefund = amount - msg.value; + if (!msg.sender.send(amountToRefund)) { + throw; + } } } @@ -409,6 +430,21 @@ modifier checkValue(uint amount) { // Syntax same as javascript, but no type conversion from non-boolean // to boolean (comparison operators must be used to get the boolean val) +// For loops that are determined by user behavior, be careful - as contracts have a maximal +// amount of gas for a block of code - and will fail if that is exceeded +// For example: +for(uint x = 0; x < refundAddressList.length; x++) { + if (!refundAddressList[x].send(SOME_AMOUNT)) { + throw; + } +} + +// Two errors above: +// 1. A failure on send stops the loop from completing, tying up money +// 2. This loop could be arbitrarily long (based on the amount of users who need refunds), and +// therefore may always fail as it exceeds the max gas for a block +// Instead, you should let people withdraw individually from their subaccount, and mark withdrawn + // 7. OBJECTS/CONTRACTS @@ -587,13 +623,13 @@ contract CrowdFunder { address public fundRecipient; // creator may be different than recipient uint public minimumToRaise; // required to tip, else everyone gets refund string campaignUrl; + byte constant version = 1; // Data structures enum State { Fundraising, - ExpiredRefundPending, - Successful, - ExpiredRefundComplete + ExpiredRefund, + Successful } struct Contribution { uint amount; @@ -604,11 +640,11 @@ contract CrowdFunder { State public state = State.Fundraising; // initialize on create uint public totalRaised; uint public raiseBy; + uint public completeAt; Contribution[] contributions; - event fundingReceived(address addr, uint amount, uint currentTotal); - event allRefundsSent(); - event winnerPaid(address winnerAddress); + event LogFundingReceived(address addr, uint amount, uint currentTotal); + event LogWinnerPaid(address winnerAddress); modifier inState(State _state) { if (state != _state) throw; @@ -620,10 +656,13 @@ contract CrowdFunder { _ } + // Wait 6 months after final contract state before allowing contract destruction modifier atEndOfLifecycle() { - if(state != State.ExpiredRefundComplete && state != State.Successful) { + if(!((state == State.ExpiredRefund || state == State.Successful) && + completeAt + 6 months < now)) { throw; } + _ } function CrowdFunder( @@ -651,9 +690,10 @@ contract CrowdFunder { ); totalRaised += msg.value; - fundingReceived(msg.sender, msg.value, totalRaised); + LogFundingReceived(msg.sender, msg.value, totalRaised); checkIfFundingCompleteOrExpired(); + return contributions.length - 1; // return id } function checkIfFundingCompleteOrExpired() { @@ -663,9 +703,9 @@ contract CrowdFunder { // could incentivize sender who initiated state change here } else if ( now > raiseBy ) { - state = State.ExpiredRefundPending; - refundAll(); + state = State.ExpiredRefund; // backers can now collect refunds by calling getRefund(id) } + completeAt = now; } function payOut() @@ -676,22 +716,27 @@ contract CrowdFunder { throw; } - winnerPaid(fundRecipient); + + LogWinnerPaid(fundRecipient); } - function refundAll() + function getRefund(id) public - inState(State.ExpiredRefundPending) + inState(State.ExpiredRefund) { - uint length = contributions.length; - for (uint i = 0; i < length; i++) { - if(!contributions[i].contributor.send(contributions[i].amount)) { - throw; - } + if (contributions.length <= id || id < 0 || contributions[id].amount == 0 ) { + throw; + } + + uint amountToRefund = contributions[id].amount; + contributions[id].amount = 0; + + if(!contributions[id].contributor.send(amountToSend)) { + contributions[id].amount = amountToSend; + return false; } - allRefundsSent(); - state = State.ExpiredRefundComplete; + return true; } function removeContract() @@ -700,13 +745,13 @@ contract CrowdFunder { atEndOfLifecycle() { selfdestruct(msg.sender); + // creator gets all money that hasn't be claimed } function () { throw; } } // ** END EXAMPLE ** - // 10. OTHER NATIVE FUNCTIONS // Currency units @@ -732,8 +777,14 @@ sha3("ab", "cd"); ripemd160("abc"); sha256("def"); +// 11. SECURITY + +// Bugs can be disastrous in Ethereum contracts - and even popular patterns in Solidity, +// may be found to be antipatterns -// 11. LOW LEVEL FUNCTIONS +// See security links at the end of this doc + +// 12. LOW LEVEL FUNCTIONS // call - low level, not often used, does not provide type safety successBoolean = someContractAddress.call('function_name', 'arg1', 'arg2'); @@ -742,7 +793,7 @@ successBoolean = someContractAddress.call('function_name', 'arg1', 'arg2'); someContractAddress.callcode('function_name'); -// 12. STYLE NOTES +// 13. STYLE NOTES // Based on Python's PEP8 style guide // Quick summary: @@ -753,7 +804,7 @@ someContractAddress.callcode('function_name'); // else should be placed on own line -// 13. NATSPEC COMENTS +// 14. NATSPEC COMENTS // used for documentation, commenting, and external UIs // Contract natspec - always above contract definition @@ -773,9 +824,8 @@ someContractAddress.callcode('function_name'); - [Solidity Docs](https://solidity.readthedocs.org/en/latest/) - [Solidity Style Guide](https://ethereum.github.io/solidity//docs/style-guide/): Ethereum's style guide is heavily derived from Python's [pep8](https://www.python.org/dev/peps/pep-0008/) style guide. - [Browser-based Solidity Editor](http://chriseth.github.io/browser-solidity/) -- [Gitter Chat room](https://gitter.im/ethereum/solidity) +- [Gitter Solidity Chat room](https://gitter.im/ethereum/solidity) - [Modular design strategies for Ethereum Contracts](https://docs.erisindustries.com/tutorials/solidity/) -- Editor Snippets ([Ultisnips format](https://gist.github.com/nemild/98343ce6b16b747788bc)) ## Sample contracts - [Dapp Bin](https://github.com/ethereum/dapp-bin) @@ -783,13 +833,24 @@ someContractAddress.callcode('function_name'); - [ConsenSys Contracts](https://github.com/ConsenSys/dapp-store-contracts) - [State of Dapps](http://dapps.ethercasts.com/) +## Security +- [Thinking About Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/) +- [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/) +- [Hacking Distributed Blog](http://hackingdistributed.com/) + ## Information purposefully excluded - Libraries ## Style - Python's [PEP8](https://www.python.org/dev/peps/pep-0008/) is used as the baseline style guide, including its general philosophy +## Editors +- [Vim Solidity](https://github.com/tomlion/vim-solidity) +- Editor Snippets ([Ultisnips format](https://gist.github.com/nemild/98343ce6b16b747788bc)) + ## Future To Dos - New keywords: protected, inheritable +- List of common design patterns (throttling, RNG, version upgrade) +- Common security anti patterns Feel free to send a pull request with any edits - or email nemild -/at-/ gmail |