What is blockchain? What are smart contracts? How do smart contracts work? What kinds of vulnerabilities exist in smart contracts, and most importantly, how do you find them?
This is an introductory guide and an external resources page (v0.1) for learning about the basics of blockchain, smart contracts, and smart contract security on a more conceptual level. The goal is to provide a launching pad for hackers, researchers, devs, and companies to quickly get up to speed on the technology, so they can start hunting for bugs and collecting bounties. Since the field is changing by the day, this guide will be updated regularly. Resources that expand on the various topics are available at the bottom of the page.
The fundamental idea of Blockchain is that it functions as a public, decentralized database that records transactions, which are chained together as blocks, in a permanent way. There are different kinds of blockchains on the market, and Bitcoin and Ethereum are two of the most important. The most relevant blockchain to us is Ethereum, since that’s the blockchain which virtually all smart contracts and distributed applications (DApps) use, so that’s what we’ll focus on here.
A smart contract is a self-executing contract, where the terms are written in code. As smart contract inventor Nick Szabo defined the concept in a 1996 whitepaper, “A smart contract is a computerized transaction protocol that executes the terms of a contract. The general objectives of smart contract design are to satisfy common contractual conditions (such as payment terms, liens, confidentiality, and even enforcement), minimize exceptions both malicious and accidental, and minimize the need for trusted intermediaries.”
101 Blockchains has a helpful reference graphic and article which explains what smart contracts are, how they work, what the benefits are, and what they might be used for.
Obviously, Szabo’s definition is a fairly high-level one. But, as shown in the graphic, it’s not hard to think of potential applications for smart contracts. In fact, smart contracts have been used in supply chain management, ICOs, decentralized finance (DeFi) projects, and more. It’s a very new form of application, but the development ecosystem has exploded in size and so have the risks. A single bug can cost smart contract developers millions of dollars and ruin a company’s reputation overnight. The stakes are high, and that’s why so much attention is shifting from web apps to smart contracts. While web apps have developed stronger security standards over time, the smart contract space is largely unexplored and underprotected.
There are two main types of accounts on the Ethereum blockchain: externally owned accounts and contract accounts.
An externally owned account is when a user creates a public address and maintains a private key associated with that address. This address can hold ether and interact with contracts on the blockchain. A typical address, based on hexadecimal format, looks like this: 0xb794f5ea0ba39494ce839613fffba74279579268.
A contract account, on the other hand, has a public address on the network and also contains smart contract code.
How do you get started with a Solidity developer environment? HackerNoon has one of the better guides to setting up the environment, using the basics of Solidity, deploying your first smart contract, and accessing that contract through any modern web browser. For quickly running test contracts without having to set up a full environment on your own system, you can also use Remix, a browser-based IDE.
Let’s jump into a few important concepts in blockchain and smart contract development that will speed up the learning process.
Just because you create and deploy a smart contract doesn’t mean that you have any privileged access or rights with respect to that contract. By default, your interaction with that contract on the blockchain will be the same as everyone else’s. However, it’s common in actual smart contracts for there to be functions where an owner role is involved, whether in running an auction or a lottery draw. Since this owner doesn’t exist by default, it has to be created. Ownership is about access control. Who has access control on the smart contract?
OpenZeppelin’s Ownable library is one of the most common ways to implement ownership, and it allows functions like transferring ownership or renouncing it. Permissions can of course get more granular with the creation of multiple roles.
Ownership can also be abused, such as in the case where an owner of a token can arbitrarily create an unlimited number of tokens, and also where you accidentally allow users to call functions that should be reserved for an admin.
There are a number of different Ethereum standards that you’ll encounter as you become more familiar with smart contracts, so it’s helpful to get a head start on exactly what these standards and what they entail.
The most common standard is ERC-20, and it describes the rules that smart contract tokens have to follow. In simple terms, an ERC-20 token is a fungible token, meaning that it functions like a $20 bill. Every $20 bill can be exchanged for another $20 bill. They are interchangeable. One of the reasons why this standard was created was to give some kind of clear and consistent logic to the proliferation of tokens on the blockchain operating according to different rules and creating chaos. This guide on Blockgeeks shows the three optional rules and six mandatory rules that are part of the ERC-20 standard. One of the mandatory rules, totalSupply, specifies exactly how many tokens are in circulation.
Another common standard which is seeing a lot of use is ERC-721, which is a non-fungible token standard. Unlike in the ERC-20 standard, in this case, tokens are not interchangeable, which creates all kinds of interesting use cases. This standard is used to establish authenticity of ownership over scarce digital resources, like collectibles or art.
Solidity, like other object-oriented programming languages, has a concept of inheritance, which can be applied to contracts. There can be parent contracts and child contracts, which inherit from parent contracts.
There are at least two reasons why you might use inheritance in smart contract development and analysis. First, it allows changes in one parent contract to be reflected in child contracts. And second, it allows you to reuse code and reduce overall program length.
Interfaces are a specific kind of inheritance with important rules. They cannot define state variables or constructors, functions have to be external, and interfaces cannot themselves inherit from other contracts.
The most important and relevant topic, though, is how to find bugs in smart contracts. The first point to make is that smart contracts are full of bugs for three simple reasons: they are immutable, hold value, and are fully transparent. Vulnerabilities in smart contracts, then, are easy to make, easy for other parties to view, and can be difficult to fix once these contracts already hold value.
The resources below feature encyclopedia-grade explanations of various smart contract vulnerabilities, and it’s recommended that you read through them. For now, we’ll stick to discussing the most common and/or important smart contract vulnerabilities that cause a lot of damage, so you can get started finding bugs.
Reentrancy is when a program’s execution can be interrupted and re-run from a particular point, a vulnerability made famous in the DAO hack. One way this vulnerability could manifest is if a bank’s wire system only checked an account’s balance at the beginning of the wire, as opposed to also checking the balance when the wire is about to be sent, in order to ensure sufficient funds. This could lead to a reentrancy attack that calls the send function multiple times to send more funds than are available in the account.
In other words, this is an issue of temporality where a send function can be called before a check balance function is called.
In smart contracts, a function will make an external call, which if not done just correctly will allow an untrusted called contract to perform a re-entrancy attack on the original contract.
Coinmonks, Quantstamp, and DASP have excellent examples and analyses of reentrancy attacks that dive further into the code.
Integer Overflows and Underflows
Incorrect use of arithmetic in smart contracts can lead to integer overflow or integer underflow. In simple terms, an overflow is when there’s an output larger than allowed and an underflow is when there’s an output smaller than is allowed. If overflows and underflows aren’t accounted for in the code, then inputs and outputs will be vulnerable to attack.
In Ethereum, an unsigned integer (uint) can range anywhere from 0 to 115792089237316195423570985008687907853269984665640564039457584007913129639935, which is the maximum uint value of (2^256)-1. An overflow, then, is when 1 is added to that limit, at which point the overflow pushes the number back to 0. An underflow is when 1 is subtracted from 0, pushing the number from 0 to the top, which is 115792089237316195423570985008687907853269984665640564039457584007913129639935.
You can see that an underflow attack is most practical. If a user has a balance of 0 and sends a token, the balance will revert to ~10^77, which then can be sent to a wallet. The things to look for are: who has access to the uint? How much can it be incremented at a time? Is the uint a smaller data type like uint8 or uint 16?
Generally, the best way to fix this is to just use Open Zeppelin’s safe math library or use at least version 0.8.0, as overflow/underflow checks were added into Solidity. However, these checks often increase gas usage (transaction fees), and so many developers disable these checks, which is bad practice.
Since the blockchain is radically transparent, unconfirmed transactions sit in the mempool and are visible before they are confirmed and placed in a block. This presents a problem if a particular smart contract performs functions based on transactions received in a particular order. If several unconfirmed transactions are both in the mempool and visible, this raises the possibility that higher transaction fees (transaction fees are referred to as gas) could be paid to prioritize certain transactions over others. There are at least three different types of front-running: displacement, insertion, and suppression. This is one of the most difficult problems to fix. You can read more about front-running at ConsenSys’ guide on Known Attacks and from security researcher Samczsun.
With the ability to use flashloans to mint a near-infinite amount of ETH, we’ve seen hackers exploit vulnerabilities where a token/protocol relies too heavily on a live price feed. Flashloans can be used to skew the weighting of a token pair on AMMs (Automated Market Makers) like Uniswap or Balancer, so that the “price” of a token drastically increases or decreases. Exploiters are then able to use this manipulated price to interact with the flawed token/protocol, typically withdrawing funds using an increased token price to receive more funds than they should.
A common solution is to use a time-weighted price feed, so that price is averaged out over X time periods. Not only does this prevent flashloan manipulation, it reduces the chance you can be front-run, as an order executed right before yours won’t have as drastic an impact on price.
One tool that gathers Uniswap price feeds every thirty minutes is Keep3r. If you’re looking to build a custom solution, Uniswap provides a sliding window example.
This ends the introductory guide for getting started on Solidity and smart contract security. The current version is v0.1, so be sure to check back in the future for more updates. Below is a list of helpful resources for diving deeper into smart contract development and security. Please note that some of the links reference versions of Solidity in their examples that are not the current version of the language (0.8.0), meaning there will be slight differences with code that is more up to date. Always refer to the official Solidity documentation to double check what you’ve learned.