• Hacken
  • Blog
  • Discover
  • List Of Smart Contract Vulnerabilities & How To Mitigate Them

List Of Smart Contract Vulnerabilities & How To Mitigate Them

16 minutes

Introduction

Smart contracts automate and secure blockchain transactions without intermediaries, making them essential for Web3 and decentralized apps. However, they are prone to vulnerabilities. In Q1 2024, smart contract exploits led to almost $45 million in losses across 16 incidents, averaging $2.8 million per exploit. This article explores the top 10 most critical smart contract vulnerabilities and how to mitigate them.

Types of Smart Contract Vulnerabilities

Smart contract vulnerabilities can be categorized into several types, each posing significant risks to blockchain applications. Understanding these vulnerabilities is essential for developing robust and secure smart contracts.

VulnerabilityDescription
ReentrancyExploits the contract’s external call feature, allowing repeated calls before the initial function completes.
Integer Overflow/UnderflowResults from arithmetic operations that exceed the data type’s storage capacity.
Improper Access Control Allow unauthorized users to access or modify contract data or functions (such as unprotected withdrawal) due to inadequate access restrictions
Front-RunningExploits the time gap between the transaction’s broadcast and its inclusion in the blockchain.
Denial of Service (DoS)Making the contract unavailable or unresponsive by consuming all available gas or causing transactions to continually fail.
Weak RandomnessUsing insecure block-related methods to generate random numbers, which can be manipulated.
Vulnerable External CallsRisks associated with making external calls without proper validation.
Logic ErrorsIncludes flaws in the contract’s logic leading to unexpected behaviors.
Oracle ManipulationDistortion of oracle price feeds or other off-chain data to steal assets.
Flashloan AttacksThe use of uncollateralized loans to manipulate markets or exploit contract vulnerabilities.

How Smart Contract Vulnerabilities Occur

Smart contract vulnerabilities typically arise from common coding mistakes or logical errors. Issues like unchecked external calls, improper validation, and arithmetic errors can lead to exploits. Let’s delve into specific vulnerabilities and their mitigations.

1. Reentrancy

A reentrancy attack occurs when a contract makes an external call to another contract before updating its state. The called contract can then call back into the original contract, causing unexpected behavior.

Reentrancy Types

There are several types of reentrancy attacks:

  • Single-Function Reentrancy: The attacker repeatedly calls the same function.
  • Cross-Function Reentrancy: A vulnerability in one function can exploit other functions sharing the same state variables.
  • Cross-Contract Reentrancy: Two contracts sharing the same state fail to update immediately before a cross-contract call.
  • Read-Only Reentrancy: Reentering a view function can cause inconsistent state values, leading other protocols to read incorrect data and take unintended actions.

Code Example Of Reentrancy Vulnerability 

contract Deposit {

    mapping(address => uint) userBalance;

    function deposit() external payable {

        userBalance[msg.sender] = msg.value;

    }

    // this function is vulnerable to reentrancy attacks

    function withdraw() external {

        require(userBalance[msg.sender] >= 0);   

        (bool sent,) = payable(msg.sender).call{value: userBalance[msg.sender]}("");

        require(sent, "Failed to send Ether");

        userBalance[msg.sender] = 0;

    }

}

interface IDeposit {

    function deposit() external payable;

    function withdraw() external;

}

contract AttackDeposit {

    IDeposit private depositContract;

    constructor(address _target) {

        depositContract = IDeposit(_target);

    }

    function attack() external payable {

        require(msg.value == 1 ether, "Invalid attack amount");

        depositContract.deposit{value: msg.value}();

        depositContract.withdraw();

    }

    receive() external payable {

        if(address(depositContract).balance >= 1 ether) {

            depositContract.withdraw();

        }

    }

}

The vulnerability arises when we send the user their requested amount of ether. In this scenario, the attacker exploits the withdraw() function. Because their balance hasn’t been reset to 0, they can transfer tokens despite already having received some. The attack involves invoking the withdraw function in the victim’s contract. Upon receiving the tokens, the receive function inadvertently triggers the withdraw function again. As the check passes, the contract sends tokens to the attacker, subsequently activating the receive function.

Crypto Hacks Involving Reentrancy Attacks

Rari Capital Hack ($80M)

On April 30, 2022, Rari Capital, a decentralized lending and borrowing platform, was hacked due to a flaw in its borrowed code from Compound. The borrow function lacked proper checks-effects-interactions patterns. The attacker exploited this by:

  1. Obtaining a 150,000,000 USDC flashloan.
  2. Depositing it into the fUSDC-127 contract.
  3. Calling the vulnerable borrow function to borrow assets.
  4. The function transferred the borrowed amount before updating the accountBorrows mapping. Without a reentrancy guard, the attacker repeatedly called the borrow function before the mapping updated, draining $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 assets using a flashloan and ran the doTransferOut function five times in a loop. After repaying the flashloan, they took the remaining funds and disappeared with $80 million. Transaction: ​​0xab486012

Orion Protocol ($3M)

On February 2, 2023, the Orion protocol was hacked due to a reentrancy vulnerability in one of its core contracts, resulting in a $3 million loss. ​​The attacker exploited the depositAsset() method of the ExchangeWithOrionPool contract, which lacked reentrancy protection. They created a fake token (ATK) with a self-destruct feature leading to the transfer() function.

Reentrancy Mitigation

Use the Checks-Effects-Interactions pattern to ensure state changes occur before external calls.

Vulnerable Implementation

mapping (address => uint) public balances;

function withdraw(uint _amount) public {

    require(balances[msg.sender] >= _amount);

    (bool success, ) = msg.sender.call{value: _amount}("");

    require(success);

    balances[msg.sender] -= _amount;

}

Recommended Implementation

function withdraw(uint _amount) public {

    require(balances[msg.sender] >= _amount);

    balances[msg.sender] -= _amount;

    (bool success, ) = msg.sender.call{value: _amount}("");

    require(success);

}

In general, to prevent reentrancy attacks, Web3 projects can:

  • Ensure all state changes happen before calling external contracts, i.e., update balances or code internally before calling external code.
  • Use function modifiers that prevent reentrancy. OpenZeppelin provides a ReentrancyGuard contract with this functionality.
  • Employ the pull payment method.
  • Implement emergency stop patterns to prevent multiple executions of vulnerable code.
  • Conduct thorough smart contract testing and auditing.

2. Integer Overflow/Underflow

Integer overflow and underflow occur when arithmetic operations exceed the storage capacity of the data type, leading to unexpected results.

Underflow occurs when a value is decreased below zero, while overflow occurs when it exceeds its maximum value. These vulnerabilities can lead to unexpected behavior in smart contracts, potentially resulting in financial losses or system failures. 

Consider a uint8 variable, which can hold a maximum of 8 bits. This means the highest number it can store is represented in binary as 11111111 (or in decimal as 2^8 − 1 = 255). In the event of underflow, subtracting 1 from a uint8 set to 0 will result in its value wrapping around to 255. Conversely, overflow occurs when attempting to add 1 to a uint8 set to 255, causing the value to reset back to 0.

Code Example Of Integer Overflow/Underflow Vulnerability 

contract MyContract {

    uint256 public a = type(uint256).min; // Minimum value for uint256, which is 0

    uint256 public b = type(uint256).max; // Maximum value for uint256, which is 2^256 - 1

    function add() external {

        // b == 115792089237316195423570985008687907853269984665640564039457584007913129639935

        b = b + 1; // This causes an integer overflow

        // After incrementing, b wraps around to 0

        // b == 0

    }

    function substract() external {

        // a == 0

        a = a - 1; // This causes an integer underflow

        // After decrementing, a wraps around to the maximum uint256 value

        // a == 115792089237316195423570985008687907853269984665640564039457584007913129639935

    }

}

Crypto Hacks Involving Integer Overflow/Underflow

Poolz Finance Hack ($390K)

On March 15th, 2023, Poolz Finance contracts were hacked, resulting in a loss of at least $390K across BSC and Polygon due to an integer overflow vulnerability in the unaudited LockedControl smart contract. The attacker exploited the overflow by manipulating the GetArraySum() method, which increased the sum beyond its maximum limit, allowing them to withdraw excess tokens into their wallet.

PoWHC Hack ($800K)

Proof of Weak Hands Coin (PoWHC), a Ponzi scheme itself, was exploited due to an integer underflow vulnerability, allowing a hacker to steal 866 ETH. The vulnerability in the “approve” function of the ERC-20 implementation led to the balance of a second account being incorrectly adjusted, resulting in an inflated balance. By manipulating the transferFrom() and transferTokens() functions, the attacker caused the second account’s balance to underflow to 2²⁵⁶-1, enabling the theft.

Integer Overflow/Underflow Mitigation

With the release of Solidity 0.8, this concern has been addressed. The compiler now automatically verifies each arithmetic operation for overflow and underflow, and if detected, it throws an error. This alleviates the burden on developers, as they no longer need to manually handle these issues.

For Solidity under version 0.8, use the SafeMath library to prevent overflow and underflow. This library provided functions to safeguard against overflow and underflow vulnerabilities, ensuring the integrity of arithmetic operations in smart contracts.

Vulnerable Implementation

function transfer(address _to, uint256 _value) public {

    balances[msg.sender] -= _value;

    balances[_to] += _value;

}

Recommended Implementation

using SafeMath for uint256;

function transfer(address _to, uint256 _value) public {

    balances[msg.sender] = balances[msg.sender].sub(_value);

    balances[_to] = balances[_to].add(_value);

}

3. Improper Access Control

An access control vulnerability allows unauthorized users to access or modify a contract’s data or functions. These arise when the code fails to restrict access based on permissions. In smart contracts, access control issues can affect governance and critical functions like minting tokens, voting, withdrawing funds, pausing/upgrading contracts, and changing ownership.

Common Access Control Vulnerabilities

  • Missed Modifier Validations: Missing validations in critical functions can lead to contract compromise or fund loss.
  • Incorrect Modifier Names: Errors in modifier names can bypass controls, risking fund loss or unauthorized changes.
  • Overpowered Roles: Excessive privileges create vulnerabilities; use the least privilege principle.

Code Example Of Improper Access Control 

function mint(address account, uint256 amount) public {

    // No proper access control is implemented for the mint function

    _mint(account, amount);

}

Crypto Hacks Involving Improper Access Control

HospoWise Hack

HospoWise was hacked due to a public burn function, allowing anyone to burn tokens. The code’s burn function lacked access control, enabling attackers to burn all Hospo tokens on UniSwap, causing inflation and draining the pool for ETH.

Prevention: Proper access control, such as onlyOwner, or making the function internal.

Rubixy Hack

Rubixy was exploited due to a constructor naming error. The function Fal1out was meant to be the constructor but was callable by anyone, allowing attackers to claim ownership and drain funds.

Prevention: Use proper constructor syntax and careful contract naming.

Improper Access Control Mitigation

  • Grant only the minimum access levels needed for entities to perform their tasks – Principle of Least Privilage (POLP). This principle helps prevent 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 by using role-based access control (RBAC) to define specific roles with distinct permissions.
// This code has not been professionally audited. Use at your own risk.

pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract MyContract is AccessControl {

    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");

    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    address public oracle;

    address public treasury;

    constructor(address minter) {

        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);

        _grantRole(MANAGER_ROLE, minter);

    }

    function setOracleAddress(address _oracle) external onlyRole(MANAGER_ROLE) {

        require(_oracle != address(0), "Invalid oracle address!");

        oracle = _oracle;

    }

    function setTreasuryAddress(address _treasury) external onlyRole(ADMIN_ROLE) {

        require(_treasury != address(0), "Invalid treasury address!");

        treasury = _treasury;

    }

}

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.

4. Front-Running Attack

Front-running occurs when a malicious actor preempts a transaction by submitting a similar one with a higher gas fee. Since June 2020, Maximum Extractable Value traders operating bots (aka MEV bots) have profited over $1 billion on Ethereum, BSC, and Solana, often harming retail investors.

Three Types of Front-Running Attacks

  1. Displacement: Using higher gas fees to prioritize their transaction.
  2. Suppression: Flooding the network with high-fee transactions to delay others.
  3. Insertion: Sandwiching a victim’s transaction between two of their own to profit from price changes.

Front-Running Mitigation

Use commit-reveal schemes to obscure bid details until after the bidding period.

Vulnerable Implementation

function placeBid(uint256 _bid) public {

    require(_bid > highestBid);

    highestBid = _bid;

}

Recommended Implementation

function placeBid(bytes32 _sealedBid) public {

    sealedBids[msg.sender] = _sealedBid;

}

function revealBid(uint256 _bid, bytes32 _secret) public {

    require(sealedBids[msg.sender] == keccak256(abi.encodePacked(_bid, _secret)));

    require(_bid > highestBid);

    highestBid = _bid;

}

To protect swapping applications, implement a slippage restriction between 0.1% and 5%, depending on network fees and swap size. This minimizes slippage, defending against front-runners who exploit higher rates, thus safeguarding your trades and reducing predatory risks.

For a detailed guide on front-running attacks and to learn about other mitigation strategies, see Front-Running In Blockchain: Real-Life Examples & Prevention

5. Denial of Service (DoS) Attack

DoS attacks can disrupt contract functionality by exploiting reverts, external call failures, and gas limit issues, making it unavailable to legitimate users.

DoS with Unexpected Revert

When a contract operation fails, it reverts changes. The EVM uses REVERT (0xFD) and INVALID (0xFE) opcodes to handle these errors, with REVERT returning the remaining gas to the caller and INVALID not returning any gas.

DoS with Unexpected Revert Example 

The unexpected revert occurs when the contract attempts to send 1 ether using the call method. If the recipient is a contract that reverts upon receiving Ether, the transaction fails, preventing the beneficiary flag from being reset. This can lead to a DoS condition, blocking further withdrawals if the same address repeatedly attempts to withdraw.

function withdraw() public {

    require(beneficiaries[msg.sender]);

    beneficiaries[msg.sender] = false;

    (bool success, ) = msg.sender.call{value: 1 ether}("");

    require(success);

}

DoS with Unexpected Revert Mitigation

Use pull over push payment patterns to prevent DoS. The pull pattern shifts the responsibility of withdrawing funds onto the recipient, preventing the contract from being locked due to failed transfers. A fully mitigated code would store pending withdrawals and allow users to claim them. 

function withdraw() public {

    require(beneficiaries[msg.sender]);

    beneficiaries[msg.sender] = false;

    payable(msg.sender).transfer(1 ether);

}

External Call Failures

External calls can fail accidentally or deliberately, causing a DoS condition. 

External Call Failure Mitigation

Let users withdraw funds rather than push them to them automatically.

  • Avoid combining multiple calls in a single transaction, especially in loops.
  • Assume external calls can fail.
  • Implement logic to handle failed calls.

Gas Limit Vulnerabilities

Large arrays or loops can exceed the block gas limit, causing a DoS condition.

Gas Limit Vulnerabilities Mitigation

  • Avoid looping over large arrays.
  • Plan for operations to take multiple blocks and transactions if necessary.

6. Weak Randomness

Generating random numbers on Ethereum is challenging due to its deterministic nature. Solidity relies on pseudorandom factors, and complex calculations are costly regarding gas.

Weak Random Generation Methods

Smart contract developers often use insecure block-related methods to generate random numbers, such as the current block timestamp, difficulty, number, address of the current miner, or the hash of a given block. However, these methods can be insecure because miners can manipulate them, affecting the contract’s logic.

function guess(uint256 _guess) public {

        uint256 answer = uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender)));

}

Weak Randomness Mitigation

  • Using Oracles: Employ external sources of randomness like Chainlink VRF, which provides provably fair and verifiable random numbers.
  • Commitment Schemes: Use commit-reveal approaches, which have applications in coin flipping, zero-knowledge proofs, and secure computation (e.g., RANDAO).
  • Signidice Algorithm: Use cryptographic signatures suitable for PRNG in applications involving two parties. 

7. Vulnerable External Calls

Making external calls without proper validation, like unchecked calls or calls to arbitrary addresses, can lead to unexpected behavior or security risks. 

Unchecked External Calls

The function doesn’t verify the call’s success or failure. Even if the external call fails, the transaction continues, which can lead to unexpected behavior or vulnerabilities.

function externalCall(address _to) public {

    (bool success, ) = _to.call("");

    require(success);

}

Checked External Call

The call is validated and the failure is handled.

function externalCall(address _to) public {

    require(isValidAddress(_to));

    (bool success, ) = _to.call("");

    require(success);

}

function isValidAddress(address _addr) internal pure returns (bool) {

    return _addr != address(0);

}

External Calls to Arbitrary Addresses

Calls to arbitrary addresses refer to interactions with external addresses that are not predetermined or trusted. Attackers can exploit this flaw to run unauthorized code, extract assets, or disrupt the contract’s functionality.

Crypto Hacks Involving Vulnerable External Calls

Dexible Exploit ($2M)

On Feb 20, 2023, the Dexible DEX aggregator and execution management system’s self-swapping function was exploited for its external call vulnerability, which allowed users to define a router contract.

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.

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));

    }

}

Vulnerable External Calls Mitigation

  • Use caution when making external calls: Calls to untrusted contracts can execute malicious code. Treat every external call as a potential security risk and follow best practices to minimize danger.
  • Mark untrusted contracts: Clearly name variables, methods, and contract interfaces to indicate potential risks when interacting with external contracts.
  • Avoid state changes after external calls: Malicious code can hijack control flow during external calls, leading to vulnerabilities like reentrancy. Avoid state changes after making external calls.
  • Do not use transfer() or send(): .transfer() and .send() forward exactly 2,300 gas, which may not be enough for recipients due to changing gas costs. Use .call() instead and check return values.
  • Handle errors in external calls: Use low-level call methods cautiously and always check return values to handle failures.
  • Favor pull over push for external calls: Isolate each external call into its own transaction, especially for payments. Let users withdraw funds rather than push them automatically to avoid gas limit issues.
  • Do not delegatecall to untrusted code: Using delegatecall with untrusted contracts can lead to state changes and potential loss of contract balance.

8. Logic Errors

Logic errors in smart contracts can lead to unintended behaviors, compromising security and functionality.

Logic Error Example

The function blindly adds the provided amount to the sender’s balance without validation, leading to potential issues like overflow and invalid inputs.

function updateBalance(int256 _amount) public {

    balances[msg.sender] += _amount;

}

Reasons for Logic Errors

  • Developer Overload: The developers may have been overburdened with tasks and missed this validation step.
  • Lack of Expertise: Not enough knowledge of best practices and potential pitfalls in smart contract developing.
  • Insufficient Testing: The function may not have undergone thorough testing to catch this logic error.

Logic Error Mitigation

The general idea is to implement thorough testing and code reviews to detect and fix logic errors.

For example, the mitigated code adds a validation check to ensure the amount is positive before updating the balance, preventing overflow and invalid input issues.

function updateBalance(int256 _amount) public {

    require(_amount != 0, "Invalid amount");

    balances[msg.sender] = balances[msg.sender].add(_amount);

}

9. Oracle Manipulation

Oracles are the blockchain’s gateway to the real world. They connect smart contracts to off-chain data (real-world events, price feeds, random number generation). However, oracle manipulation can significantly distort market prices through methods like spoofing, ramping, bear raids, cross-market manipulation, wash trading, and frontrunning.

Crypto Hacks Involving Oracle Manipulation

Inverse Finance ($15.6M)

Attackers manipulated the price of the INV token using SushiSwap’s TWAP oracle, borrowing $15.6M by depositing inflated INV tokens as collateral. Relying on a single oracle was a major problem.

Lodestar ($6.5M)

A bad actor manipulated the price oracle of plvGLP collateral, enabling them to drain the lending pools and profit approximately $6.5 million. The core vulnerability was in how the GLPOracle calculated the price of plsGLP. The attacker manipulated the price by increasing the total assets via the donate function, which pushed the price higher and allowed the attacker to borrow more than the true value of their collateral.

BonqDAO ($1.8M)

Polygon DeFi protocol BonqDAO fell victim to a price oracle hack due to a smart contract code error. The attacker stole 100 million $BEUR stablecoins and 120 million $WALBT. The exploit was enabled by ​​a vulnerability inside the smart contract for price feed that supplies Bonq protocol with the ALBT price from the Tellor Oracle.

AaveV3 (Prevented)

A vulnerability in the fallback Oracle allowed attackers to set arbitrary asset prices, posing a significant security risk. A third-party audit prevented the possible hack. 

Oracle Manipulation Mitigation

  • Use Reliable Oracles: Implement robust, decentralized oracles like Chainlink to minimize manipulation risk. Consider using VWAP (Volume-weighted average price) instead of TWAP (Time-weighted average price) for more accurate and manipulation-resistant prices, as VWAP reflects the asset price across various trading environments.
  • Implement Monitoring: Set up tools like Hacken Extractor to detect and alert on anomalous activities quickly. Continuous monitoring can help identify suspicious patterns or attempts to manipulate oracle prices.
  • Check and Validate Inputs: Ensure that all inputs, especially those affecting prices, are validated and resistant to manipulation. Proper validation can prevent attackers from exploiting price calculation mechanisms.

Learn more with Blockchain Oracles: Their Importance, Types, And Vulnerabilities.

10. Flashloan Attacks

Flashloan attacks use uncollateralized loans to manipulate markets or exploit contract vulnerabilities within a single transaction block. In Q1 2024, 10 high-profile flashloan attacks resulted in $33M in losses. Flashloans are not inherent vulnerabilities within the contract; rather, attackers use them to increase leverage and magnify the impact of existing smart contract weaknesses.

Crypto Hacks Involving Flashloan Attacks

Beanstalk ($181M)

Beanstalk, a stablecoin protocol with a governance structure, was exploited due to inadequate checks against flashloans in its smart contract. The attacker took a massive flashloan, gained a 78% supermajority, and used the emergencyCommit function to pass a unanimous proposal, draining funds from the protocol.

function emergencyCommit(uint32 bip) external {

  require(isNominated(bip), "Governance: Not nominated.");

  // Requires 1 day to be passed (getGovernanceEmergencyPeriod=1day)

  require(

  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%)

  require(

  bipVotePercent (bip). greaterThanOrEqualTo(C.getGovernanceEmergencyThreshold()),   "Governance: Must have super majority." );

  _execute(msg.sender, bip, false, true);

}

Sonne Finance ($20M)

On May 16, 2024, Sonne Finance was exploited for $20 million due to a known vulnerability in Compound V2 forks. Despite warnings from previous incidents, the protocol failed to implement comprehensive safeguards, allowing an attacker to manipulate governance permissions and drain funds using flash loans.

Flashloan Attack Mitigation

  • Audit Smart Contracts: Conduct security audits regularly to identify and fix vulnerabilities. Comprehensive security audits can uncover potential weaknesses in contract logic and implementation.
  • Requiring collateral for loans makes it more expensive for hackers to borrow assets.
  • Implement limits on the size of flash loans that can be taken out, which can help mitigate large-scale attacks.
  • Add time locks to smart contracts to hinder the ability to repay flashloans too quickly, giving developers time to detect and prevent attacks.
  • Projects with governance voting systems must implement an anti-flashloan mechanism to prevent exploiters from using flashloans to gain the majority of the voting power.

Learn more about Flashloan Attacks & Prevention.

Security Patterns for Smart Contracts

Security patterns are essential for developing robust smart contracts. Key patterns include:

  • Checks-Effects-Interactions Pattern: Implement the Checks-Effects-Interactions pattern to ensure state changes occur before external calls and utilize the Reentrancy Guard to prevent reentrancy attacks.
  • Mutex Pattern: Prevents reentrancy by locking the contract during execution.
  • Balance Limit Pattern: Limits a contract’s balance to reduce risk.

Best Practices for Secure Smart Contract Development

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

  1. Reuse Audited Libraries: Use well-established libraries like OpenZeppelin, and thoroughly read documentation to understand and properly use the code.
  2. Test Your Code: Aim for 100% branch coverage, use multiple user tests, and employ test fuzzing (e.g., Foundry framework) to detect errors.
  3. Apply for an Audit: Consider professional audits, such as from Hacken, to identify potential vulnerabilities.
  4. Use SafeERC20 Library: When dealing with ERC20 tokens, utilize the SafeERC20 library to handle operations that could fail.
  5. Employ a MultiSig Wallet: MultiSig wallets can be used for contract ownership to increase security and reduce single points of failure.
  6. Use Locked Pragma Versions: Lock Solidity versions to avoid known bugs and ensure stability (e.g., use 0.8.10 instead of ^0.8.10).
  7. Carefully Interact with Third-Party Contracts: Verify third-party contracts for bugs, audits, and proper usage to minimize external calls.
  8. Exercise Caution with Delegatecall: Understand that delegatecall executes code in the calling contract’s context, preserving msg.sender and msg.value.
  9. Use msg.sender for Authentication: Prefer msg.sender over tx.origin to enhance security and prevent authorization vulnerabilities.
  10. Avoid Arithmetic Overflow and Underflow: Use Solidity versions above 0.8 or employ the SafeMath library for earlier versions.
  11. Utilize Static Code Analyzers: Use tools like Slither for static code analysis to detect vulnerabilities and enhance code quality.
  12. Validate Function Arguments: Ensure all user-provided arguments are validated to prevent malicious input.
  13. Beware of Integer Division Rounding: Use multipliers for precision or store numerator and denominator separately.
  14. Explicitly Mark Visibility: To avoid unintended access, define visibility for functions and storage variables.
  15. Avoid using extcodesize to check for EOAs. extcodesize returns zero during contract construction, which can lead to potential issues.
  16. Restrict Access to Critical Functions: Implement access controls to restrict important functions.
  17. Use Emergency Stop Pattern: Implement Pausable contracts to halt operations in emergencies.
  18. Prefer Pull Over Push Pattern: Use the pull pattern to handle payments securely.

Follow @hackenclub on 𝕏 (Twitter)

Wrapping Up

Given the extensive array of potential vulnerabilities outlined – from specific examples spotlighted to over 30 additional issues identified in our smart contract audit checklist – the threat landscape in the blockchain domain is complex and multifaceted. Smart contract developers are pivotal in creating cutting-edge applications and establishing security measures to protect these contracts from evolving threats. Their challenging task is to stay ahead of these risks, ensuring their applications’ security, integrity, and trustworthiness.

Web3 projects must prioritize security in their blockchain apps. By adhering to guidelines and proactively addressing vulnerabilities, developers can build user trust, protect assets, and contribute to the stability and growth of the blockchain ecosystem. Prioritizing security safeguards individual projects and strengthens the entire decentralized finance space. Let’s work together to create a safer and more secure blockchain environment for everyone.

Subscribe
to our newsletter

Be the first to receive our latest company updates, Web3 security insights, and exclusive content curated for the blockchain enthusiasts.

Speaker Img

Table of contents

  • Introduction
  • Types of Smart Contract Vulnerabilities
  • How Smart Contract Vulnerabilities Occur
  • 1. Reentrancy

Tell us about your project

Follow Us

Read next:

More related

Trusted Web3 Security Partner