μννΈμ¨μ΄λ₯Ό μνλλλ‘ κ°λ°νλ κ²μ μ΄λ ΅μ§ μμ§λ§, λ€λ₯Έ μ¬λμ΄ μμ λ€λ₯Έ μλλ‘ μλμν€λκ±Έ λ§λ κ²μ μ΄λ ΅μ΅λλ€.
μ리λν°μμλ λͺ¨λ μ€λ§νΈ 컨νΈλνΈκ° 곡κ°μ μΌλ‘ μ€νλκ³ λλΆλΆμ μμ€μ½λλ₯Ό νμΈν μ μλ κ²½μ°κ° λ§μ΅λλ€. λ°λΌμ μ΄λ° 보μ μΈ‘λ©΄μ κ³ λ €μ¬νμ νΉν λ μ€μν©λλ€.
λ¬Όλ‘ λ³΄μμ μΌλ§λ μ κ²½μ μ¨μΌνλ μ§λ μν©μ λ°λΌ λ€λ¦ λλ€. κ°λ Ή, μΉ μλΉμ€λ 곡격μλ₯Ό ν¬ν¨ν λμ€μκ² κ³΅κ°λμ΄μΌνκ³ , λꡬλ μ κ·Όν μ μμ΄μΌνλ©°, μ΄λ€ λμλ μ€νμμ€λ‘ κ΄λ¦¬λλ κ²½μ°λ μμ΅λλ€. λ§μ½ μΉ μλΉμ€μ μ€μμΉ μμ μ λ³΄λ§ μ μ₯νλ€λ©΄ λ¬Έμ κ° λμ§ μμ§λ§, μν κ³μ’μ κ°μ μ 보λ₯Ό κ΄λ¦¬νλ€λ©΄ λμ± μ‘°μ¬ν νμκ° μμ£ .
μ΄ μ₯μμλ μ‘°μ¬ν΄μΌν λ¬Έμ λ€κ³Ό μΌλ°μ μΈ λ³΄μκ΄λ ¨ ν¨ν΄λ€μ λ€λ£Ήλλ€. νμ§λ§ μ΄λ μλ²½ν ν΄κ²°λ²μ΄ μλλλ€. μ¦, μ€λ§νΈ 컨νΈλνΈ μμλ λ²κ·Έκ° μλλΌλ, μ»΄νμΌλ¬λ νλ«νΌ μ체μ λ²κ·Έκ° μμ μ μλ€λ μκΈ°μ£ .
μΈμ λ κ·Έλ λ―μ΄, μ΄ λ¬Έμλ μ€ν μμ€ κΈ°λ°μ λ¬Έμμ΄κΈ° λλ¬Έμ, 보μμ λν λ¬Έμ κ° μκΈ΄λ€λ©΄ μ£Όμ μμ΄ λ΄μ©μ μΆκ°ν΄μ£ΌμκΈ° λ°λλλ€.
μ€λ§νΈ 컨νΈλνΈ μμ λͺ¨λ μ 보λ 곡κ°μ μΌλ‘ 보μ¬μ§λλ€. μ¬μ§μ΄ μ§μ λ³μ λ° μν λ³μκ° ``private``μΌλ‘ μ μΈλμλ€κ³ ν΄λ λ§μ°¬κ°μ§μ£ .
λ§μ½ λΉμ μ΄ μ±κ΅΄μμ λΆμ νμλ₯Ό λ§κ³ μ νλ€λ©΄, λμλ₯Ό μμ±νλ κ²μ΄ μ΄λμ λ μ μ©ν μ μμ΅λλ€.
(A)μ½νΈλνΈμμ (B)μ½νΈλνΈλ‘ μ°κ²°λλ μ΄λ ν μνΈμμ© λ° Etherμ μ μ‘μ μ μ΄κΆμ (B)μκ² λκ²¨μ£Όκ² λ©λλ€. μ΄ λλ¬Έμ Bμ μνΈμμ©μ΄ λλκΈ° μ μ λ€μ Aλ₯Ό νΈμΆν μ μλ μν©μ΄ λ²μ΄μ§ μ μμ΅λλ€. μλ₯Ό λ€μ΄, λ€μ μ½λλ λ²κ·Έλ₯Ό ν¬ν¨νκ³ μμ΅λλ€(μμ½λ μ½λμ λλ€).
pragma solidity ^0.4.0; // λ²κ·Έκ° ν¬ν¨λ μ½λμ λλ€. μ¬μ©νμ§ λ§μΈμ! contract Fund { /// 컨νΈλνΈμ Ether μ 보 mapping mapping(address => uint) shares; /// μ§λΆμ μΈμΆνλ ν¨μ function withdraw() public { if (msg.sender.send(shares[msg.sender])) shares[msg.sender] = 0; } }
``send``ν¨μ μ체μμ gasμ μλΉλμ μ μ΄νκΈ° λλ¬Έμ, ν° λ¬Έμ λ λμ§ μμ§λ§, κ·ΈλΌμλ μ΄ μ½λλ 보μ μμ λ¬Έμ λ₯Ό κ°μ§κ³ μμ΅λλ€. Etherμ μ μ‘μ νμ μ½λμ μ€νμ ν¬ν¨νκΈ°μ, μμ μλ λ°λ³΅μ μΌλ‘ ``withdraw``λ₯Ό μ€νν μ μκ²λμ£ . κ²°κ³Όμ μΌλ‘ μ€λ³΅λ ``withdraw``ν¨μμ μ€νμ ν΅ν΄ 컨νΈλνΈ μμ λͺ¨λ Etherλ₯Ό κ°μ Έκ° μ μλ€λ μλ―Έμ λλ€. μν©μ λ°λΌ, 곡격μλ μλ μ½λ μ ``call``μ ν΅ν΄ λ¨μ gasλ₯Ό λͺ¨λ κ°μ Έμ¬ μ μμμ§λ λͺ¨λ¦ λλ€.
pragma solidity ^0.4.0; // λ²κ·Έκ° ν¬ν¨λ μ½λμ λλ€. μ¬μ©νμ§ λ§μΈμ! contract Fund { /// 컨νΈλνΈμ Ether μ 보 mapping mapping(address => uint) shares; /// μ§λΆμ μΈμΆνλ ν¨μ function withdraw() public { if (msg.sender.call.value(shares[msg.sender])()) shares[msg.sender] = 0; } }
μ¬μ§μ 곡격μ λ§κΈ° μν΄μλ μλμ κ°μ΄ Checks-Effects-Interactions ν¨ν΄μ μ¬μ©ν μ μμ΅λλ€.
pragma solidity ^0.4.11; contract Fund { /// 컨νΈλνΈμ Ether μ 보 mapping mapping(address => uint) shares; /// μ§λΆμ μΈμΆνλ ν¨μ function withdraw() public { var share = shares[msg.sender]; shares[msg.sender] = 0; msg.sender.transfer(share); } }
μ¬μ§μ 곡격μ Ether μ μ‘μμ λΏλ§ μλλΌ ν¨μλ₯Ό νΈμΆνλ μ΄λ€ μν©μμλ μνλ μ μμ΅λλ€. λμκ°, μ¬λ¬λΆμ νλμ κ³μ μ λ§μ 컨νΈλνΈλ₯Ό κ°μ§ μλ μμ ν λ°μ, μ΄ λ, νλμ 컨νΈλνΈκ° λ€λ₯Έ 컨νΈλνΈλ₯Ό νΈμΆν μ μλ€λ κ²λ μμλ¬μΌν©λλ€.
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 to
normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete
contract to be stalled at a certain point. This may not apply to constant
functions that are only executed
to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations
and stall those. Please be explicit about such cases in the documentation of your contracts.
- 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 ways
to 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 rely on 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 using
addr.call.value(x)()
. This is essentially the same asaddr.transfer(x)
, only that it forwards all remaining gas and opens up the ability for the recipient to perform more expensive actions (and it only returns a failure code and does not automatically propagate the error). This might include calling back into 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 call
depth, 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 contract
requires more than the allotted amount of gas (explicitly by using
require
,assert
,revert
,throw
or because the operation is just too expensive) - it "runs out of gas" (OOG). If you usetransfer
orsend
with a return value check, this might provide a means for the recipient to block progress in the sending contract. Again, the best practice here is to use a :ref:`"withdraw" pattern instead of a "send" pattern <withdrawal_pattern>`.
External function calls can fail any time because they exceed the maximum call stack of 1024. In such situations, Solidity throws an exception. Malicious actors might be able to force the call stack to a high value before they interact with your contract.
Note that .send()
does not throw an exception if the call stack is
depleted but rather returns false
in that case. The low-level functions
.call()
, .callcode()
and .delegatecall()
behave in the same way.
Never use tx.origin for authorization. Let's say you have a wallet contract like this:
pragma solidity ^0.4.11; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract TxUserWallet { address owner; function TxUserWallet() public { owner = msg.sender; } function transferTo(address dest, uint amount) public { require(tx.origin == owner); dest.transfer(amount); } }
Now someone tricks you into sending ether to the address of this attack wallet:
pragma solidity ^0.4.11; interface TxUserWallet { function transferTo(address dest, uint amount) public; } contract TxAttackWallet { address owner; function TxAttackWallet() public { owner = msg.sender; } function() public { TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance); } }
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.
- In
for (var i = 0; i < arrayName.length; i++) { ... }
, the type ofi
will beuint8
, because this is the smallest type that is required to hold the value0
. 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 functionf(uint8 x)
with a raw byte argument of0xff000001
and with0x00000001
. Both are fed to the contract and both will look like the number1
as far asx
is concerned, butmsg.data
will be different, so if you usekeccak256(msg.data)
for anything, you will get different results.
Restrict the amount of Ether (or other tokens) that can be stored in a smart contract. If your source code, the compiler or the platform has a bug, these funds may be lost. If you want to limit your loss, limit the amount of Ether.
Keep your contracts small and easily understandable. Single out unrelated functionality in other contracts or into libraries. General recommendations about source code quality of course apply: Limit the amount of local variables, the length of functions and so on. Document your functions so that others can see what your intention was and whether it is different than what the code does.
Most functions will first perform some checks (who called the function, are the arguments in range, did they send enough Ether, does the person have tokens, etc.). These checks should be done first.
As the second step, if all checks passed, effects to the state variables of the current contract should be made. Interaction with other contracts should be the very last step in any function.
Early contracts delayed some effects and waited for external function calls to return in a non-error state. This is often a serious mistake because of the re-entrancy problem explained above.
Note that, also, calls to known contracts might in turn cause calls to unknown contracts, so it is probably better to just always apply this pattern.
While making your system fully decentralised will remove any intermediary, it might be a good idea, especially for new code, to include some kind of fail-safe mechanism:
You can add a function in your smart contract that performs some self-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-chain computations might be needed there.
If the self-check fails, the contract automatically switches into some kind of "failsafe" mode, which, for example, disables most of the features, hands over control to a fixed and trusted third party or just converts the contract into a simple "give me back my money" contract.
Using formal verification, it is possible to perform an automated mathematical proof that your source code fulfills a certain formal specification. The specification is still formal (just as the source code), but usually much simpler.
Note that formal verification itself can only help you understand the difference between what you did (the specification) and how you did it (the actual implementation). You still need to check whether the specification is what you wanted and that you did not miss any unintended effects of it.