安全性考虑¶

While it is usually quite easy to build software that works as expected,it is much harder to check that nobody can use it in a way that was not anticipated.

In Solidity, this is even more important because you can use smart contractsto handle tokens or, possibly, even more valuable things. Furthermore, everyexecution of a smart contract happens in public and, in addition to that,the source code is often available.

Of course you always have to consider how much is at stake:You can compare a smart contract with a web service that is open to thepublic (and thus, also to malicious actors) and perhaps even open source.If you only store your grocery list on that web service, you might not haveto take too much care, but if you manage your bank account using that web service,you should be more careful.

This section will list some pitfalls and general security recommendations butcan, of course, never be complete. Also, keep in mind that even if yoursmart contract code is bug-free, the compiler or the platform itself mighthave a bug. A list of some publicly known security-relevant bugs of the compilercan be found in thelist of known bugs, which is also machine-readable. Notethat there is a bug bounty program that covers the code generator of theSolidity compiler.

As always, with open source documentation, please help us extend this section(especially, some examples would not hurt)!

Pitfalls¶

Private Information and Randomness¶

Everything you use in a smart contract is publicly visible, evenlocal variables and state variables marked private.

Using random numbers in smart contracts is quite tricky if you do not wantminers to be able to cheat.

Re-Entrancy¶

Any interaction from a contract (A) with another contract (B) and any transferof Ether hands over control to that contract (B). This makes it possible for Bto call back into A before this interaction is completed. To give an example,the following code contains a bug (it is just a snippet and not acomplete contract):

  1. pragma solidity ^0.4.0;
  2.  
  3. // THIS CONTRACT CONTAINS A BUG - DO NOT USE
  4. contract Fund {
  5. /// Mapping of ether shares of the contract.
  6. mapping(address => uint) shares;
  7. /// Withdraw your share.
  8. function withdraw() public {
  9. if (msg.sender.send(shares[msg.sender]))
  10. shares[msg.sender] = 0;
  11. }
  12. }

The problem is not too serious here because of the limited gas as partof send, but it still exposes a weakness: Ether transfer can alwaysinclude code execution, so the recipient could be a contract that callsback into withdraw. This would let it get multiple refunds andbasically retrieve all the Ether in the contract. In particular, thefollowing contract will allow an attacker to refund multiple timesas it uses call which forwards all remaining gas by default:

  1. pragma solidity ^0.4.0;
  2.  
  3. // THIS CONTRACT CONTAINS A BUG - DO NOT USE
  4. contract Fund {
  5. /// Mapping of ether shares of the contract.
  6. mapping(address => uint) shares;
  7. /// Withdraw your share.
  8. function withdraw() public {
  9. if (msg.sender.call.value(shares[msg.sender])())
  10. shares[msg.sender] = 0;
  11. }
  12. }

To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern asoutlined further below:

  1. pragma solidity ^0.4.11;
  2.  
  3. contract Fund {
  4. /// Mapping of ether shares of the contract.
  5. mapping(address => uint) shares;
  6. /// Withdraw your share.
  7. function withdraw() public {
  8. var share = shares[msg.sender];
  9. shares[msg.sender] = 0;
  10. msg.sender.transfer(share);
  11. }
  12. }

Note that re-entrancy is not only an effect of Ether transfer but of anyfunction call on another contract. Furthermore, you also have to takemulti-contract situations into account. A called contract could modify thestate of another contract you depend on.

Gas Limit and Loops¶

Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully:Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due tonormal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the completecontract to be stalled at a certain point. This may not apply to constant functions that are only executedto read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operationsand stall those. Please be explicit about such cases in the documentation of your contracts.

Sending and Receiving Ether¶

  • Neither contracts nor “external accounts” are currently able to prevent that someone sends them Ether.Contracts can react on and reject a regular transfer, but there are waysto move Ether without creating a message call. One way is to simply “mine to”the contract address and the second way is using selfdestruct(x).
  • If a contract receives Ether (without a function being called), the fallback function is executed.If it does not have a fallback function, the Ether will be rejected (by throwing an exception).During the execution of the fallback function, the contract can only relyon the “gas stipend” (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way.To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function(for example in the “details” section in Remix).
  • There is a way to forward more gas to the receiving contract usingaddr.call.value(x)(). This is essentially the same as addr.transfer(x),only that it forwards all remaining gas and opens up the ability for therecipient to perform more expensive actions (and it only returns a failure codeand does not automatically propagate the error). This might include calling backinto the sending contract or other state changes you might not have thought of.So it allows for great flexibility for honest users but also for malicious actors.
  • If you want to send Ether using address.transfer, there are certain details to be aware of:
    • If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract.
    • Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the calldepth, they can force the transfer to fail; take this possibility into account or use send and make sure to always check its return value. Better yet,write your contract using a pattern where the recipient can withdraw Ether instead.
    • Sending Ether can also fail because the execution of the recipient contractrequires more than the allotted amount of gas (explicitly by using require,assert, revert, throw orbecause the operation is just too expensive) - it “runs out of gas” (OOG).If you use transfer or send with a return value check, this might provide ameans for the recipient to block progress in the sending contract. Again, the best practice here is to usea “withdraw” pattern instead of a “send” pattern.

Callstack Depth¶

External function calls can fail any time because they exceed the maximumcall stack of 1024. In such situations, Solidity throws an exception.Malicious actors might be able to force the call stack to a high valuebefore they interact with your contract.

Note that .send() does not throw an exception if the call stack isdepleted but rather returns false in that case. The low-level functions.call(), .callcode() and .delegatecall() behave in the same way.

tx.origin¶

Never use tx.origin for authorization. Let’s say you have a wallet contract like this:

  1. pragma solidity ^0.4.11;
  2.  
  3. // THIS CONTRACT CONTAINS A BUG - DO NOT USE
  4. contract TxUserWallet {
  5. address owner;
  6.  
  7. function TxUserWallet() public {
  8. owner = msg.sender;
  9. }
  10.  
  11. function transferTo(address dest, uint amount) public {
  12. require(tx.origin == owner);
  13. dest.transfer(amount);
  14. }
  15. }

Now someone tricks you into sending ether to the address of this attack wallet:

  1. pragma solidity ^0.4.11;
  2.  
  3. interface TxUserWallet {
  4. function transferTo(address dest, uint amount) public;
  5. }
  6.  
  7. contract TxAttackWallet {
  8. address owner;
  9.  
  10. function TxAttackWallet() public {
  11. owner = msg.sender;
  12. }
  13.  
  14. function() public {
  15. TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
  16. }
  17. }

If your wallet had checked msg.sender for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking tx.origin, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds.

Minor Details¶

  • In for (var i = 0; i < arrayName.length; i++) { }, the type of i will be uint8, because this is the smallest type that is required to hold the value 0. If the array has more than 255 elements, the loop will not terminate.
  • The constant keyword for functions is currently not enforced by the compiler.Furthermore, it is not enforced by the EVM, so a contract function that “claims”to be constant might still cause changes to the state.
  • Types that do not occupy the full 32 bytes might contain “dirty higher order bits”.This is especially important if you access msg.data - it poses a malleability risk:You can craft transactions that call a function f(uint8 x) with a raw byte argumentof 0xff000001 and with 0x00000001. Both are fed to the contract and both willlook like the number 1 as far as x is concerned, but msg.data willbe different, so if you use keccak256(msg.data) for anything, you will get different results.

Recommendations¶

Restrict the Amount of Ether¶

Restrict the amount of Ether (or other tokens) that can be stored in a smartcontract. If your source code, the compiler or the platform has a bug, thesefunds may be lost. If you want to limit your loss, limit the amount of Ether.

Keep it Small and Modular¶

Keep your contracts small and easily understandable. Single out unrelatedfunctionality in other contracts or into libraries. General recommendationsabout source code quality of course apply: Limit the amount of local variables,the length of functions and so on. Document your functions so that otherscan see what your intention was and whether it is different than what the code does.

Use the Checks-Effects-Interactions Pattern¶

Most functions will first perform some checks (who called the function,are the arguments in range, did they send enough Ether, does the personhave tokens, etc.). These checks should be done first.

As the second step, if all checks passed, effects to the state variablesof the current contract should be made. Interaction with other contractsshould be the very last step in any function.

Early contracts delayed some effects and waited for external functioncalls to return in a non-error state. This is often a serious mistakebecause of the re-entrancy problem explained above.

Note that, also, calls to known contracts might in turn cause calls tounknown contracts, so it is probably better to just always apply this pattern.

Include a Fail-Safe Mode¶

While making your system fully decentralised will remove any intermediary,it might be a good idea, especially for new code, to include some kindof fail-safe mechanism:

You can add a function in your smart contract that performs someself-checks like “Has any Ether leaked?”,“Is the sum of the tokens equal to the balance of the contract?” or similar things.Keep in mind that you cannot use too much gas for that, so help through off-chaincomputations might be needed there.

If the self-check fails, the contract automatically switches into some kindof “failsafe” mode, which, for example, disables most of the features, hands overcontrol to a fixed and trusted third party or just converts the contract intoa simple “give me back my money” contract.

Formal Verification¶

Using formal verification, it is possible to perform an automated mathematicalproof that your source code fulfills a certain formal specification.The specification is still formal (just as the source code), but usually muchsimpler.

Note that formal verification itself can only help you understand thedifference between what you did (the specification) and how you did it(the actual implementation). You still need to check whether the specificationis what you wanted and that you did not miss any unintended effects of it.

原文: http://solidity.apachecn.org/cn/doc/v0.4.21/security-considerations.html