Solidity and Smart Contracts Gotchas

Solidity itself is a fairly simple language, on purpose. However, the “mental model” of smart contracts development against the blockchain is unique. We compiled a (by no means complete) list of subtle issues which may be non-obvious, or even confusing at times, to what you expect from a “common” programming environment. Items are not sorted by priority.

This is all the fun, isn’t it? So here is our TOP 10, no wait 62, issues.

[1] Everything that the contract runs on the blockchain, every calculation, costs money, the gas. There is a price for each action the EVM takes on behalf of your contract. Try to offload as much computations as you can to the client. E.g., suppose you want to calculate the average donation in a contract that collects money and counts donations. The contract should only provide the total donations, and the number of donations, then calculate the average in the client code.

[2] Everything the contract stores in it’s persistent storage costs money, the gas. Try to minimise storage only to what absulutly positively is required for the contract to run. Data like derived calculations, caching, aggregates etc, should be handled on the client.

[3] Whenever possible, use events and logs for persistent data. The logs are not accessible to the contract code, and are static, so you can’t use it for code execution on the block chain. But you can read the logs from the client, and they are much cheaper.

[4] The pricing of contract storage is not linear. There is a high initial cost to setting the storage to non zero (touching the storage). Never reset and reintiate the storage.

[5] Everything the contracts uses for temporary memory costs money, the gas. The pricing of using memory, the part that is cleared once execution done (think RAM), is not linear either, and cost per the same unit of usage increases sharply once you used a lot of memory. Try to avoid unreasonable memory usage.

[6] Even when you free storage, the gas you paid for that storage is partially refundble if. Don’t use storage as a temporary store.

[7] Each time you deploy a contract, it costs money, the gas. So the habbit of pushing the whole codebase after every small commit, can cost a lot of money. When possible, try to breakdown the functionality to small focused contracts. If you fix one, you don’t have to re-deploy the others. Use library contracts (see below). Removing a contrat is partially refundble, but less than deployment.

[8] No, sorry. Refunds will never exceed the gas provided in the transaction that initiated the refundble action. In fact, refund is maxed to 50% of the gas in that tansaction.

[9] Whenever you just send money to a contract (a transaction with value > 0), even without calling any function, you run the contract’s code. The contract gets an opportunity to call other functions. Etheruem is different from Bitcoin: even simple money sending runs code (infact Bitcoin has a simple stack based scripts, but the common case is simple money transfers)

[10] Every contract can have one un-named function, the fallback function, which the contract runs when it’s called even if no specific function was named in the transaction. This is the function you will hit when sending money in a transaction that just has value.

[11] If a contract has no fallback function, or it has one without the payable modifier, then a simple transaction that just sends the contract Ether, without calling any function, will fail.

[12] Contracts are saved on the blockchain in a compiled EVM (ethereum virtual machine) bytecode. There is no way to understand from the bytecode what the contract actually does. The only option to verify is to get the Solidity source that the author of the contract claims is, recompile the Source with the same compiler version the contract on the blockchain was compiled, and verify that this compilation matches the bytecode on the blockchain.

[13] Never send Eth to a contract unless you absolutely positively trust it’s author, or you verified the bytecode against a source compilation yourself.

[14] Never call a contract, never call a function of a contract, unless you absolutely positively trust it’s author, or you verified the bytecode against a source compilation yourself.

[15] If you loose the ABI, you will not be able to call the contract other than the fallback function. The situation is very similar to having a compiled executable without the source. When you compile with Populus, it saves the ABI in a project file. The ABI tells the EVM how to correctly call the compiled bytecode and pad the arguments. Without it, there is no way to do it. Don’t loose the ABI.

[16] There is actually a bit convuluted way to call a contract without the ABI. With the address call method it’s possible to call the fallback function, just provide the arguments. It’s also an easy way to call a contract if the fallback is the main function you work with. If the first argument of the call is a byte4 type, this first argument is assumed to be a function signature, and then arguments 2..n are given to this function (but not loosing the ABI is better). To call and forward the entire remaining gas to the callee contract use addr.call.value(x)()

[17] When a contract sends money to another contract, the called contract gets execution control and can call your caller before it returns, and before you updated your state variables. This is a re-entry attack. Therefore, after this second call, your contract runs agian while the state variables do not reflect the already sent money. In other words: the callee can get the money, then call the sender in a state that does not tell that money was sent. To avoid it, always update the state variables that hold the amount which another account is allowed to get before you send money, and revert if the transaction failed.

[18] Moreover, the called contract can run code or recursion that will exceed the max EVM stack depth of 1024, and trigger exception which will bubble up to your calling contract.

[19] Safer, and cheaper for you, to allow withdrawal of money rather than sending it. The gas will be paid by the beneficiary, and you will not have to transfer execution control to another account.

[20] Contracts are stateful. Once you send money to a contract, it’s there. You can’t reinstall, or redeploy (or restart, or patch, or fix a bug, or git pull… you get the idea). If you didn’t provide a mechanism to withdraw the funds in the first place, it’s lost. If you want to update the source of a deployed contract, you can’t. If you want to deploy a new version and didn’t provide a mechanism to send the money to the new version, you are stuck with the old one.

[21] call and delegatecall invoke other contracts, but can’t catch exceptions in these contracts. The only indication that you will get if the call excepted, is when these functions return false. This implies that providing an address of non-existent contract to call and delegatecall will invoke nothing but still no exception. You get true for both successful run and calling non-existent contract. Check existence of a contract in the address, before the call.

[22] delegatecall is a powerful option, yet you have to be careful. It runs another contract code but in the context of your calling contract. The callee has access to your calling contract Eth, storage, and the entire gas of the transaction. It can consume all, and send the money away to another account. You can’t hedge the call or limit the gas. Use delegatecall with care, typically only for library contracts that you absolutely trust.

[23] call on the client is a web3 option that behaves exactly like sending a real transaction, but it will not change the blockchain state. It’s kinda dry-run transaction, which is great for testing (and it’s not related at all to the Solidity call). call is also useful to get info from the current state, without changing it. Since no state is changed, it runs on the local node, saving expensive gas.

[24] Trusted contract libraries are a good way to save gas of repeating deployments, for code that is actually reusable.

[25] The EVM stack limit is 1024 deep. For deeply nested operations, prefer working in steps and loops, over recursions.

[26] Assigning variables between memory and storage always creates a copy, which is expensive. Also any assignment to a state variables. Avoid it when possible.

[27] Assigning a memory variable to a storage variable always creates a pointer, which will not be aware if the underlying state variable changed

[28] Don’t use rounding for Eth in the contract, since it will cost you the lost money that was rounded. Use the very fine grained Eth units instead.

[29] The default money unit, both in Solidity and Web3, like msg.value, or getting the balance, is always Wei.

[30] As of solc 0.4.17 Solidity does not have a workable decimal point type, and your decimals will be casted to ints. If needed, you will have to run your own fixed point calculations (many times you can retrieve the int variables, and run the decimal calculation on the client)

[31] Once you unlock your acount in a running node, typically with geth, the running process has full access to your funds. Keep it safe. Unlock an account only in a local, protected instance.

[32] If you connect to a remote node with rpc, use it only for actions that do not require unlocking an account, such as reading logs, blocks data etc. Don’t unlock accounts in remote rpc nodes, since anybody who manages to get access to the node via the internet can use the account funds.

[33] If you have to unlock an account to deploy contracts, send transactions, etc, keep in this account only the minimum Eth you need for these actions.

[34] Anybody who has the private key can drain the account funds, no questions asked.

[35] Anybody who has the wallet encrypted file and it’s password can drain the account funds, no questions asked.

[36] If you use a password file to unlock the account, make sure the file is well protected with the right permissions.

[37] If you look at your acount in sites like etherscan.io and there are funds in the account, yet localy the account balance is 0 and geth refuses to run actions that require funds for gas - then your local node is not synced. You must sync until the block with the transactions that sent money to this account.

[38] Once the contract is on the blockchain, there is no builtin way to shut it down or block it from responding to messages. If the contract has a bug, an issue, a hack that let hackers steal funds, you can’t shutdown, or go to “maintenance” mode, unless you provided a mechanism for that in the contract code beforehand.

[39] Unless you provided a function that kills the contract, there is no builtin way to delete it from the blockchain.

[40] Scope and visibility in Solidity are only in terms of the running code. When the EVM runs your contract’s code, it does care for public, external or internal. The EVM doesn’t use these keywords, but visibility is enforced in the bytecode and the exposed interface (this is not just a compiler hint). However, the scope visibility definitions have no effect on the information that the blockchain exposes to the outside world.

[41] If you don’t explicity set a payable modifier to a function, it will reject the Eth that was sent in the transaction. If no function has payable, the contract can’t accept Ether.

[42] This is the answer.

[43] It’s not possible to get a list of all the mapping variable keys or values, like mydict.keys() or mydict.values() in Python. You’ll have to handle such lists yourself, if required.

[44] The contract’s Constructor runs only once when the contract is created, and can’t be called again. The constructor is optional.

[45] Inheritence in Solidity is different. Usually you have a Class, a Subclass, each is an independent object you can access. In Solidity, the inheritance is more syntatic. In the final compilation the compiler copies the parent class members, to create the bytecode of the derived contract with the copied memebers. In this context, private is just a notion of state variables and functions that the compiler will not copy.

[46] Memory reads are limited to a width of 256 bits, while writes can be either 8 bits or 256 bits wide

[47] throw and revert terminate and revert all changes to the state and to Ether balances. The used gas is not refunded.

[48] function is a legit variable type, and can be passed as an argument to another function. If a function type variable is not initialized, calling it will obviously result in an exception.

[49] Mappings are only allowed for state variables

[50] delete does not actually deletes, but assigns the initial value. It’s a special kind of assignment actually. Deleting a local var variable that points to a state variable will except, since the “deleted” variable (the pointer) has no initial value to reset to.

[51] Declared variables are implictly initiated to their initial default value at the begining of the function.

[52] You can declare a function as constant, or the new term view, which theoretaclly should declare a “safe” function that does not alter the state. Yet the compiler does not enfore it.

[53] internal functions can be called only from the contract itself.

[54] To access an external function f from within the same contract it was declared in, use this.f. In other cases you don’t need this (this is kinda bonus, no?)

[55] private is important only if there are derived contracts, where private denotes the members that the compiler does not copy to the derived contracts. Otherwise, from within a contract, private is the same as internal.

[56] external is available only for functions. public, internal and private are available for both functions and state variables. The contract’s interface is built from it’s external and public memebers.

[57] The compiler will automatically generate an accessor (“get” function) for the public state variables.

[58] now is the time stamp of the current block

[59] Ethereum units wei, finney, szabo or ether are reserved words, and can be used in experessions and literals.

[60] Time units seconds, minutes, hours, days, weeks and years, are reserved words, and can be used in experessions and literals.

[61] There is no type conversion from non-boolean to boolean types. if (1) { ... } is not valid Solidity.

[62] The msg, block and tx variables always exist in the global namespace, and you can use them and their members without any prior decleration or assignment

Nice! You got here. Yes, we know. You want more. See Writing Contracts