Of perhaps greatest importance in the world of DeFi is the existence of smart contracts. These endlessly executing programs form the basis of being able to perform verifiable and distributed computation in a financially-secure nature. It is this exact importance however that make them such attractive targets to malicious actors who desire to perform attacks that will potentially earn them wealth. It is also this exact importance, and the often fragile disposition of smart contracts, that leave them so vulnerable to even unintended vulnerabilities exploited by genuine actors with no complete understanding of the consequences. Therefore, smart contracts can be compared to 'glass cannons'; enormously powerful in their applications, but equally as weak to a kaleidoscope of often unforeseen dangers. Any improvements of these structures hence must involve a concentrated focus on their security, including an analysis of accepted patterns, warnings of dangerous approaches, and perspectives on where the optimal application of smart contracts exist.
Contents:
FAQ
The State of Smart Contract Security
The Causes of Smart Contract Security Flaws
Common Vulnerabilities
The Recommended Way to Send Ether
Summary of Contract Design Patterns
Preventative Methodologies
The Effect of The Merge
FAQ
What are smart contracts?
Hosted on a blockchain, smart contracts are distributed and trustless forms of computation that can be verified before being run. They can be thought of as programs or web applications written in a specific format. Once they are on the blockchain as well it is intended that they will be endlessly executing, waiting for any input without discrimination. This means they are particularly versatile and important as a foundation for verifiable computation. Often they can operate with regards to managing finances with no interaction from any staff. As an extension of their trustless and distributed nature though they can be seen to be vulnerable to a number of unintended exploits.
What was the re-entrancy attack?
Re-entrancy attacks describe a class of attacks wherein by the object-oriented nature of the EVM (and the Solidity language as a whole) certain aspects of the smart contract can be accessed in an unintended order. For instance, a malicious actor may be able to call a withdrawBalance() method multiple times and receive funds before any element of the smart contract performs a check to see if the actor has the specified funds or to update the actor’s account. This was what precisely happened during the re-entrancy attack against The DAO, and allowed ~$70M of funds to be stolen (though in a controversial manner this attack was reversed at a later date).
Why are smart contracts potentially vulnerable?
Regardless of any other faults with the design of Solidity or the EVM these aren’t specifically responsible as the root of smart contract vulnerabilities. Instead it comes down to two key features of smart contracts which are also their most significant features: decentralisation, and immutability. Decentralisation means that any users across the entire blockchain can view and inspect the smart contract without any restrictions - finding vulnerabilities are far easier this way than attempting to inspect proprietary software for instance. Immutability means that even if a developer or a genuine actor discovers a fault that needs to be rectified smart contracts cannot be edited once deployed - once a vulnerability is found it cannot be removed.
What is the optimal way to transfer funds on Ethereum?
Despite call() being the method that was used commonly prior to, and exploited by, the re-entrancy attack, it has been recently recommended that this is returned to being the ideal form of transferring funds. These recommendations were made based on recent changes to the EVM wherein a commonly used opcode for transfer() and send() functions, SLOAD, had its assigned gas cost increased. This has now meant that running either of these two functions is more expensive for users. To ensure that returning to call() does not result in further instances of re-entrancy attacks users are urged to apply the checks-effect-interaction software pattern (as is often recommended with object-oriented paradigms), which will mean that any meaningful effect such as the transfer of founds won’t occur till all necessary validations have been made.
What are some common smart contract software patterns?
One of the primary categories of software design patterns often cited for use in smart contracts are those of the ‘proxy contract’ category. Within this category are more specific patterns, including: clone factory contracts, and the diamond standard. In general the concept is to have different layers of smart contracts, where a front-facing contract for instance might receive all function calls and pass them off to other contracts to perform the computations. These patterns have the advantage of allowing a greater opportunity for updating smart contracts over time as one only needs to replace the front-facing contract to have it point at new contracts.
What steps should be taken to avoid smart contract exploits?
There are four steps any developers can take to move a vulnerable smart contract to being a secure smart contract. Initially developers can adhere to safe design principles, and analyse lists of known vulnerabilities. Then they can use specialised software that search for vulnerabilities and perform tests that the developers may have initially missed - though these programs can’t necessarily be relied upon as no software will be as efficient as a person. Auditors can eventually be brought in, who excel in the field of inspecting smart contracts for vulnerabilities. After this step an alpha or beta version of the smart contract might be published with a bug bounty attached to incentivise the community to discover exploits they may be found through stress testing of each end-point. Upon completion of all four of these steps a smart contract could be said to have completed a transition to probabilistically-safe computation, especially in contrast to any prior vulnerability.
The State of Smart Contract Security
Smart contracts can be developed for different purposes and can execute transactions with digital assets of substantial value. Hence, they are subject to complex and intricate exploitations. A high-level programming language such as Solidity, features abstraction but comes with security drawbacks.
Hackers siphoning millions of dollars out of vulnerable smart contracts are a weekly occurrence. However, it isn’t exactly clear how much in total vulnerabilities have cost smart contract developers and users. If the money lost isn’t substantial, smart contract exploits are often left undocumented. Incidents can occur in not just Ethereum but several different and even lesser known cryptocurrencies that support smart contracts.
The price of cryptocurrencies is also too volatile to have accurate reports. Since there is no standardisation on evaluating damages, several articles may report different losses on the same incident. If the DAO hack was reported in January 2022, a hyperbolic example would estimate a loss of \$9 billion USD from the 3.6 million ether stolen. Research papers and security companies do report total losses in blockchain ecosystems however, there isn’t a consensus, and values reported can range from hundreds of millions to tens of billions.
But this isn’t to say that blockchain security, particularly smart contract security, isn’t important. The nature of the blockchain ecosystem makes not so smart contracts more exploitable, but many tools and services are available that prevent hacks and mitigate damages.
The following is an introduction to (somewhat amusing) incidents that have cost users tens or hundreds of millions of dollars due to simple smart contract bugs.
Parity Wallet Bug
The “I accidentally killed it” incident
- Parity is an Ethereum client integrated into a user’s web browser. Users can access features of the Ethereum network including dApps and token wallet functions
- In late 2017, an updated Parity Wallet library contract was deployed to patch a multi-signature vulnerability
- The new contract developed contained a new vulnerability where a user could turn the library contract into a regular multi-sig wallet and become the owner of it by calling an initialisation function
- A newbie user, devops199, stumbled across this vulnerability and dipped his toe into the exploit
- The new (illegitimate) owner of the wallet then executed self-destruct on the contract
- devops199 (username now deleted) self-reported this on a thread in GitHub
The Outcome
- Parity multi-sig wallets depended on this library (that is now dead) and the address of the library was hardcoded into the wallets
- This resulted in 587 wallets being affected, freezing 514k ETH + some other tokens, totalling a loss of \$156 million at that time
User devops199 comes clean on Gitter. Source
The Meltdown of IRON Stablecoin
“Bad ideas are born everyday”
- Iron Finance’s stablecoin IRON, is a partially collateralised token that is soft pegged to the USD. It consists of USDC and an internal collateral token, TITAN
- Through the Iron treasury, users could mint or redeem IRON for \$0.75 USDC and \$0.25 worth of TITAN
- At its peak, this DeFi protocol was worth \$2 billion USD
- This token saw its end when whales started selling off TITAN causing a large panic sell. This domino effect caused the “world’s first large-scale crypto bank run” as stated in the post mortem by Iron Finance
Market history of TITAN before the complete crash. Source
The Outcome
- The price of TITAN fell to \$0 from an all-time high of \$64. The peg collapsed and IRON was priced at \$0.71 on the market
- The IRON contract still had \$272 million worth of USDC. So, there was still \$0.74 of USDC for every 1 IRON in the Iron Treasury. But people were not recovering their \$0.74... why?
require(_share_price > 0, "Invalid share price");
- The above line of code is from their redeeming function in the smart contract
_share_price
is the price of TITAN which is taken from an oracle and was reporting a price of... 0- Since the specified condition is
>
rather>=
, all external calls to the redeem function fails. This essentially locked up \$272 million.
From Iron Finance,
Because the TITAN price falls to 0 which we have unthought of, the contract will revert the redemption transaction. We will need to schedule a timelock transaction to have a temporary fix and you can redeem again. The timelock delay is 12 hours. We will inform later in this chat the exact time you can redeem.
The Uranium Finance Fallout
A very expensive typo
- Uranium Finance is a Binance Smart Chain-based DeFi project
- It is a fork of Uniswap V2, so an automated market maker (AMM) protocol but it additionally provides users with daily dividends
- The developers extensively copied from Uniswap but they made a crucial error in their Uranium v2 release
The following lines are from the original code that Uranium forked.
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reseve1).mul(1000**2), 'UniswapV2: K');
The next bit of code is from the Uranium developers.
uint balance0Adjusted = balance0.mul(10000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(10000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reseve1).mul(1000**2), 'UniswapV2: K');
- Notice the difference? The value 1000 was replaced with 10000 in the first two lines but not at the end
The Outcome
- This bad case of copy paste created a vulnerability where an attack could swap a very small amount of the input token for 98% of the total balance of the output token
- Uranium reported losing \$50 million in various crypto including BTC, ETH, BUSD, USDT, ADA, DOT, WBNB, and U92
(1/2)‼️ Uranium migration has been exploited, the following address has 50m in it The only thing that matters is keeping the funds on BSC, everyone please start tweeting this address to Binance immediately asking them to stop transfers.
— Uranium Finance (@UraniumFinance) April 28, 2021
Qubit Finance Exploit
PancakeBunny developers are repeat offenders
- Qutbit finance is a BSC-based lending and borrowing platform developed by the same team behind PancakeBunny
- They offer X-Collateral, a cross-chain feature that allows users to collateralise their assets like Ethereum to borrow against on another chain like BSC
- A newly deployed
depositETH()
function in the QBridgeHandler smart contract was implemented, deprecating a previously used function calleddeposit()
- However,
deposit()
was left in the contract and it had a simple vulnerability in the following line
tokenAddress.safeTransferFrom(depositer, address(this), amount);
- The transaction doesn’t revert when the
tokenAddress
is 0, meaning the deprecated deposit function could end normally regardless of the amount WETH owned by the caller - What made it worse was that the contract treats the
deposit()
event as a valid deposit of WETH because the functionsdeposit()
anddepositETH()
emit the same event
The Outcome
- An attacker received some funds from Tornado Cash just before the hack on 22 Jan, 2022.
- The attacker called
deposit()
without really making any deposit and emitted a valid Deposit event - This transaction was repeated 16 times, minting 77,162 qXETH (\$185 million of Qubit xETH) for the attacker’s BSC address.
- They used the qXETH as collateral to borrow USD stablecoins, MDX, CAKE, BUNNY, WETH, and BTC-B before trading it all for approx. 200k BNB (\$80 million)
The protocol was exploited by;
— Qubit Finance (@QubitFin) January 28, 2022
0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7
The hacker minted unlimited xETH to borrow on BSC.
The team is currently working with security and network partners on next steps.
We will share further updates when available.
Side notes:
- According to their documents, the cross chain feature was audited in December 2021
- PancakeBunny was exploited in two major incidents in 2021.
The Causes of Smart Contract Security Flaws
Several key features of the blockchain are double-edged swords when it comes to smart contract security.
Decentralisation of the blockchain enables a trustless and pseudonymous network however, this allows adversaries to create and deploy malicious smart contracts while hiding their identity (to some degree).
Immutability of the chain means it becomes difficult to patch vulnerable smart contracts once they are deployed onto the blockchain.
With public ledgers, smart contracts are generally open-source. Developers could fall victim to replay attacks if they aren’t aware of the exposure of their smart contracts and their transactions. Replay attacks occur when an adversary “replays” another user’s or contract’s valid request for withdrawal. Failure to keep secrets can also happen if cryptography is not applied on crucial functions and values.
The reasons why smart contracts are more vulnerable than typical programs.
The immaturity of blockchain platforms and smart contract languages has also an effect on security. Decentralised applications (dApps) development differs from traditional application development and so a thorough understanding of the operations on the blockchain is necessary. Otherwise, the intentions of a developer could be inconsistent with the implementation.
Common Vulnerabilities
The following vulnerabilities outlined are generally concerned around Solidity, an object-oriented programming language for smart contracts notably run on Ethereum. This isn’t the entire list of known vulnerabilities in Solidity and extensive lists can be found in Solidity documentations, security articles and academic papers. It is also important to outline that bugs are constantly fixed in Solidity updates and recommendations to mitigating vulnerabilities often change as well.
Re-entrancy
A re-entrancy attack can occur when interacting smart contracts make external calls before resolving any effects. i.e. necessary checks or balance updates are not performed before a transaction. For example, a malicious smart contract could repeatedly request funds before the target contract could update their balance.
The DAO was a popular decentralised investment fund that lost approx. \$70 million during The DAO hack in 2016. Attackers utilised their proxy contract’s fallback function to initialise more transfer of ether given sufficient funds in the DAO. The balance of the vulnerable contract was never updated as the proxy contract recursively requested and received funds. The ether transferring function call().value()
was also used in the DAO. The function does not have a gas limit and returned a simple false value on an error (instead of a throw exception which would revert the state back to before the function call). Prevention methods against re-entrancy attack are later outlined in this article.
The figure above illustrates how the attacker was able to recursively request and receive funds from the DAO before any internal states were updated. Source
Callstack Depth Exception
Smart contracts have a maximum callstack of 1024 frames meaning external calls could fail and throw an exception at any time if the limit is exceeded. As a result, if these exceptions aren’t handled appropriately, adversaries could possibly gain access to funds or abilities to control a smart contract.
Since the Tangerine Whistle upgrade, it’s now impractical to execute a call stack depth attack because of the implemented 63/64 rule which essentially replaced the call stack limit with a gas restriction. If a call now requests for more than the maximum allowed amount of gas, the contract will call with 63/64 of the maximum allowed amount of gas.
A Ponzi Scheme on Ethereum - GovernMental
An early Ponzi game, GovernMental suffered from a callstack depth exception vulnerability. GovernMental was a smart contract where creditors (participants) were entitled to earn their money back with 10% interest if they lend to the government (contract) a minimum of 1 ETH. The funds were distributed as 5% towards the jackpot, 5% towards corrupt officials (contract owner) and 90% for paying out creditors. Once the jackpot was full (10k ETH), 95% of the funds were to be paid out to the creditors in the order of the contributions until the funds ran out. At any stage of the game, if the government does not receive any new funds for 12 hours, the most recent creditor would win the jackpot and everybody else will lose their claims.
The Ponzi game stored participants and their funds in two arrays and were cleared after every game using the following instructions.
creditorAddresses = new address[](0);
creditorAmounts = new uint[](0);
The code above compiles to iterate through and clear every element one by one.
At one stage, the game grew too large that the gas required to clear the arrays after each game exceeded the maximum allowed for a single transaction, freezing the jackpot pay out. Vitalik chimed in and suggested someone to spam 50 ETH worth of transaction fees to spam the gas limit up. After two months, a persistent caller unlocked the funds and received the 1100 ETH jackpot.
Phishing with tx.origin
Transaction origin, tx.origin
, is a global variable in Solidity which returns the address of an account that initiated the chain of interactions between smart contracts. It is not recommended to use tx.origin
for authorisation as the identity is easily spoofed. Instead, use msg.sender
which returns the address that created a transaction or initiated a function.
Also, Vitalik Buterin has mentioned the possibility of tx.origin
being removed from the Ethereum protocol, making code containing tx.origin
incompatible for future upgrades.
Timestamp and Block Hash Usage
Smart contracts can use block timestamps or hashes as a condition to trigger some functionality. However, it should be avoided to use these “out of control” parameters to trigger crucial operations. To some degree, malicious miners can manipulate and influence these values to change the output of a contract in their favour.
Arithmetic Under/Over Flows
Like other programming languages, integer overflow and underflow will occur in Solidity if an arithmetic operation produces a numeric value outside the range that can be represented with the allocated memory space.
Solidity has an inbuilt overflow check for operations and will raise a failing assertion error if a result falls outside the value range. This can be switched to the “unchecked” mode by using unchecked{ … }
to wrap an operation result on overflow. Similarly, the SafeMath library by OpenZeppelin will revert a transaction when an operation overflows.
Unfortunately, the inbuilt checked mode and the SafeMath library does not make a smart contract fool proof from overflow bugs. A contract can be left in a “stuck” state if an overflow is unavoidable.
Unchecked External Call
It should be assumed that external contract calls can and will fail. Appropriately handling errors is required to prevent DoS attacks. External calls should also not be made in conditional statements (e.g. if statements and for/while loops) as this would open a vulnerable contract up to DoS attacks. Malicious callees could permanently fail, preventing the victim from continuing executing.
In the example below, the caller expects the oracle to return an integer value but the malicious oracle implementation may intentionally throw an exception.
function dos(address oracleAddr) public {
badOracle = Oracle(oracleAddr);
if (badOracle.answer() < 42) { revert; }
// ...
}
The Recommended Way to Send Ether
Solidity offers three functions to pay ether to a specified address: transfer()
, send()
, and call()
.
Function | Forwarded gas | Action on failure |
---|---|---|
.transfer() | 2300 | Throws an exception |
.send() | 2300 | Returns false |
.call().value() | All available amount by default | Returns false |
For a new developer, there seems to be conflicting advice on the recommended function to use. After the infamous DAO hack, the security community recommended to avoid call()
and to use transfer()
and send()
instead to prevent re-entrancy attacks. These functions forward a fixed stipend of 2300 gas to its recipients, enough to log an event in a fallback function but not enough for multiple external calls. Furthermore, transfer()
was preferred over send()
as an accidental safe method as send()
requires the return boolean to be handled if the transaction fails.
The Istanbul upgrade on the Ethereum network invalidated an assumption that was made in these recommendations. It was assumed that gas costs were constant. EIP-1884 was accepted as part of the upgrade and it repriced the gas costs of some opcodes. For example, SLOAD
which reads a word from storage, was historically under-priced and the cost was increased from 200 to 800 gas. These changes were accepted to better balance operations cost with resource consumption, overall improving stability in processing time. For developers, this meant that gas costs can and will change and so smart contracts could no longer depend on gas costs.
As expected, the gas repricing subsequently broke some existing smart contracts. The revised advice was to return to using call()
and to avoid using transfer()
and send()
. To mitigate re-entrancy attacks, the use of the checks-effects-interactions pattern is recommended.
- Perform your checks first, e.g., validate inputs and check if enough ether was received
- Change any internal states like variables if all your checks pass, e.g. update contract balance
- Any interactions made to other contracts should only then be done at the end of your function, e.g. send ether last
Summary of Contract Design Patterns
Smart contract developers of today implement various design patterns to reduce the costs associated with deployment. Such designs provide easier management for keeping track of multiple interconnected contracts and enable advanced capabilities such as up-gradability. These capabilities allow for the avoidance of certain characteristics of smart contracts that typically leave them more susceptible to attack.
Proxy Contracts
The most commonly used pattern is known as proxy contracts. It follows the structure where a proxy contract delegates function calls to the target contract that contains the logic, using its address. If, for instance, a bug requires fixing or existing functionality needs to be improved, the developer only needs to deploy the updated version of the target contract. Then, the proxy contract can be re-configured to delegate calls to the new address without breaking any dependencies. Although this design pattern only covers the upgradability aspect, other patterns provide unique advantages that are worth exploring. In each of the following sections, a summary will be provided for each design pattern, exploring both common and uncommon.
Generalised architecture of a Proxy Contract design pattern.
Clone Factory Contracts
The factory pattern is a creational design pattern often used in programming languages. It allows the creation of objects without exactly having to specify the class. Likewise, in Solidity, a factory contract is used for deploying child contracts and delegating calls to their functions. When combined with a proxy contract, it provides various benefits, including lower costs for deploying multiple instances of a cloned contract, greater security, and easier tracking. As compared in the Solidity Developer blog post, there is a fifty-percent difference in the gas costs when deployed using the clone factory pattern. Such a pattern was introduced in the EIP-1167: Minimal Proxy Contract. The EIP describes a method to cheaply clone contract functionality while preserving the immutability property. More specifically, it presents a standard for a minimal bytecode implementation that delegates calls to a contract using its address. This functionality is enabled by using the assembly language of Solidity to store the bytecode of the target contract in the memory and calling the create opcode to deploy it. The exact details of the implementation are available in the GitHub repository.
Multi-Facet Proxy (Diamond Standard)
The diamond standard was introduced in EIP-2535 as a modular way to build and upgrade smart contracts, bypassing the virtual size limit of 24KB. As an important note, it would only be ideal for large-scale contracts as there can be overhead in terms of complexity and gas for small contracts. There are various advantages for its usage, including:
- an easier integration with other contracts during deployment for testing purposes,
- a gas-efficient and systemic way to organize contract data and code,
- and upgrading functionalities using functions such as add, replace, or remove.
The standard enhances the up-gradability aspect because there is no limit to the number of functionalities that can be added. Additionally, it is possible to enable immutability for any function as it is an integral property of smart contracts that needs preserving. Name Registry
When a factory contract is used for deploying multiple child contracts, it can become a tedious process to keep track of their addresses if changed over time, i.e redeployed. The name registry pattern aims to solve this issue by implementing a mapping for storing the contract’s name and address: mapping(string name => address contract_address) registry. It is stored in a separate contract and is responsible for keeping track of all the deployed contracts. For implementation, the contract’s code is available in this Medium article. The benefit of mapping a string as a key to retrieving the corresponding address as the value is that the former can remain the same, and the latter can be modified.
Assembly Logic
The design patterns summarized above all implement a common functionality of delegating calls in the fallback function. This function is triggered when a contract receives Ether without msg.data or a call is sent to a function that it does not contain. By taking advantage of the other type of invocation, calls from external contracts can be executed in the context of the receiver contract. It can be achieved by implementing the following logic in the receiver’s fallback function:
function() external payable {
address addr = implementation_address; // Address of the external contract
assembly {
calldatacopy(0x0, 0x0, calldatasize);
let result := delegatecall(gas, addr, 0x0, calldatasize, 0x0, 0);
returndatacopy(0x0, 0x0, returndatasize);
switch result case 0 {revert(0, 0)} default {return (0, returndatasize)};
}
}
Preventive Methodologies
In this section, preventive methodologies to improve the security of smart contracts are introduced. These are some reliable resources, however, developers are also encouraged to explore other methods, tools and services that are equally, if not more reputable.
Chronology of the recommended actions a smart contract developer would take upon.
Security Recommendations and Good Coding Practices
The straightforward (and cheapest) approach in better securing a smart contract is following good coding practices and implementing security considerations. It is important for developers to integrate these recommendations throughout the development cycle to minimise relying entirely on analysis tools and auditing services. Resources are readily available online for smart contract developers that cover known vulnerabilities, fail safe patterns, and guidelines.
There isn’t a one-size-fits-all solution to securing your own contract. Developing smart contracts requires a different engineering mindset than traditional application development. Developers should stay up to date with the latest trustworthy resources like the list below to keep track of security developments.
Some reliable resources include:
- SWC, a community catalogue of known common weaknesses in smart contract code
- SCV-list, a list of vulnerabilities disclosed by top security experts in DeFi
- Repo of in-depth and up-to-date posts of past mistakes made by Solidity developers
- Pitfalls and security recommendations from the Solidity documentation
- An extensive knowledge list of contract security recommendations by Guy Lando
- Recommended list of coding patterns like the checks-effects-interactions pattern for preventing exploits and mitigating damages
- Ethereum.org security documentation on outlining attack types and tools for smart contracts
- A guide to contract security best practices by Consensys
- A baseline knowledge of considerations for intermediate Solidity developers
Analysis Tools
Analysis tools can be used to improve the security of smart contracts by helping developers identify vulnerabilities. Many trustworthy and reliable tools are open source with a few paid tools being worth the price. It’s important to utilise these tools throughout the development cycle, including post-deployment, to maintain the highest level of security possible.
However, they’re not perfect. Some of these analysis tools have limited coverage and false positive alerts can occur. Implementing a variety of tools can help extend the coverage of vulnerabilities. Though be aware, as previously recognised security tools may have had their projects deprecated or abandoned.
A variety of some reliable static and dynamic analysis tools are introduced below. Thereafter, a compiled list of real time monitoring tools and services that can be implemented post-deployment of a smart contract.
Static and Dynamic Analysis Tools
SmartCheck is a static analysis tool that detects vulnerabilities in smart contracts. It translates Solidity code to XML format and uses XPath queries to detect problematic patterns.
Securify2 is another static analysis tool that supports the detection of 37 vulnerabilities. It is the successor of Securify, a popular security scanner with improved scalability, higher precision, and greater vulnerability cover. It currently only supports flat contracts, i.e. contracts that do not have import statements.
MythX is a security analysis service by Consensys that helps detect Solidity vulnerabilities throughout development. It is regarded as one of the most reliable and extensive tools available for contract developers. This tool is available through MythX CLI as well as VSCode, Truffle, and Remix plugins. The paid service provides three different scanning modes that are available in different subscription plans.
Theo is an exploitation framework and a blockchain recon and interaction tool. The interface is similar to Metasploit (a penetration testing software). The tool can automatically scan for vulnerabilities and send transactions to exploit smart contracts. Other features include a transaction pool monitor, frontrunning and backrunning transactions, and web3 console.
Surya is an easy to use auditing tool that visualises outputs and information of a smart contracts’ structure. It is easiest to use through the VSCode's Solidity Auditor extension. Call graphs and inheritance graphs can be generated to help manually inspect a developers’ own contract.
Echidna is a Haskell program for property-based testing. Property-based testing is an efficient and tailored form of fuzzing, a technique to check the correctness and safety policies of code by feeding random data. Echidna generates inputs tailored to an application under test with source code integration which shows which lines are covered after a fuzzing campaign. It also uses Slither, a static analyser, before a fuzzing campaign to display additional information about a contract.
MadMax is a static analysis tool that finds gas-focused vulnerabilities in smart contracts. It is deployed as a client for the Gigahorse framework, a binary lifter from low-level EVM code to a higher-level representation. Developers are able to test their code in the testnet against MadMax through the Contract Library, an automatic contract analyser.
Vertigo is a mutation testing framework for Solidity. Mutation testing is a white box testing technique that involves slightly modifying a program and comparing the behaviour to the original version. Vertigo implements a variety of mutation operators that target different parts of a contract like arithmetic operations, modifiers and comparisons.
Real Time Monitoring and Analysis Tools
Contract Library
Contract Library by Dedaub provides a full decompilation and automatic analysis of all contracts on the Ethereum mainnet and testnets. Contracts on chains are visible within seconds of deployment. Vulnerability analyses are continually (every fortnight) revised by the Dedaub team, releasing updates to improve the precision and accuracy of their service.
Developers using Contract Library should be aware that the security analysis performed on the contracts are not extensive and false positive warnings are common. However, the service is user friendly and fast, making it valuable for initial contract testing.
Karl
Karl is an on-chain monitoring tool. It connects to a blockchain of choice and monitors for new blocks. The tool is accessed through a CLI with the ability to save outputs to local files or http endpoints.
It uses Mythril, an open-source component of MythX that analyses EVM bytecode, to look for newly deployed vulnerable smart contracts. Mythril is targeted in finding common vulnerabilities and not detecting issues involving business logic in dApps. It is a symbolic executor meaning it is unable to explore all possible states of a contract. Developers are able to control the speed and coverage of the tool by adjusting the maximum recursion depth and execution timeout.
Forta
Forta is considered the first decentralised runtime security protocol. It is a \$23 million incubation project by blockchain security start-up OpenZepplin. Though still in development, the product has also been backed by other venture capital firms such as Coinbase Ventures, Blockchain Capital, and Andreessen Horowitz.
The goal of Forta is to detect system critical issues and threats in real time. Meaning users are able to gain valuable information about their systems and implement an effective incident response plan in the event of a security issue.
This protocol has agents and nodes as their two main components. Agents are anomaly detection scripts that look for malicious or unusual transaction behaviours or state changes on smart contracts. They can monitor contracts across Layer 1, Layer 2 or sidechain. Nodes execute Agents against each block of transactions.
When an Agent detects an anomaly, it triggers a network wide alert which is stored in an IPFS and linked to a public blockchain. Forta also stores and maintains an automated public registry of all alerts, allowing users to access relevant alerts through an API or the explorer.
SmartWarden
SmartWarden is an automated intrusion detection system (IDS) for smart contracts presented in a paper by Echeberria-Barrio et al. Deploying an IDS has been a standard security procedure in traditional network architectures to detect and alert suspicious activities. The developers of SmartWarden consider that it should be no different in web3.
SmartWarden can be installed on a smart contract of interest. Once the contract has been deployed onto a chain, every call that the contract receives, SmartWarden will determine if it is malicious or not by going through three components: path monitoring, feature extractor, and detector.
The path monitoring component computes the path where the received transaction comes from. It takes the address that sends the call and traces through the chain, adding addresses that are related via transactions.
The feature extractor selects and calculates details from the computed path. Particularly path length, total number of different addresses that take part in the path, time taken for path to complete, total number of functions called during the path’s transactions, total value that has been transferred during the path, and total number of transactions that have transferred a positive value. Taking these features, a feature vector is then generated as an input for the last component.
The detector is composed of a multilayer perceptron model. Specifically, it is formed by one hidden layer of 1000 neurons with the activation function of sigmoid. The output layer is implemented with the softmax activation function, formed by 2 neurons. The model predicts if the received feature vector is malicious or not. If malicious, the received call is not carried out. If the call is legitimate, it is executed.
Security Auditors
Security audits are considered to be the most effective method of detecting vulnerabilities before deployment. Particularly with the rise of DeFi, it has become a crucial part in the development cycle as more money is poured into the blockchain ecosystem. Smart contract auditing involves security experts and experienced blockchain developers meticulously analysing contract code and identifying difficult-to-find vulnerabilities. Despite it being the most reliable method for preventing insecure contracts from being deployed, it can be unjustifiable to pay for this high cost service, i.e. the benefit of audits does not outweigh the cost for start-ups looking to deploy proof-of-concept contracts.
The following are four different firms that offer smart contract audits as one of their services. There are many other contract auditing companies that are available and the numbers are continuously growing as it is an important and competitive industry.
- Considered as the largest community of leading smart contract auditors and blockchain security experts
- They provide multi-layered audits which involves three or more independent auditors performing an unbiased audit on developers’ smart contracts
- Has over \$12 billion worth of assets under audit with notable clients including OpenSea, Loopring, and Nexus Mutual
- They also make a remarkable claim of no funds lost in any of their clients
- Consensys is recognised as one of the largest blockchain incubators with their main focus on the development of Ethereum blockchain applications and software, particularly financial infrastructures.
- Much like other auditors, publications are available on their website detailing their previous audit
- They also provide other security products and services including threat modelling, a fuzzer, and Scribble, which is a specification language for writing properties and runtime verification tools.
- From the beginning, OpenZepplin has been tackling the challenges of building dApps and is well known for its development of modular and secure Solidity libraries
- With their focus on dApps security, they offer a security audit for distributed systems
- Some of the most popular reports they have published include a critical vulnerability in MakerDAO’s governance system, an audit on Compound Finance’s contracts and an audit on the Solidity compiler
- Trail of Bits is a cybersecurity research and consulting company that provide a range of services and tools to their clients
- Alongside providing smart contract audits, they have developed security analysis tools such as Slither and Echidna with their own cutting-edge research
- Trail of Bits focus isn’t just on blockchain security. They offer security engineering as well as research and software assurance services like threat modelling, cryptography, and infrastructure security.
- Sigma Prime is a Sydney based, information security firm specialising in offensive security services as well as Ethereum smart contract security assessments
- Pier Two has a good working relationship with Sigma Prime, having engaged in prior smart contract audits for DeFi primitives.
- Sigma Prime founded and maintains Lighthouse, an open-source implementation of the Ethereum 2.0 specification written in Rust. Sigma Prime is heavily involved in Ethereum 2.0 engineering and research.
Bug Bounty Programs
Bug bounty programs are opportunities given to the public to report bugs or vulnerabilities of an organisations’ applications for compensation and sometimes recognition. They give white hat hackers an incentive to find problems in code and report it before a cybercriminal could exploit it. Bug bounties are particularly valuable for smart contracts as they are a cost-effective way of increasing security post-deployment.
Immunifi
Immunifi is the leading bug bounty and security service platform for web3. They guard over \$25 billion in user funds while having over \$73 million in bounties. The platform offers a “whitehat army”, a secure dashboard, PR and comms support for web3 projects looking to start a bounty program. In terms of payment, they charge a 10% “performance” fee for vulnerabilities found and submitted.
Code Arena
Code Arena (Code4rena or Code 423n4 or C4) hosts community driven contests for smart contract audits. The players in the arena:
- Wardens are the competitors in the contests, auditing code and “protecting the DeFi ecosystem”
- Sponsors create and fund the prize pools to audit their project
- Judges award the prizes based on bugs reported by Wardens
C4’s model brings more experts to audit projects in these contests. Compared to traditional bug bounties, audit contests incentivise wardens with guaranteed payouts from low-hanging fruit, i.e. a project seeking an audit contest will have unreviewed code and a fixed prized pool.
The Effect of the Merge
Smart Contract Security Changes
Planned for Q2 2022, the Merge will involve the official switch to Proof of Stake (PoS) when the Ethereum mainnet “merges” with the Beacon Chain. Smart contract security will be minimally affected by this upgrade since the execution layer, formerly known as Eth1.x, will not undergo major changes post-merge. DApps will mostly interact in the same way and after minimal updates.
The figure above outlines how the merge redefines the consensus layer but does not majorly change the execution layer.
There is a possibility of the Merge bringing in a wave of new contract developers. Adversaries would be able to take advantage of naïve developers and exploit simple but overlooked vulnerabilities, like they have been for copy paste flash loan projects on the BSC.
Ethereum PoS Block Creation and Fork Choice Rule
- Time is partitioned into epochs, further subdivided into 32 slots of 12 seconds
- Each slot contains a committee of 128 validators
- A randomly selected validator in each committee becomes the proposer of the slot and has the power to create a block
- Epoch boundary blocks (EBB) are checkpoint blocks that represent the start of an epoch
- A validator determines the EBB by selecting the block in the first slot. If it doesn’t exist, it chooses the block in the highest slot in the most recent epoch
- In each slot, the validators in the committee have one vote (an attestation)
- An attestation contains a source EBB, a target EBB, and the head of the chain according to Hybrid Latest Message Driven Greedy Heaviest Observed SubTree (HLMD-GHOST)
- The source and target EBB are defined by the Casper the Friendly Finality Gadget (Casper FFG)
- Weight of a given block is the sum of the stake owned by validators who attest to the block and any of its descendants
- HLMD-GHOST defines how the head of the chain can be traced from the last justified EBB by choosing the block with the highest weight at each fork until the leaf block (a block with no children)
- Figure above shows how the HLMD-GHOST determines the canonical chain from conflicting leaf blocks
- The numbers represent the weight of each block
- The blue blocks represent the heaviest in each fork thus, belong to the canonical chain
Casper the Friendly Finality Gadget
- Casper FFG is a consensus protocol that abstracts the block proposing mechanism
- Finality is achieved through validators voting for the blocks that make up the underlying chain, known as checkpoints
- As previously mentioned, every attestation contains a source EBB and target EBB. This is also referred to as a link
- A supermajority link, L, between EBBs is established when validators controlling 2/3 of the total stake release attestations voting for L
- From the first or root block, the target of a supermajority link is justified
- The source block of a supermajority link that is already justified, become finalised
- By default, the root block is justified and finalised
- Honest validators will follow the Casper FFG and attest the highest checkpoint to establish a link
- Finalised blocks are a part of the canonical chain permanently unless it is proven that validators controlling 1/3 of the total stake were dishonest
- Figure shows the Casper FFG process in the first three epochs.
- Block 0 is justified and finalised by default
- A supermajority link with block 0 and block 32 justifies block 32
- Since the EBB was not proposed in the first slot of epoch 2, block 63 was “borrowed”
- A supermajority link between block 32 and block 63 finalises block 32 and justifies block 63
Attacks on the Consensus Layer
Papers have proposed dishonest strategies on the Consensus Layer that are arguably cheap and are not punishable through the slashing conditions (two of the three conditions of which are illustrated below). Both forms of attack however rely on a mixture of “luck” (as dictated by the pseudo-random selection of block proposers for a given slot in an epoch) and a significant control of validators in a given validator committee for an epoch (where significant can still be less than the third of validators required of most Byzantine actions).
Two of the three conditions which would cause a node to be slashed.
Short range block reorganisations
This attack allows malicious validators to orphan legitimate blocks from the canonical chain by creating forks with privately created blocks. This attack requires an adversary with 30% stake of an epoch.
For any slot n, if an attacker is the block proposer of n+1, it can execute a length-1 reorg and orphan the block n+2 according to the following procedure:
- The attacker creates a new block and withholds releasing it in the allocated slot
- The attacker attests the privately created block using its slot n+1 attestation
- The selected proposer for the slot n+2 will present a block with parent block n after not observing the expected block at n+1.
- Other validators may attest block n+2 while the attacker privately attests block n+1 for slot n+2. Other attacker controlled validators will privately attest block n+1
- The attacker will release block n+1 with its attestations as soon as block n+2 is observed
- Blocks n+1 and n+2 are conflicting as they both have the same parent block. Assuming block n+1 was released as soon as block n+2 was proposed, block n+1 would have more attestations. HLMD-GHOST will see block n+1 as the head of the chain causing block n+2 to be orphaned.
The implications and some assumptions made can be found here. Longer range reorganisation attacks are also possible.
Finality Delay
Finality delay is a method of attack that involves withholding the release of EBBs to delay the finality of future epochs. This intentional network delay can indefinitely stall consensus decisions.
An example involves the attacker controlling validators selected to propose an EBB and its subsequent block. It also requires 30% of the stake. The attack is executed in the following manner:
- Consider the proposed block n which was released in the last slot of epoch 0. The attacker controls validators selected to propose the EBB of epoch 1 (block n+1 and block n+2)
- The attacker creates block n+1 and block n+2 as well as an attestation for block n+1. This is all kept private to network
- Honest validators do not see the EBB of epoch 1, and thus attests block n as the new EBB.
- The attacker waits until 139 attestations have been made for block n before releasing the blocks it has kept private.
Unlike the previous attack, the honest and malicious attestations are for different blocks and thus, are not conflicting. HLMD-GHOST rules identify that the released fork is the head of the chain. Since block n+2 is the only leaf block, making it the head of the chain, honest attestations for block n are now wasted on the wrong EBB.
The attacker will also withhold the rest of their attestations in epoch 1. No supermajority link with block n or block n+1 is ever created, indefinitely delaying the finality of the epoch.
Refined Strategies
A refined variants of the previously mentioned attacks can be found in this paper here and here.