Welcome to lesson 13!
Security is a priority in all areas of life. It is especially a concern in the online space since today it is easier than ever for anyone to hack into anything. Luckily, we can avoid unwanted uncomfortable situations with the right practices and safety precautional proceedings. This video is especially important because we will show you the security concerns in regards to Solidity and we will also give you recommendations on how to avoid these breaches of security! To find out more about security concerns check out our video below and read the article:
Welcome to lesson 13, in this lesson we will have a look at security concerns and considerations for Solidity language. Here’s a really good quote about security, relevant for this lesson, and it says:
“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.”
So, while Solidity does not look like a hard language to learn, and it is not, there are many bugs lurking just around the corner. We will have a look at common pitfalls in Solidity programming language and ways to go around them.
So the first common pitfall is that everything you use in a smart contract is publicly visible. Even local variables and state variables marked as private. Read access can be hardened by using encryption and only read access can be truly limited.
Re- entrancy is another common pitfall and it’s a type of attack where functions can be called repeatedly before the first invocation of the function is finished. And the way to prevent it is to not perform external calls in contracts before making sure that all internal works have been finished. We will have a look at an example of that soon. And this is also how the famous DAO attack was executed.
Another common pitfall is the gas limit. And loops are especially prone to cause that. Especially loops that do not have a fixed number of iterations for example loops that depend on storage values have to be used carefully since they can grow in size and make gas consumption hit the limits.
Ether send function failure
Another common pitfall to keep in mind is that Ether send function can fail. So when sending money your code should always be prepared for the send function to fail.
The final common pitfall to keep in mind is the timestamp dependency. Never depend on timestamps for critical parts of a code because miners can manipulate stamps.
Now, let’s have a look at some of the most common security recommendations:
Fail as loud as possible
One way to make smart contract safer is to fail early and to fail as loud as possible. This can be done by using exceptions properly. In the example below, we use require and assert exception handlers to check that the name argument, like here, is valid and that the corresponding record in payout, like so, maps to a non-zero value. And if it doesn’t, if none of the exceptions are thrown we simply return the payout of a corresponding name.
Now, function modifiers can be especially useful to make code more readable and still allow it to fail early and loud. In the example below, we have only by modifier which is used by change owner function to only allow the owner of a contract change it to another owner. So here we have a function modifier “only by” which takes a parameter account of data type address and it makes sure that the message sender and the account passed to this function match. And we apply this modifier for the change owner function by passing the owner parameter to only by function modifier and what this does is that it only allows the owner of this contract to execute the change owner function.
Checks- effects- interactions
The next security recommendation is to make use of checks-effects-interactions pattern. Checks- effects- interactions pattern is a simple and a well recommended coding pattern where your first check all pre-conditions by using assert and require. Then, make changes to contract state and only then finally interact with other contracts via external calls.
Now, consider the example below which introduces attack surface for a re-entrancy attack. In this example, the recipient could call withdraw multiple times before withdraw is finished executing and as a result get multiple refunds.
So as you can see here we have a function withdraw and it has another function within: send, which sends a given amount of shares to the sender of this message. Now this function is an external function and while it’s executing the recipient could call this withdraw function again over and over and get the refund multiple times. Only once this function is finished executing we reach this line where we assign the shares of the message sender to 0. As you can see this is quite problematic and this is how re-entrancy attack works. But by using checks-effects-interactions pattern, we can avoid this problem as in the example below. So before transferring the funds we make sure that the receiver has any funds to receive at all, like here. And if he or she does, we set his or her balance to 0 and only then make the token transfer. So even if withdraw is called again before token transfer is finished executing the corresponding account balance will already be set to 0, like we did here, and only then, after it’s set to 0, after the balance is set to 0 we make the token transfer. And this will prevent the re-entrancy attack.
Restrict Ether amount
Now, another security recommendation is to restrict the amount of Ether or other tokens that can be stored in a smart contract. So if your source code the compiler or the platform has a bug, these funds may be lost. But if you want to limit your loss you can just limit the amount of Ether stored in the contract. Also attackers are less likely to try to mess with contracts with low balances and will instead obtain with contracts holding millions in funds.
So here we have an example of a limit funds contract, which has a limit of 5000, so if we try to deposit some funds to this contract and if we go over the limit it will throw an exception. So this makes sure that the balance does not go over 5000 because this is the limit that we are comfortable with to keep in this contract.
Keep contracts small and easy
Another security recommendation is to keep contracts small and easily understandable. So single out unrelated functionality in other contracts or into libraries also general software engineering recommendations apply, such as limit the amount of local variables, limit length for function and document all the functions so that others can see what your intention was and whether it is different than what the code does.
Ethereum virtual machine limits
Now, the final security recommendation is to mind Ethereum virtual machine limits. The Ethereum virtual machine has a fair chunk of limits that one should be mindful of. For example integers can easily overflow or underflow or gas limit could be reached and all the work done until that point will be reverted and thus lost. So here we have an example of a problematic code contract which calculates payouts for affiliates stored in the affiliates array and then sends payouts to them. And the payouts are calculated in the calculate payout function which we assume is quite expensive to compute. So while the above example looks simple enough, it hides two potential flaws:
- First since unsigned integer 8 is the lowest sufficient data type to hold value 0 this is what variable “I” will be initialized to as you can see here. However, since unsigned integer 8 is made up of 8 bits it can only represent 255 distinct values. Thus if affiliates array goes over 255 elements we are going to experience integer overflow and as a result never terminate the loop. This can be fixed by declaring unsigned integer which defaults to 256 bits and thus can store huge values.
- Another problematic area with the above example is potential gas limit. Imagine a scenario where we are calculating payout and sending it out for hundreds of affiliates. We have processed 90% of them but then ran out of gas. As a result all the work we have done up to that point will be for naught because Ethereum virtual machine will revert all the changes to the state in which it was before calling this function. Now, the best way to go about the gas limit problem is to separate the payout in calculation functions and first do the payouts calculation and only then do the payouts. Then if either one fails at least not as many resources will be lost.
So as you can see here:
- first we calculate the payouts separately and store the payout that needs to be payed out in a payouts array.
- and then we have another function: pay affiliates, which only has to retrieve the amount to be paid to the affiliate and then sends that amount.
So this is the separation of concerns that is really useful in this situation.
So, this is it for security recommendations. There are many more and you can expect the resources area of our Github repository to see more. But this should give you a solid ground to at least be familiar with the security concerns in Solidity.
In the next lesson, our final lesson, we will learn about the differences between deployment on test network and main network, and I will deploy the dummy token contract on the main network and show you how to interact with it.
I’ll see you there!