How to write secure smart contracts?

A review of programming languages and best practises to help you write secure smart contracts.

How to write secure smart contracts?

Or a short history of Solidity: why this language got written the way it was written, why it is horrible for writing smart contracts that handle real money, and how one can write safe smart contracts in Solidity despite these shortcomings.

Solidity is the de-facto king of smart contract languages. Created in 2014 by Gavin Wood, the language has since become widely adopted, and is the most sane and secure (oh, the irony) language for writing blockchain smart contracts. While not the safest language by design, and we'll get into why it is so later in this post, there are essentially no alternatives. There was another language called Serpent, which apparently suffers from major flaws that forced Augur to reissue their tokens and rewrite them in Solidity.

So, what's so cool about this language? How did it reach its dominant market share? Let's take a look at a code example to answer this question.

solidity language code sample

This is what Solidity code for ERC20 tokens looks like

The Good

The first thing you notice is that smart contract code looks like... code. More specifically, like code from C language family, with curly braces, return statements, and semicolons everywhere. In fact, it quite resembles JavaScript, the most popular language in the world.

There's built-in library support. While, for some reason, Solidity creators didn't create a package repository, like all other new languages (Rust, Go, Julia, Ruby, Python, JavaScript, to name a few), at least they allow importing other libraries after you copy-paste them into your codebase (like good ol' C). This promotes code sharing and growing the ecosystem, and also enables developers to chunk projects into smaller parts, which is so important in order to manage project's complexity and reduce number of bugs.

Built-in data types, especially new ones, specific to blockchain-development, feel convenient and are easy to understand.

The Bad

I could go on and on about the things I dislike in this language. Instead, I'm just going to leave some links as to what other prominent developers think about Solidity.

Researchers at Cornell University attribute at least 50% blame for the DAO hack to Solidity. It was almost impossible to write a bug-free version of the DAO with the tools the programmers had.

I would lay at least 50% of the blame for this exploit squarely at the feet of the design of the Solidity language. This may bolster the case for certain types of corrective action.

I refuse to lay blame exclusively on a poorly coded contract when the contract, even if coded using best practices and following language documentation exactly, would have remained vulnerable to attack.

This was actually not a flaw or exploit in the DAO contract itself: technically the EVM was operating as intended, but Solidity was introducing security flaws into contracts that were not only missed by the community, but missed by the designers of the language themselves.

The latest $30 million hack with the standard multisignature wallet developed by Parity was actually caused by Solidity language design. Kind of ironic, because Parity was founded by Gavin Wood, the designer of Solidity language.

And more and more developers are feeling this way.

The Ugly

The ugly part is that it didn't have to be this way. Ethereum Virtual Machine has a fine design. Nobody writes low-level machine code anyway, and it's the high-level languages like Solidity that are supposed to provide developers and users with security guarantees.

There are languages that are perfect for immutable code that handles real money transactions. Examples of such languages are Haskell, Coq, Idris, Lean. However, these languages are hard to write in, and Ethereum creators decided to sacrifice quality and everyone's security for the sake of getting bubble-like mass adoption.

To quote the creator of Bitlattice:

Another factor that was mentioned by authors themselves was the immense Javascript popularity among coders. This assures that a coder won’t flee seeing strange syntax. While this way opens the world of smart contracts to the large community of coders it comes with an adverse effect of attracting people who often code on a very basic level and have poor understanding of how coding works in a broader perspective.

I will be harsh, but if even an idiot can use a certain tool, mostly idiots will use it, as they are so numerous. Nature acts here against responsible development.

lean language code sample

This is a code sample written in a mathematically verifiable language Lean

Can we do something about it?

Of course we can. And we don't even need to hard fork or drop ethereum altogether.

One way to solve this, and the best way, would be to retire solidity and create another language that can produce verifiably correct, bug-free code. As I've mentioned above, such languages do exist, and are hard to write code in (i.e. you won't be able to pick up such a language and start coding real systems in three months). Adoption of such language will ensure that only qualified professionals with world-class education are writing the code, and investors will be able to verify the bug-free nature of it. Sacrifice time-to-market for security and stability.

Another way would be to build security measures around your Solidity code, and this is the approach we're taking with Radex. In short, there are two types of bugs: design bugs and implementation bugs. Design bug means that you underspecified your system and someone more clever than you will be able to abuse a design flaw. Implementation bug means that you had good intentions and good design, but for a variety of reasons you made a typo, the language itself is broken (hello Solidity), and the resulting product has bugs in it. There are ways to tackle those two classes of bugs independently.

In order to prevent design bugs there's only one thing you can do - think before you act. Model your state and interactions in another tool that can help you check and ensure that you don't have design bugs. Only once you've ensured and proved beyond reasonable doubt that your design is flawless should you proceed to implementation.

In order to prevent implementation bugs - write extensive test suites, or even practice test driven development. Write tests for the so called happy path, when things go according to your plan, but also anticipate what could go wrong, and write explicit tests that ensure that your program handles such situations. For example, when you're writing an exchange, you should probably test that one cannot withdraw more funds than they currently own.

Related Articles