Logo
blank Skip to main content

Smart Contract Vulnerabilities and Their Mitigation Through Security Audits

Smart contracts are a rapidly growing technology, with the global smart contracts market expected to reach $73.7 billion by 2030. Smart contracts are used in a wide variety of applications, including decentralized finance (DeFi), non-fungible tokens (NFTs), and supply chain management. Essentially, smart contracts are at the heart of decentralized blockchain networks. 

Smart contracts are a powerful tool, but they are also susceptible to a variety of vulnerabilities. By understanding the different types of vulnerabilities and how to avoid them, businesses can reduce their risk of loss and protect their assets. Even the most careful developers can make mistakes, so itโ€™s important to have an independent third party conduct regular security audits to discover and mitigate these vulnerabilities in a timely manner.

In this article, we explore different types of smart contract vulnerabilities and how to avoid them. This article will be relevant to business owners and tech leaders of blockchain projects who are considering the security of their existing or new solutions.

Understanding smart contracts and their vulnerabilities

What are smart contracts?

Smart contracts are self-executing contracts (written in code) that contain the terms of an agreement between a buyer and seller of tokens. The code and the agreements within it exist across a distributed, decentralized blockchain network. Basically, smart contracts are the core of decentralized applications. Here are their key characteristics:

core features of smart contracts
  • Code-based agreements. The code defines the rules and conditions of an agreement. Writing your agreements in code as smart contracts makes them more efficient, secure, and transparent.
  • Automated execution. Once deployed on a blockchain, a smart contract is automatically executed when the specified conditions are met. For example, a payment can be triggered at the moment the product is delivered without any manual confirmation.
  • Trustlessness. Smart contracts operate in a trustless environment, meaning that participants donโ€™t need to trust each other. The code and the blockchainโ€™s consensus mechanism ensure contract execution.
  • Immutability. Immutability guarantees that terms described in a smart contract cannot be changed without the consensus of the network.
  • Decentralization. This guarantees the execution of agreements without the need for central authorities like banks, notaries, or lawyers.

Even though there are a lot of benefits to using smart contracts, they come with some weak points as well.

What are smart contract vulnerabilities?

Smart contracts are software, and like all software, they can have vulnerabilities. For example, since smart contracts exist in the form of publicly available code, the results of transactions are transparent and open to everyone. This transparency may give malicious actors a chance for smart contract exploits. A malicious actor might use publicly available information to perform an attack that exploits vulnerabilities present in the smart contract or to execute a social engineering attack. 

Attacks on smart contract security can have serious consequences. For example, users may lose their funds or have their data compromised, or businesses may have their operations disrupted. Also, exploitation of a smart contract can damage the reputation of the blockchain platform on which it is deployed. This can lead to users losing trust in the platform and its applications.

From our experience in cybersecurity, Apriorit experts have some general recommendations for dealing with smart contract vulnerabilities with the help of security audits. Smart contracts are complex and can contain subtle errors that can be difficult for developers to find. A smart contract security audit can help to identify these errors and provide recommendations for fixing them. Also, when it comes to the security of blockchain projects, security audits can help to make sure that usersโ€™ funds are protected along with their data. Here are some of the reasons why you need to regularly conduct a smart contract audit:

  • Smart contracts are immutable. Once a smart contract is deployed on a blockchain, it cannot be changed. This means that any vulnerabilities in the contract can be exploited indefinitely.
  • Smart contracts are often used to manage large sums of money or other valuable assets. This makes them a prime target for attackers.
  • Smart contract vulnerabilities are often complex and difficult to detect. Even experienced developers can miss vulnerabilities in their own code.

At Apriorit, we have performed dozens of smart contract audits for various blockchain networks. At the end of each audit, our smart contract auditors offer a detailed description of vulnerabilities found, an assessment of possible risks, and options for their mitigation. 

We often see vulnerabilities in the following categories:

CategoryPossible consequencesRemediation
Denial-of-serviceTemporary or permanent unavailability 
Locking of funds
Additional verification 
Architectural changes
Incorrect third-party service usageIncorrect behaviorAdditional verifications
Mathematical errorsLoss of fundsChanges to mathematical operations 
Logic errorsIncorrect behaviorAdditional verifications 
Architectural changes
TransparencyTrust issuesArchitectural changes

One possible way to prevent some of these issues during development is to have comprehensive documentation and to cover all logic with unit tests. Before going live, smart contracts need to be audited to ensure their safety. Letโ€™s look at these categories of vulnerabilities in more detail with some examples. 

Denial-of-service

Denial-of-service (DoS) vulnerabilities can significantly impact a smart contractโ€™s availability temporarily or even render a smart contract completely unusable. Letโ€™s briefly explain how this kind of attack works.

One of the most severe consequences of a DoS attack is a situation in which a contractโ€™s funds become locked, making it impossible to withdraw tokens. This may be caused by: 

  • Unrestricted loops 
  • Incorrect or lacking functionality

Letโ€™s take a look at each cause. In our examples, we will use Solidity, but these principles also apply to smart contracts in other languages.

1. Unrestricted loops 

The problem with unrestricted loops in a smart contract is caused by network restrictions. In a blockchain environment, there is a limit on the number of instructions that can be executed within a single transaction. This limitation adds a constraint on the number of iterations a loop can perform, depending on the computational cost of each iteration. 

This problem may occur when the contract allows users to add values to an array and then tries to process all these values in a single transaction. The example below shows a scenario where 100 iterations may be required to complete an operation, but we can only execute 10, so the operation will never be completed.

Solidity
contract LoopDoS { 
โ€ฏ โ€ฏ address[] registrations; 
โ€ฏ โ€ฏ function register() external { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ registrations.push(msg.sender); 
โ€ฏ โ€ฏ } 
โ€ฏ โ€ฏ function process() external { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ for (uint256 i = 0; i < registrations.length; ++i) { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ โ€ฏ โ€ฏ //... 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ } 
โ€ฏ โ€ฏ } 
} 

Most static analyzers will issue a warning as soon as they detect a loop in a smart contract. You can also run unit tests with different numbers of iterations to calculate the upper limit of iterations a contract can safely handle. 

2. Incorrect or lacking functionality 

If a smart contractโ€™s functionality depends on the state of specific variables, itโ€™s important to ensure that the contract can always return to a functional state, regardless of any prior actions. 

The simplest example is when the contract has a pause functionality but lacks the ability to unpause. This limitation often results from certain calculations; for example:

Solidity
contract ConditionDoS { 
โ€ฏ โ€ฏ uint256 a; 
โ€ฏ โ€ฏ uint256 b; 
โ€ฏ โ€ฏ function addToA(uint256 _a) external { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ require(a < 50) 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ a += _a; 
โ€ฏ โ€ฏ } 
โ€ฏ โ€ฏ function addToB(uint256 _b) external { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ require(b < 50) 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ b += _b; 
โ€ฏ โ€ฏ } 
โ€ฏ โ€ฏ function process() external { 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ require(a + b < 100) 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ //... 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ a = 0; 
โ€ฏ โ€ฏ โ€ฏ โ€ฏ b = 0; 
โ€ฏ โ€ฏ } 
} 

In this example, the contract incorrectly verifies variables in the addToA and addToB functions. This oversight can lead to a situation where the systemโ€™s state becomes invalid, preventing execution of the process function that is responsible for resetting the state.

Worried about smart contract security?

Aprioritโ€™s cybersecurity specialists can give you peace of mind by identifying and fixing any vulnerabilities during a security audit.

Incorrect use of third-party services 

Decentralized applications often rely on smart contracts to interact with different third-party services, such as price oracles, swaps, and cross-chain messaging services. Most third-party services have documentation that describes how to securely use them and specifies their limitations.  

Most security issues with third-party services are caused by:

  • Insufficient checking of return values
  • Unverified user input 
  • Undocumented limitations

1. Insufficient checking of return values 

To ensure the secure integration of third-party services, itโ€™s important for smart contracts to thoroughly verify a serviceโ€™s return values. This verification process includes checking the returned status code and validating the correctness of all return values. The return value may be a status code that can take several different values. The contract should be able to correctly process statuses that indicate unexpected behavior in third-party services. 

A return value that consists of multiple fields may be only partially valid. For example, one field may contain chainId, which specifies the network for processing a particular message. Failing to validate this field may lead to unintended consequences, such as the processing of testnet messages on the production network. 

This is why you need to have a rigorous and comprehensive validation process in place. 

2. Unverified user input 

One significant vulnerability arises when smart contracts directly utilize parameters from user input to interact with third-party services. This practice can result in incorrect service use and lead to unpredictable outcomes. Malicious actors might exploit the weaknesses of those services to launch an attack. 

To mitigate this risk, all user input should be validated according to the following rules: 

  • Numeric value is less than A and greater than B 
  • Custom values (such as token addresses) are in a list of valid values 

This validation process serves as a safeguard against misuse or abuse of user inputs.

3. Undocumented limitations 

Sometimes, documentation of third-party services doesnโ€™t cover all of their limitations. In this case, an in-depth analysis and verification of the correct use of these services is crucial. This examination helps to identify potential pitfalls that may not be evident.

Mathematical errors 

Mathematical errors are a significant category of vulnerabilities that stem from incorrect mathematical operations within smart contracts. Some math-related issues, like integer overflow/underflow, have become increasingly rare, as many smart contract languages and compiler tools have implemented built-in validations. Nonetheless, mathematical errors can still occur during development. 

Here are some common math errors that we have found during recent audits: 

  • Precision loss
  • Inconsistent operations

1. Precision loss 

One of the most common mathematical errors is a loss of precision, which often comes from an incorrect order of operations. For example, dividing before multiplying can lead to an incorrect result. Letโ€™s compare two examples that perform the same calculation but in a different order:

OrderDivision firstDivision last
Coderate =  totalBalance / totalUsers 
sharesBalance = userShares ร— rate
balanceShare = totalBalance ร— userShares 
sharesBalance = balanceShare / totalUsers
ValuestotalBalance = 100 
totalUsers = 30 
userShares = 10
totalBalance = 100 
totalUsers = 30 
userShares = 10
Resultsrate = 100 / 30 = 3 
sharesBalance = 10 ร— 3 = 30
balanceShare = 100 ร— 10 = 1000 
sharesBalance = 1000 / 30 = 33

As we can see, the order of division and multiplication affects the precision of the results. When division is done first, the resulting balance has three fewer tokens than when division is done last. 

2. Inconsistent operations 

Inconsistent operations take place when similar calculations are performed in different parts of a smart contract. This problem may be caused by missing steps in calculations or mistakes in formulas, which can lead to incorrect results. For example, a contract may calculate fees and shares with slightly different formulas.

Here is the formula for calculating fees:

Solidity
{ 
โ€ฏ โ€ฏ // get fees 
โ€ฏ โ€ฏ rate = โ€ฏtotalBalance / totalUsers; 
โ€ฏ โ€ฏ sharesBalance = totalUserShares * rate; 
โ€ฏ โ€ฏ fee = totalBalance - sharesBalance; 
} 

And here is the formula for calculating shares:

Solidity
{ 
โ€ฏ โ€ฏ // get shares 
โ€ฏ โ€ฏ balanceShare = totalBalance * userShares 
โ€ฏ โ€ฏ sharesBalance = balanceShare / totalUsers 
} 

In these examples, precision varies when calculating fees compared to calculating user shares. As a result, the total sum of calculated fees and share balances might exceed the total balance.

You can detect such mathematical issues with sufficient unit test coverage. By implementing strict mathematical practices and validating all calculations, developers can minimize the risk of math-related vulnerabilities.

Related project

Developing Smart Contracts for Creating and Selling NFT Images

Read how we partnered with an EU-based startup to create non-fungible tokens (NFTs) using our vast experience in blockchain and web development.

Project details
Developing Smart Contracts for Creating and Selling NFT Images

Logic errors 

Logic errors represent a unique class of vulnerabilities that are highly specific to individual projects. Analyzing and identifying logic errors requires a deep understanding of the systemโ€™s required behavior and implementation. Triggering logic errors often requires performing several operations in a specific sequence. Here are some of the most common logic errors:

  • Inconsistency with documentation 
  • Unhandled negative scenarios
  • Value manipulation 

1. Inconsistency with documentation 

Inconsistency with documentation refers to a mismatch between the written documentation for a smart contract and its actual implementation. A common method for detecting simple logic errors is to compare the projectโ€™s documentation with the actual implementation. Many issues identified through this approach are related to differences in parameters, return values, undocumented behaviors, incorrect formulas, and missing actions. 

From our auditing experience, 90% of inconsistencies are a result of outdated documentation. 

2. Unhandled negative scenarios 

While a smart contract might function correctly when itโ€™s used as intended, malicious actors often seek to exploit smart contracts in unexpected ways. As a safeguard, every variable and operation should be thoroughly analyzed for boundary conditions and the potential impact of various combinations of conditions on the code. The best ways to verify that a contract can handle exceptional scenarios are full unit test coverage and fuzzing.

3. Value manipulation 

Value manipulation vulnerabilities are often associated with flash loan attacks. During these attacks, malicious actors attempt to execute several operations that were originally intended to be separate transactions within a single transaction. Detecting such vulnerabilities can be challenging due to the manipulations you need to perform.

In simple cases, an attacker might directly send an asset to the contract to make its calculations incorrect. As an example, letโ€™s look at a simple contract that accepts deposits and calculates shares: 

Solidity
function deposit(uint256 amount) external { 
โ€ฏ โ€ฏ token.safeTransferFrom(msg.sender, address(this), amount); 
โ€ฏ โ€ฏ balance[msg.sender] += amount; 
} 
function getShare(address user) external view returns(uint256 share) { 
โ€ฏ โ€ฏ share = (balance[user] * 1e9) / token.balanceOf(address(this)); 
}

The deposit function transfers tokens to the contract and updates the userโ€™s balance. However, if tokens are sent directly to the contract, this will increase the contractโ€™s token balance without increasing a userโ€™s balance. If the contract logic assumes that the total sum of shares is 100%, this may lead to various issues, like vulnerability to DoS attacks. 

In more complex scenarios, third-party services can play a crucial role in these attacks. Malicious actors may target critical values of the contract, such as Price or ExchangeRate, to manipulate the contractโ€™s behavior. As a result, the security of smart contracts relies heavily on anticipating and mitigating these intricate logic errors through rigorous analysis and testing.

Transparency 

While smart contract code is inherently open and transparent, trust issues can still emerge due to contract management policies and practices. Lack of trust is caused by numerous scam projects where contract owners abuse admin privileges to steal funds. To make the contract trustworthy, itโ€™s essential to have a management policy resistant to misbehavior and malicious actions. Here are the most common transparency vulnerabilities: 

  • Centralized governance
  • Excessive approval requirements
  • Upgradability

1. Centralized governance

Many smart contract projects rely on centralized governance, where a single individual or organization has full control over the contract. In this case, the risk of malicious action is high, as a single decision by this central authority can lead to malicious actions. For example, the owner may set a 100% fee and drain all tokens from the contract. In contrast, a decentralized governance model requires consensus among stakeholders to make any contract modifications. While it may not completely eliminate the risk of governance misbehavior, it significantly reduces the likelihood of harmful actions.

2. Excessive approval requirements 

In some smart contract platforms, core actions require additional approval from the contract owner. This added layer of approval leads to the question of what will happen if the action isnโ€™t approved. Letโ€™s look at the following code example:

Solidity
function withdraw(uint256 amount) external{ 
โ€ฏ โ€ฏ balance[msg.sender] -= amount; 
โ€ฏ โ€ฏ toWithdraw[msg.sender] += amount; 
} 
function confirmWithdraw(address user) external onlyOwner { 
โ€ฏ โ€ฏ (bool sent,) = user.call{value: toWithdraw[user]}(""); 
โ€ฏ โ€ฏ require(sent, "Failed to send Ether"); 
โ€ฏ โ€ฏ toWithdraw[user] = 0; 
} 

In this code, the ownerโ€™s action is required to prevent users from directly accessing the call function. If the owner ignores the request, the coins will be locked in the contract, potentially causing inconvenience or financial losses for users. 

3. Upgradability 

The immutability of deployed smart contracts prompted the development of various upgradability patterns that allow changes to contract behavior without affecting the contractโ€™s storage. While upgradability is a useful feature for improving and maintaining contracts, it also introduces smart contract risks. For example, an upgraded version of a contract may include undesired functionality that could be exploited by the deployer to steal funds.

To address this risk, itโ€™s important to make upgrades transparent to the community. Also, decentralized governance ensures that upgrade decisions are made collectively and with the best interests of the contractโ€™s users in mind. This combination of transparency and governance guarantees the integrity and security of smart contracts when updates are necessary. 

These are the most widespread blockchain smart contract vulnerabilities today, but they are just some of the most common smart contract vulnerabilities that we find during audits. We show our clients how these vulnerabilities can affect security and provide advice on how to fix them. However, there are many other more specific problems that can be hidden in a smart contract.

Conclusion 

Developing secure smart contracts requires an understanding of common mistakes that can harm your data and reputation. However, it can be challenging to identify and mitigate all potential smart contract security risks by yourself. Independent smart contract security audit is essential for identifying and mitigating potential vulnerabilities.

Apriorit specialists have extensive expertise in blockchain security testing and can deliver the security of your smart contracts on any platform! We use a variety of tools and techniques to identify and fix common smart contract vulnerabilities, including code reviews, static analysis, and dynamic testing.

Are you looking to improve the cybersecurity of your project with the help of smart contracts?

Contact Apriorit today for a security audit, and letโ€™s strengthen your software together.

Have a question?

Ask our expert!

Maryna-Prudka
Maryna Prudka

R&D Delivery Manager

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.