• Hacken
  • Blog
  • Discover
  • Smart Contract Vulnerabilities: How To Identify And Prevent Them

Smart Contract Vulnerabilities: How To Identify And Prevent Them

By Saylık Seher

Share via:

Smart contracts are self-executing programs that can be programmed to execute on their own when specific conditions are met. In the area of blockchain and decentralized finance, their popularity is rising. Smart contracts, however, are prone to flaws like all other software. This post covers the most typical smart contract vulnerabilities and their examples, along with solutions for avoiding them.

External Calls to Arbitrary AddressesHigh
Checks-Effects-Interactions Pattern Violation / ReentrancyHigh, Medium
Missing Validation / Input Validation ViolationHigh, Medium
Flashloan AttackHigh
Inconsistent DataHigh, Medium
Floating PragmaLow
Smart contract vulnerabilities and their severity level

Most Common Smart Contract Vulnerabilities

1. External Calls to Arbitrary Addresses (High)

Malicious contracts may withdraw the contract’s balance in response to external calls to arbitrary addresses, resulting in a loss of funds. Due to this flaw, attackers can use the contract’s capabilities to their advantage and run malicious or unauthorized code that can extract assets from the contract or can break the working mechanism of the contract.

Example of the Dexible exploit (20 February 2023). Dexible is a decentralized exchange (DEX) aggregator and execution management system. One of their essential features was self swapping. The selfSwap function allows users to define a router contract which is the main problem that caused this hack.

Attention! This function can be called by anyone and the contract makes external calls to an arbitrary router address.

contract Dexible {
    function selfSwap(address tokenIn, address tokenOut, uint112 uint112 amount, address router, address spender, TokenAmount routeAmount, bytes routerData) external {
        IERC20(routeAmount.token).safeApprove(spender, routeAmount.amount);
        // Here an external call is made to the router
        (bool s, ) = router.call(routerData);

Instead of a safe&valid DEX, a hacker made a contract to call a malicious ERC20 contract and drained $2M worth of tokens. The crucial part is the contracts were not audited.

Sample malicious contract

contract maliciousRouter {
    //Instead of a validated router contract, the hacker implements this tricky function and makes Dexible contract to transfer its assets to this malicious contract.
    function transfer() external {
        IERC20(USDC).transferFrom(msg.sender, address(this), IERC20(USDC).balanceOf(msg.sender));

These code lines are simplified to demonstrate the hack easily. More info and the contract repo can be found here: Dexible. Transaction: 0x138daa4c…

How to prevent External Calls to Arbitrary Addresses?

Developers must implement the necessary security mechanisms to guarantee that external calls are made only to trusted and intended addresses.

2. Checks-Effects-Interactions Pattern Violation / Reentrancy Attack (High)

Reentrancy attacks occur when an attacker exploits a vulnerability in a smart contract that allows them to repeatedly call a function before the previous function call has finished executing. This can occur when a smart contract has a function that calls another contract’s function, but fails to properly update the user’s balance or state before executing the function call.

Example of the Rari Capital hack (30 April 2022). Rari protocol is a decentralized platform that allows lending and borrowing. Protocol’s code was forked from Compound and their developers accidentally used one of their old commits which led them to get hacked.  

Their borrow function was lacking of proper checks-effects-interactions pattern. Seeing this, the exploiter (1) promptly got 150,000,000 USDC as a flashloan, (2) deposited it into fUSDC-127 contract, and (3) called the vulnerable borrow function to borrow some amount of assets.

As we can see, the function firstly transfers the borrowed amount and then updates the accountBorrows mapping. Since the function does not have any reentrancy guard, the hacker called the borrow function repetitively before it updated the mapping and drained the funds worth of $80 million.

function borrow() external {
    doTransferOut(borrower, borrowAmount);
    // doTransferOut: function doTransferOut(borrower, amount) { 
    (bool success, ) = to.call.value(amount)("");
    require(success, "doTransferOut failed");
// !!State updates are made after the transfer
accountBorrows[borrower].principal = vars.accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;
totalBorrows = vars.totalBorrowsNew;…

The hacker borrowed x amount of assets using a flashloan and ran the doTransferOut function five times in a loop. After paying back the flashloan, he/she took the remaining 4x amount and disappeared with it. Transaction: ​​0xab486012 

How to prevent Checks-Effects-Interactions Pattern Violation / Reentrancy?

To prevent reentrancy attacks, there are several things to do according to the implementation. Developers must follow the “checks-effects-interactions” pattern, which involves executing all check statements and state updates before making any external calls. Additionally, it’s necessary to implement a guard to remove the ability to make multiple calls to the same function (see OpenZeppelin’s ReentrancyGuard). Alternatively, smart contract developers can restrict the gas amount available for the function to avoid infinite loops. However, the latter requires some attention since transaction gas limits of a network can be changed by the protocol.

3. Missing Validation/Input Validation Violation (High, Medium)

Example of the Poly Network Exploit (10 August 2021). The Poly Network is a decentralized finance (DeFi) platform enabling interoperability between different blockchains. On August 10, 2021, the platform suffered a massive hack, resulting in a loss of over $600 million worth of cryptocurrency. The attack exploited a vulnerability in the platform’s code, which allowed the attacker to steal assets from different blockchains supported by the platform.

The Poly network has a contract called EthCrossChainManager, which can trigger messages from another chain. Anybody may use the contract to execute a cross-chain transaction by calling the function verifyHeaderAndExecuteTx, which validates the block header and transaction inclusion. The contract’s weakness is the failure to stop users from accessing the EthCrossChainData contract, which manages the list of public keys used to authenticate data from the opposite chain. Users can cause a function to change the public keys by making changes to this list without needing to hack private keys. By delivering a cross-chain message directly to the EthCrossChainData contract, the attacker can force EthCrossChainManager to call the latter contract.

Exploiter used the _method parameter as an open gate to make the EthCrossChainManager contract call the right function since one of the parameters of hashed data can be specified by callers.

This method-ID is generated by keccak-hashing method’s name, including the signature, and then taking the first four bytes of the resulting hash:

   /* @notice Dynamically invoke the targeting contract, and trigger execution of cross chain tx on Ethereum side
    *  @param _toContract The targeting contract that will be invoked by the Ethereum Cross Chain Manager contract
    *  @param _method At which method will be invoked within the targeting contract
    *  @param _args The parameter that will be passed into the targeting contract
    *  @param _fromContractAddr From chain smart contract address
    *  @param _fromChainId Indicate from which chain current cross chain tx comes 
    *  @return true or false
   function _executeCrossChainTx(address _toContract, bytes memory _method, bytes memory _args, bytes memory _fromContractAddr, uint64 _fromChainId) internal returns(bool) {
       ...(success, returnData) = _toContract.call(
abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_args, _fromContractAddr, _fromChainId)));...
bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)")));

Since it converts the hashed data to a bytes4 variable, only 4 bytes will be validated as a signature. As we can guess, it will not be that hard to find the same output with malicious input data by brute-forcing.

  • 0xv14cts34rf44d…
  • 0xv14c2156789a…

Transaction: 0xd8c1f7…. Github repo: EthCrossChainManager 

How to prevent Missing Validation/Input Validation Violation?

Developers should add strong input validation checks to the code to guarantee that user input is structured correctly and is within allowable limitations.

4. Flashloan Attack (High)

Flashloans lets users borrow massive amounts of crypto for just one quick transaction. Due to their adaptability and accessibility they don’t require collateral and may be used for a multitude of purposes. However, because they don’t require any collateral, flashloans can be used to manipulate the market or take advantage of smart contract weaknesses

Example of the Beanstalk DeFi exploit (17 April 2022). Beanstalk is a stablecoin decentralized protocol that rewards users for contributing funds to a central funding pool called “the silo.” The protocol keeps the “bean” token at around $1. The other thing, Beanstalk features a governance structure that allows token holders to vote to change the code, with voting power proportional to the number of tokens owned. This governance setup ultimately led to a vulnerability in the system that resulted in the project’s downfall.

The exploiter created two proposals. One was a request to send all the funds to their address. The other was donating 250000$ in BEAN tokens to Ukraine. And here comes the tricky part: the emergencyCommit function allows users with super majority of funds to approve a proposals.

function emergencyCommit(uint32 bip) external {
  require(isNominated(bip), "Governance: Not nominated.");
  // Requires 1 day to be passed (getGovernanceEmergencyPeriod=1day)
  block.timestamp >= timestamp(bip).add(C.getGovernanceEmergencyPeriod()),     "Governance: Too early.");
  require(isActive(bip), "Governance: Ended.");

  //Any vote can be executed if proposer has the super majority(getGovernanceEmergencyThreshold=67%)
  bipVotePercent (bip). greaterThanOrEqualTo(C.getGovernanceEmergencyThreshold()),   "Governance: Must have super majority." );
  _execute(msg.sender, bip, false, true);

The protocol forgot to get precautions for flashloans enabling the hack just one day after the malicious proposal. The exploiter got a massive flashloan: 350M DAI, 500M USDC, 150M USDT, 32M Bean and 11.6M LUSD. They converted them to BeanStalk DeFi’s governance BEAN3Crv-f and BEANLUSD-f tokens. Reaching the 78% funds majority, they called emergencyCommit to execute both proposals they had created. See the transaction 0xcd314668…, and the BeanStalk contract.

How to mitigate Flashloan Attack?

  1. Require collateral for loans to make it more expensive for hackers to borrow assets.
  2. Implement limits on the size of flash loans that can be taken out, which can help mitigate large-scale attacks.
  3. Add time locks to smart contracts to hinder the ability to repay flashloans too quickly, giving developers time to detect and prevent attacks.

Moreover, projects with governance voting systems must implement anti-flashloan mechanism to block exploiters from using flashloans to gain the majority of the voting power.

5. Inconsistent Data (High, Medium)

When smart contract functions use data without correct validation or verification, several issues may arise. Inconsistent data might result in improper contract execution, which may result in the loss of funds or other unfavorable effects. Inconsistent data can also damage the contract’s reputation and trustworthiness, which will lower adoption and cause a decline in user confidence.

How to prevent Inconsistent Data?

  • Validate and verify data: Data can be validated and verified using methods like hash functions, digital signatures, and encryption.
  • Configure strict access controls: Smart contracts should have strict access restrictions to stop unwanted data alteration.
  • Develop smart contracts thoroughly: Developers should thorough test their contracts, including functional and security testing, to find and correct any data discrepancies or inaccuracies.

6. Floating Pragma (Low)

Solidity pragmas are an important aspect of secure smart contract development, as they define the version of the Solidity compiler to be used for compiling the contract, and can have an impact on the security and functionality of the contract.

Using outdated or vulnerable Solidity versions can potentially lead to security vulnerabilities, as well as unexpected behavior in the contract’s execution. Additionally, certain pragma settings can impact the contract’s gas usage, which can have implications for the contract’s cost and efficiency.

Floating pragma
pragma solidity ^0.8.0;

Secure, locked pragma
pragma solidity 0.8.17;

How to prevent Floating Pragma?

It’s important for smart contract developers to carefully choose and lock the Solidity pragma for their contracts, and to keep them up to date with the latest security patches and best practices.

Checked Items: List Of Vulnerabilities Hacken Looks For During Smart Contract Audit

While we’ve discussed six key vulnerabilities, it’s essential to know that there are numerous other potential pitfalls that may compromise the security of a smart contract. This is where the expertise of professional smart contract auditing companies becomes invaluable. Hacken’s team meticulously investigates all potential weak spots in your smart contracts, leaving no stone unturned.

In our smart contract auditing process, we examine a comprehensive list of common vulnerabilities, with the specific items on this list varying based on the language and platform in use.

Checklist For EVM-Based Smart Contracts (Solidity, Vyper, Yul)

Wrapping Up

Given the extensive array of potential vulnerabilities we’ve outlined – from the specific examples we’ve spotlighted to the over 30 additional issues identified across different platforms in our smart contract audit checklist – it’s evident that the landscape of threats in the blockchain domain is complex and multifaceted. The pivotal role of smart contract developers in this environment cannot be overstated. They bear the dual responsibility of developing cutting-edge blockchain applications, as well as establishing comprehensive security measures to protect these contracts from an ever-growing range of threats and vulnerabilities. Their task, challenging as it may be, is to remain ahead of these evolving risks, ensuring the security, integrity, and trustworthiness of their applications in a dynamic threat environment.

By adhering to these six pillars, developers can build a robust defense system that fortifies their smart contracts’ security:

  1. Best practices: Stick to the standards and best practices while creating and implementing smart contracts.
  2. Testing: Write unit tests before deploying smart contracts to ensure the system runs as intended.
  3. Auditing: Have smart contracts examined by qualified auditors to find any potential security flaws and recommend best practices to strengthen security.
  4. Code reviews: Do code reviews to detect problems, and in particular to respond to them quickly, before the implementation progresses too far
  5. Monitoring: Set up monitoring and alert systems to detect and respond to any unusual activity or suspicious behavior.
  6. Access controls: Put permissions and access controls to specify who can access funds and make changes on the smart contract data to mitigate asset loss due to data manipulation or outside intervention.
subscribe image
promotion image

Subscribe to our newsletter

Enter your email address to subscribe to Hacken Reseach and receive notifications of new posts by email.

Read next:

More related
  • Blog image
    Curve Finance Liquidity Pools Hack Explained Hacker H.
  • Blog image
  • Blog image

Get our latest updates and expert insights on Web3 security