Ever wondered what a parrot, platypus, and kangaroo have in common? It’s ‘Homoio-thermy’. They maintain their internal body temperature, just like auditing maintains the safety of smart contracts.
However, recent events have shown that smart contract security is not infallible. Despite improvements in smart contract security, vulnerabilities still lead to significant financial losses.
In this blog, we will explore the top 10 common errors in Solidity programming. These errors are often the root cause of vulnerabilities that can lead to costly exploits. Understanding these pitfalls and knowing how to address them is crucial for any developer working with smart contracts.
Let’s dive into these common errors and learn how we at QuillAudits tackle them to ensure the security & reliability of your smart contracts.
Unchecked external calls are a common issue in Solidity. The call()
and send()
functions are often used to interact with external addresses.
While transfer()
is generally preferred for sending Ether due to its simplicity and safety, call()
and send()
are still widely used for their versatility.
However, both call()
and send()
return a boolean value indicating whether the call was successful.
If this return value isn’t checked, it can lead to silent failures that might compromise the contract's security.
Here, the pitfall for the contract exists where send
is used without checking the response.
A failed send transaction doesn’t revert the payedOut
state.
As a result, any attacker can exploit this by calling the withdrawLeftOver
function to withdraw the remaining balance.
We’ve seen this error too many times.
Instead of send
, we use transfer
which reverts on failure.
Here’s how we’d fix the code:
By replacing send
with transfer
, the contract automatically reverts if the transaction fails, ensuring payedOut
is only set to true on success.
Ethereum smart contracts often interact with external contracts.
This requires them to make external calls. These calls are vulnerable to attacks, as seen in the infamous DAO hack.
Attackers exploit this vulnerability when a contract sends ether to an unknown address. The attacker can create a contract at that address with malicious code in the fallback function.
When the original contract sends ether, the malicious code is triggered, potentially compromising the contract.
Re-entrancy attacks exploit external calls in contracts. When a contract sends ether to an address with malicious code, it can re-enter the vulnerable contract and manipulate state variables.
Here, the transfer of ether is vulnerable. An attacker can create a malicious contract that re-enters the EtherStore contract and drains funds.
Re-entrancy attacks can be tricky, but we’ve got it covered.
First, we use transfer
instead of call.value()
for transfers.
Second, we move state changes before the external call.
By making these changes, we can prevent re-entrancy attacks effectively.
In Solidity, visibility specifiers determine how functions can be called—whether by external users, other derived contracts, or internally only. Incorrect use of these specifiers can lead to significant vulnerabilities in smart contracts.
By default, functions are public
, meaning anyone can call them if no visibility is specified. This becomes a problem when developers forget to specify visibility for functions that should be private or internal. For example:
In this example, the _sendWinnings
function is public by default, allowing anyone to call it and steal funds.
We always make visibility explicit. It’s a simple but crucial fix:
By setting _sendWinnings
to private
, we ensure it can only be called within the contract.
The visibility of the functions should be specified explicitly, even if they are to be kept public, it should be mentioned.
Constructors are special functions used to perform critical and privileged tasks during the initialization of contracts. Before Solidity v0.4.22, constructors had the same name as the contract itself. However, if the contract name is changed during development but the constructor name remains unchanged, this loophole can give attackers easy access to your smart contract.
This mismatch can lead to severe consequences. For example:
In this example, a typo in the constructor name allows anyone to call it and become the owner.
Here is the fix:
Using constructor ensures the initialization function can’t be called after deployment.
In Solidity, tx.origin
is a global variable that contains the address of the account that originally initiated the call or transaction. Using this variable for authentication is dangerous, as it makes the contract vulnerable to phishing attacks.
Contracts that use tx.origin
for user authorization are exposed to external attacks, potentially leading users to perform authenticated actions on malicious contracts. For example:
Here, authorizing withdrawAll
with tx.origin
makes the contract vulnerable to phishing.
We avoid tx.origin
for authorization and stick to msg.sender
. Here’s how:
We generally avoid using tx.origin for authorization in smart contracts. Although the use of tx.origin isn’t strictly prohibited, it has some specific use cases. We can use tx.origin to deny external contracts from calling the present contract, it can be executed with require of the form require(tx.origin == msg.sender). It is done to avoid the calling of intermediate contracts to call the current contract which limits the contract to regular codeless addresses.
Integer overflow and underflow happen when arithmetic operations exceed the storage capacity of a data type, leading to unexpected results.
Underflow occurs when a value drops below zero, while overflow happens when it surpasses its maximum value. These vulnerabilities can cause unexpected behavior in smart contracts, potentially resulting in financial losses or system failures.
Consider a uint8
variable, which can hold a maximum value of 255. If you subtract 1 from a uint8
set to 0, it wraps around to 255 (underflow). Conversely, adding 1 to a uint8
set to 255 resets it back to 0 (overflow).
With Solidity 0.8 and later, this concern has been addressed. The compiler automatically checks each arithmetic operation for overflow and underflow, throwing an error if detected.
For Solidity versions below 0.8, use the SafeMath library to prevent these issues. SafeMath provides functions to safeguard against overflow and underflow, ensuring the integrity of arithmetic operations in smart contracts.
By adopting these practices, you can safeguard your smart contracts from the pitfalls of integer overflow and underflow.
An access control vulnerability lets unauthorized users access or modify a contract’s data or functions. This happens when the code doesn't restrict access based on permissions. Such issues can affect governance and critical functions like minting tokens, voting, withdrawing funds, pausing/upgrading contracts, and changing ownership.
Common Access Control Vulnerabilities
Example of Improper Access Control
Grant only the minimum access levels needed for entities to perform their tasks – Principle of Least Privilege (PoLP). This principle prevents unauthorized parties from performing destructive actions, such as draining funds or altering critical variables.
Implement PoLP by using access control modifiers like onlyOwner
from OpenZeppelin’s Ownable contract or role-based access control (RBAC) to define specific roles with distinct permissions.
Here is how you solve it:
In the code above, RBAC assigns user permissions based on predefined roles, providing granular control. It supports multiple roles, like onlyAdminRole and onlyModeratorRole, enhancing security by limiting access to necessary functions. OpenZeppelin’s AccessControl contract simplifies RBAC implementation in smart contracts.
Front-running occurs when a malicious actor jumps ahead of a transaction by submitting a similar one with a higher gas fee. Since June 2020, Maximum Extractable Value (MEV) traders, employing bots, have racked up over $1 billion on Ethereum, BSC, and Solana, often at the expense of retail investors.
Three Types of Front-Running Attacks
Employ commit-reveal schemes to conceal bid details until after the bidding period.
To safeguard swapping applications, enforce a slippage restriction between 0.1% and 5%, depending on network fees and swap size. This minimizes slippage, protecting trades against front-runners who exploit higher rates.
DoS attacks disrupt contract functionality by exploiting reverts, external call failures, and gas limit issues, rendering it inaccessible to legitimate users.
When a contract operation fails, it reverses changes. The EVM uses REVERT (0xFD) and INVALID (0xFE) opcodes to handle errors. REVERT returns remaining gas to the caller, while INVALID doesn’t return any gas.
DoS with Unexpected Revert Example
Adopt pull over push payment patterns to prevent DoS. The pull pattern shifts fund withdrawal responsibility to the recipient, preventing contract lockup due to failed transfers. A fully mitigated code should store pending withdrawals, enabling users to claim them.
These revisions aim to maintain clarity and conciseness while highlighting the key aspects of each vulnerability and its mitigation strategies.
Generating random numbers on Ethereum is tricky due to its deterministic nature. Solidity relies on pseudorandom factors, and insecure methods can lead to exploitation.
Common Weak Random Generation Methods
Weak Randomness Mitigation
By implementing these strategies, you can ensure secure and unpredictable random number generation in your smart contracts.
As we conclude our discussion on common pitfalls in Solidity, it’s crucial to remember that smart contracts are immutable once deployed.
This underscores the importance of thorough security testing and auditing before deployment.
At QuillAudits, we specialize in identifying and mitigating risks to ensure your contracts remain secure.
Not sure if your code is good to deploy or not?
Consider leveraging our QuillShield AI or our manual audit service to safeguard your project from vulnerabilities.
Protect your smart contracts with our expertise and stay confident in their safety and reliability.
Get Pure Alpha Straight to Your Inbox. Miss this, and you’re missing out.
Insider Secrets - Delivered Right to You. Subscribe now.