Smart contracts are codes deployed on the blockchain platform and executed upon meeting certain conditions.
Smart contract security plays a crucial role in decentralized applications. An exploit in smart contracts happens due to vulnerabilities that are not discovered either due to a lack of audit or inefficient and incomprehensive Smart Contract Audits carried out by inexperienced smart contract auditors.
Millions of dollars have been stolen over the years owing to smart contract vulnerabilities and bugs. It is imperative to make your smart contracts free from vulnerabilities before they are deployed on the mainnet.
Why Smart Contract Audits Are Necessary?
Table of Contents
- 1 Why Smart Contract Audits Are Necessary?
- 2 Key Smart Contract Vulnerabilities
- 2.1 1. Reentrancy
- 2.2 2. Access Control
- 2.3 3. Floating Pragma
- 2.4 4. Zero Address Check For Critical Functions
- 2.5 5. Divide Before Multiplying
- 2.6 6. Frontrunning
- 2.7 7. Missing Withdraw Functions
- 2.8 8. Integer Overflow and Underflow
- 2.9 9. Using Block Properties For Randomness (Miner Manipulation)
- 2.10 10. Poor Visibility Specifiers For Critical State Variables May Lead to Abuse.
- 2.11 11. Timestamp Dependence
- 2.12 12. Denial of Service (DoS) Attacks
- 2.13 13. Logic Errors
- 2.14 14. Gas Limit Vulnerabilities
- 2.15 15. Unchecked External Calls
- 2.16 16. Short Address Attack
- 2.17 17. Force Sending Ether (Self-Destruct Attack)
- 2.18 18. DelegateCall
- 2.19 19. Stack Depth Limit Attack (deprecated)
- 2.20 20. Array Length Overwrite
- 2.21 21. Parity Multi-Sig Wallet Bug
- 2.22 22. BatchOverflow and Underflow in ERC20 Tokens
- 2.23 23. Uninitialized Storage Pointers
- 2.24 24. Shanghai DOS Attacks (Gas Cost Manipulation)
- 2.25 25. Frozen Ether in Parity Wallets (Library Suicide)
- 3 How to Avoid Vulnerabilities in Smart Contracts
- 4 Sum Up
Unlike most other agreements, smart contracts primarily deal with financial assets. Hence, once deployed, errors in smart contracts cannot be rectified owing to the immutable nature of the Blockchain.
Smart contract vulnerabilities could be a potential security hazard and a curious target for malicious cybercriminals. In fact, in some cases, even if there are no external exploiters, chances of capital collapse and financial losses are present.
In this blog, we will discuss the most common and critical vulnerabilities that must be checked in every smart contract before it is marked fit to be deployed on a blockchain.
Key Smart Contract Vulnerabilities
1. Reentrancy
In a Reentrancy attack, a malicious smart contract exploits the vulnerabilities of another smart contract, usually to drain off its funds.
In this case, working on a vulnerable smart contract, say Contract X includes checking the balance, sending funds, and then updating the balance sheet. This creates a window for the attacker contract, contract Y, to repeatedly call contract A. The cycle continues till contract X is deprived of all its funds.
2. Access Control
The concept of Access Control, or “who is permitted to perform a particular task,” is crucial in the context of a smart contract.
Your contract’s access control may determine who can issue tokens, vote on initiatives, halt transfers, etc.
To prevent someone else from stealing your entire system, it is crucial to understand how you implement the access control function in your smart contract( malicious users should not breach access control).
3. Floating Pragma
A solidity pragma is probably the first line of code in a solidity code that determines the compiler’s version of the smart contract.
For a smart contract compilation, Floating Pragma categorically specifies an array of compiler versions.
Now a pragma does not change the version of the compiler, and all it can do is instruct the compiler to verify if it matches the pragma. In case of a negative result, the compiler generates an error.
Using a strict pragma function, specifying the compiler version is advisable. This avoids the unintentional deployment of a contract with an outdated compiler with unresolved bugs.
4. Zero Address Check For Critical Functions
A blockchain address is a public key linked to a hash identifier. In the case of Ethereum, it is the last 20 bytes of a public key hash.
As your house has a unique physical key, your blockchain account has a unique private key(that you should not share with anyone else). These private keys enable you to conduct transactions on a blockchain platform.
Now, deploying a smart contract on a blockchain is done through a transaction involving a sender and a recipient. EVM recognizes that when a transaction includes the zero address in the receiver field, it means establishing a new contract.
The zero-address resembles a “false account” in several ways. The zero address is 20 bytes long, just like a typical Ethereum address, except it only contains empty bytes.
Also, there is no private key for a zero account, so if your smart contract is transacting to a zero address, there is no way of reverting that. This implies that your funds will be locked in a zero address forever.
5. Divide Before Multiplying
Let’s have a look at a simple mathematical example of this:
A= (10*30*18)/ 30= 180
We are now solving the same equation but performing division before multiplication.
A= (10/30)* 30*18= 179.99999
These two terms are quite close but not the same. In the case of a solidity smart contract, it is going to yield 179. Therefore, performing multiplication before division mitigates rounding-off errors in a smart contract.
6. Frontrunning
Frontrunning is a term for stock trading concerned with insider trading. A Malpractice where insider information impedes the market before the actual competition begins.
In the crypto space, it is defined as the employment of unconfirmed transactions by paying a higher gas fee to the miner. This is possible due to the public nature of the Blockchain and can also be done using a front-running bot.
This type of problem requires code refactoring or redesign to be solved.
7. Missing Withdraw Functions
A common vulnerability is when your smart contract has a payable function but misses out on the withdrawal function. It implies that there is only a payment option in your smart contract, and if you are a sender in case of a transaction, your funds are bound to get locked in your contract.
8. Integer Overflow and Underflow
Firstly, look at the example given below.
EVM states fixed-size data types for integers, implying that integers can represent only a specific range of numbers.
If a solidity code deals with overflow or underflow issues, there may be a significant difference between the calculation’s actual outcome and expected results.
This will undermine the contract’s inherent logic and result in the contract’s funds being lost. Version limitations do apply to overflow vulnerabilities, though.
Overflow will not generate an error in Solidity versions up to 0.8, but it will in versions after 0.8.
9. Using Block Properties For Randomness (Miner Manipulation)
Solidity does not have a process to generate random numbers, but many turnarounds have come up with a developer’s wish to include randomness. The miners, for their benefit, can exploit these random number generators.
One frequently employed method is using a pseudorandom number generator (PRNG), which generates a string of bytes that appear random in a predictable manner based on an initial private seed value and internal state.
10. Poor Visibility Specifiers For Critical State Variables May Lead to Abuse.
In the case of solidity code, visibility specifiers govern the manner of calling a function. The visibility specifier assumes control when allowing users to employ derived contracts to call for external functions.
The smart contract may suffer catastrophic consequences if the visibility specifiers are implemented incorrectly. If functions’ default visibility is always set to public, external contracts can request visibility even when functions don’t explicitly mention it.
Developers fail to set the visibility specifier to private, which results in this vulnerability.
11. Timestamp Dependence
Timestamp dependence refers to a situation where a smart contract’s behavior relies on the timestamp of the Ethereum block in which it is included. This can be problematic because miners have some control over the block timestamp, making it susceptible to manipulation.
Example: Consider a decentralized application (DApp) that grants access to certain features or funds only after a specific date based on the block’s timestamp. An attacker could bribe miners to manipulate the timestamp, allowing them to gain access to these resources prematurely.
Preventive Measures:
- Use block numbers rather than timestamps for critical time-based decisions.
- Implement a delay mechanism that takes into account potential timestamp manipulation.
- Consider using secure external timestamp oracles to verify time-related information.
12. Denial of Service (DoS) Attacks
Denial of Service (DoS) attacks in the context of smart contracts aim to make a contract unresponsive or unavailable by consuming all available computational resources (gas) in a transaction.
Example: An attacker deploys a smart contract that contains a function designed to consume an excessive amount of gas in a single transaction. By repeatedly calling this function, the attacker can cause the target contract to run out of gas, rendering it unresponsive.
Preventive Measures:
- Implement gas limits and establish a timeout mechanism for critical operations.
- Use gas-efficient algorithms and data structures to minimize computational resource consumption.
- Consider implementing rate limiting or access controls to prevent abuse by limiting the number of requests from a single address.
13. Logic Errors
Logic errors occur when a smart contract contains coding mistakes or flawed logic that lead to unintended behavior, such as incorrect calculations or faulty conditional statements.
Example: Imagine a token contract where a logic error allows a user to transfer more tokens than they have in their balance. This error could result in unauthorized token transfers.
Preventive Measures:
- Follow best practices and coding standards when writing smart contract code.
- Conduct thorough code audits and testing to identify and correct logic errors.
- Use formal verification tools to mathematically prove the correctness of your contract’s logic.
14. Gas Limit Vulnerabilities
Gas limit vulnerabilities arise when a function within a smart contract consumes more gas than the maximum gas limit allowed by an Ethereum block. This can lead to the function becoming inexecutable, potentially freezing the contract or its associated funds.
Example: Suppose a contract function requires a large amount of gas to execute, exceeding the gas limit of the Ethereum block. As a result, the function fails to execute, and any associated state changes or transactions are reverted.
Preventive Measures:
- Break down complex operations into smaller, manageable transactions to avoid exceeding gas limits.
- Use gas estimation tools to monitor and optimize gas usage within your contract.
- Implement fallback mechanisms or emergency stop functions to handle out-of-gas situations gracefully.
15. Unchecked External Calls
Unchecked external calls occur when a smart contract interacts with an external contract or function without properly checking the result of the call. This can lead to unintended consequences if the external call fails or behaves maliciously.
Example: A contract calls an external contract to perform a critical operation and assumes that the call will always succeed. However, if the external contract encounters an error or malicious behavior, the calling contract may proceed with unintended actions.
Preventive Measures:
- Implement comprehensive checks and error handling for external calls, including checking return values and using the
revert()
function to handle failures gracefully. - Use well-audited and trusted external contracts and libraries whenever possible to minimize the risk of faulty external calls.
- Consider using on-chain Oracle solutions to verify external data sources securely and reduce the reliance on potentially untrustworthy external contracts.
16. Short Address Attack
This attack can occur when a user or attacker sends less data than expected to a smart contract function, exploiting the way Ethereum’s EVM pads short input data.
Example: An attacker could call a token transfer function with a short address, causing the smart contract to misinterpret the input data and potentially transfer a much larger amount of tokens than intended.
Preventive Measures:
- Always check the length of the input data in your functions to ensure they match the expected length.
- Use proper function signatures and strong input validation.
17. Force Sending Ether (Self-Destruct Attack)
Ethereum allows the “self-destruct” method, which, when called, destroys the contract and sends its funds to a designated address. An attacker might force Ether into a contract without triggering its fallback function.
Example: An attacker could use the self-destruct method to send Ether to a contract that isn’t expecting it, potentially breaking invariants or logic based on the contract’s balance.
Preventive Measures:
- Avoid writing logic that relies solely on the contract’s Ether balance.
- Monitor and check for unexpected Ether balances regularly.
18. DelegateCall
The delegatecall function allows a contract to execute code in the context of itself but with the code of another contract. Improper use can lead to unintended side effects.
Example: A contract uses delegatecall to execute a library function, but the library function unexpectedly alters the state of the calling contract.
Preventive Measures
- Be cautious and aware of the state changes when using delegatecall.
- Ensure that the called contract is trusted and its behavior is well-understood.
19. Stack Depth Limit Attack (deprecated)
Earlier versions of Ethereum had a stack depth limit of 1024. If this limit was reached, the contract call would fail.
Example: By recursively calling a contract, an attacker could reach the stack depth limit, causing any subsequent calls to fail.
Preventive Measures
This attack is less relevant since the EIP-150 hard fork, but for older contracts, be aware of the potential risks and avoid deep call stacks.
20. Array Length Overwrite
Some smart contracts expose functions that allow the length of storage arrays to be set by external users, potentially allowing an attacker to delete or block access to existing entries.
Example: A contract allows users to set the length of an array. An attacker sets the length to 0, deleting all existing entries.
Preventive Measures
- Do not allow untrusted users to modify the length of storage arrays directly.
- Implement access controls and checks on functions that modify important state variables.
21. Parity Multi-Sig Wallet Bug
The Parity Multi-sig Wallet Bug was a vulnerability rooted in the wallet’s flawed initialization code. Instead of correctly setting up the contract’s internal structures, this bug enabled an external actor to take ownership of the contract and execute any of its functions.
Example: In 2017, this vulnerability was exploited when an anonymous user managed to claim ownership of a contract and siphon off around $30 million worth of Ether. The same user could also destroy the contract.
Preventive Measures
- Always ensure proper ownership controls.
- Use multiple code reviews and consider getting external audits.
22. BatchOverflow and Underflow in ERC20 Tokens
The “BatchOverflow” is a vulnerability present in some ERC20 token implementations. Due to inadequate checks, certain batch transfer functions can lead to integer overflow or underflow errors, allowing attackers to generate an astronomically large number of tokens.
Example: In 2018, attackers were able to “mint” billions of tokens by exploiting this vulnerability in several ERC20 tokens, subsequently dumping these tokens in exchanges.
Preventive Measures
- Implement safe mathematical operations to prevent overflows and underflows.
- Limit and protect batch operations against extreme inputs.
23. Uninitialized Storage Pointers
Solidity has both storage (persistent between function calls) and memory (temporary) variables. If storage variables, especially local ones inside functions, aren’t properly initialized, they could overwrite crucial contract state variables.
Example: A function intended only to set a temporary variable. If not carefully implemented, it can overwrite critical state variables such as the contract’s owner.
Preventive Measures
- Always initialize storage variables with a clear distinction from memory variables.
- Adopt clear naming conventions to differentiate local and state variables.
24. Shanghai DOS Attacks (Gas Cost Manipulation)
Ethereum’s gas system is designed to meter computation. However, when certain operations are underpriced, they can be repeatedly called in what effectively becomes a denial-of-service (DOS) attack on the network.
Example: During the Shanghai attacks of 2016, attackers exploited flaws in the Ethereum Virtual Machine by focusing on opcodes that were underpriced in terms of gas. This led to blocks that took exceptionally long times to validate.
Preventive Measures
Developers should be aware of potential gas-related vulnerabilities and should design contracts that are resilient to unexpected gas cost fluctuations.
25. Frozen Ether in Parity Wallets (Library Suicide)
Contracts in Ethereum can be designed with a self-destruct mechanism. When improperly secured, this mechanism can become a vulnerability. Parity’s multi-sig wallets were dependent on a shared library that contained this mechanism.
Example: In 2017, a user unintentionally triggered the self-destruct function on this shared library. As a result, all dependent Parity multi-sig wallets became inoperable, freezing over $150 million worth of Ether.
Preventive Measures
- If shared libraries are employed, they should be thoroughly vetted and contain fail-safes to prevent accidental triggering of destructive functions.
- Regular audits and monitoring of critical shared resources are essential.
How to Avoid Vulnerabilities in Smart Contracts
There are several ways to safeguard your smart contracts. Some of them have been mentioned below.
- Healthy Development of code
There are a few practices that need to be followed, to completely avoid or mutate a code in case of an encountered bug in the smart contract.
Firstly, the use of proxy codes to ensure upgradability
Suppose we have two smart contracts. Let’s call them A and B, where A is the proxy contract and B is the logic contract.
A interacts directly with the user and has a delegate function that sends the transaction to B.
Since blockchain is immutable, both A and B cannot be changed once deployed, but in case of an issue with the logic, the link between A and B can be broken and replaced with an upgraded logic via another smart contract C.
Secondly, developers should write unit test cases.
It encompasses the implementation of comprehensive test coverage associated with smart contract functionality. It ensures the optimized performance of the functions included in the smart contract.
Thirdly, Using the latest version of the Libraries
Open-source libraries provide reusable standard codes; these are tried and tested pieces of code for vulnerabilities. Hence, using the latest version of libraries significantly reduces the probability of vulnerabilities in smart contracts.
- Internal Security Checks – Internal Audit
Setting up an internal team of security professionals acts as the first line of safety for your smart project. Also, An internal team can frequently audit your code for bugs in comparison to a third-party audit.
- Regular Audits
Undoubtedly, the blockchain space is on its development curve and with its evolution, security concerns and vulnerabilities are also evolving.
Hence, regular audits aid in ensuring the protection of your smart contract from the evolving bugs in the Web3.0 domain.
- Bug Bounty
A bug bounty program provides a platform for building a bridge between the project and the white-hat hackers. Here, real-world hackers tamper with your code to find vulnerabilities.
ImmuneBytes is all set to launch its BugBytes (A Bug Bounty Platform), where you can ensure the security of your smart contract by competing with hackers and paying only if they catch a bug.
- Opt for Security Monitor Tool/Alert Tool
In the blockchain space, repeated or blacklisted offenders are a reality. Deploy a tool or outsource an alert service to acquire notification in case a blacklisted hacker is tempering with your smart contract.
Immunebytes is soon launching its alert tool, AlertBytes, to notify you of the transaction on your smart project. Here you can customize your service based on the alert method(place of receiving notification), condition( send or receive transaction alerts), and threshold( minimum amount of transaction).
Sum Up
Smart contracts are the backbone of the blockchain space, especially with its ever-widening domain usage. With the rapidly scaling Blockchain uses vulnerabilities are probably inevitable.
But, there is a simple yet effective way out. A third-party review of the smart contract and, in case your budget allows further, succeeding it with a bug bounty would be a good idea!!
It seems like an additional burden on your pockets but trust us, it could save you from a potential catastrophe. Any project that cares about the security of its smart contracts must include a third-party solidity smart contract audit as a standard procedure.