f in x
Smart Contract Security Audit — Reentrancy and Overflow Developers Can't Ignore
> cd .. / HUB_EDITORIALE
Trend emergenti e tecnologie

Smart Contract Security Audit — Reentrancy and Overflow Developers Can't Ignore

[2026-06-28] Author: Ing. Calogero Bono
Zenithby Meteora Web Il sistema operativo della tua attività. Social, clienti, prenotazioni e fatture in un'unica piattaforma. Palestre, barber, professionisti. Scopri Zenith Demo gratis · senza carta

You deployed a smart contract on Ethereum. It seems to work. Then someone calls a function multiple times before the first one finishes, and the balance goes to zero. Or an arithmetic overflow lets them mint tokens out of thin air. This is not science fiction: it's reentrancy and overflow, the two black holes of on-chain security.

At Meteora Web, we don't write smart contracts every day, but we work with developers who do, and we've seen projects blow up over a single line of code. We come from accounting: a smart contract bug is like a double-entry balance off by hundreds of thousands of euros. On the blockchain, there's no easy reversal.

This guide is for devs who have already written at least one Solidity contract and want to understand how to prevent reentrancy and overflow, and what to look for in an audit. No abstract theory: real code examples, tools we use, mistakes we've seen.

How does a reentrancy attack work and how to prevent it in a smart contract?

The attack exploits an external call (`call`, `delegatecall`, `send`) made before the contract state is updated. The attacker's contract calls the original function again before the first execution finishes, creating a draining loop.

Sponsored Protocol

Classic vulnerable withdrawal contract

// VULNERABLE
contract VulnerableBank {
    mapping(address => uint) public balances;
    
    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount);
        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "Failed to send");
        balances[msg.sender] -= _amount; // updates AFTER call
    }
    
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
}

If `msg.sender` is a contract with a `fallback` or `receive` that calls `withdraw` again, funds are drained until the contract's balance is zero.

Prevention: Checks-Effects-Interactions pattern

Simple rule: update state before making external calls.

// SAFE
contract SecureBank {
    mapping(address => uint) public balances;
    
    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount);
        balances[msg.sender] -= _amount; // UPDATE FIRST
        (bool sent, ) = msg.sender.call{value: _amount}("");
        require(sent, "Failed to send");
    }
}

Alternative: use a mutex (lock). OpenZeppelin provides ReentrancyGuard.

Sponsored Protocol

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureBank is ReentrancyGuard {
    function withdraw(uint _amount) public nonReentrant {
        // logic here
    }
}

Tools to detect reentrancy

  • Slither: static analysis; detects reentrancy patterns (run slither . --detect reentrancy).
  • Mythril: symbolic analysis; finds runtime vulnerabilities.
  • Echidna: fuzzing; tests specific properties.

What are arithmetic overflows in smart contracts and how to avoid them?

In Solidity < 0.8, integer overflows don't revert: if a uint8 reaches 256, it wraps to 0. In version >= 0.8, the compiler automatically checks arithmetic overflows (except inside unchecked blocks). But even with 0.8+, some operations in unchecked, derived contracts, or custom libraries can cause overflows.

Example overflow (pre-0.8)

// VULNERABLE (before Solidity 0.8)
contract Token {
    mapping(address => uint) public balances;
    uint public totalSupply;
    
    function mint(address to, uint amount) public {
        totalSupply += amount; // possible overflow
        balances[to] += amount;
    }
}

If totalSupply is uint256, overflow is unlikely but possible with malicious inputs. In practice, danger comes from implicit casts, operations on uint8, or intermediate calculations.

Sponsored Protocol

Prevention

  • Use Solidity >= 0.8 for automatic checks.
  • Avoid unchecked blocks unless strictly necessary and tested.
  • Use SafeMath if working with versions < 0.8 (OpenZeppelin SafeMath).
  • Watch out for casts: uint256(uint160(address)) is safe, but uint8(uint256) truncates without warning.

What tools should you use for an effective security audit?

An audit is not just tools: it's a process. We recommend a three-layer approach: static, dynamic, and manual.

1. Static analysis with Slither

Slither is our starting point. It analyzes code without executing and detects dozens of vulnerabilities: reentrancy, timestamp dependence, tx.origin, uncontrolled delegatecall, and more.

pip install slither-analyzer
slither . --print human-summary --print contract-summary

2. Dynamic analysis with Foundry

Foundry (forge) lets you write tests in Solidity and run fuzzing. We write property-based tests: "for any input, the total balance remains unchanged".

// Fuzzing test with Foundry
function testWithdrawFuzz(uint amount) public {
    vm.assume(amount <= depositBalance);
    uint before = address(this).balance;
    vault.withdraw(amount);
    assert(address(this).balance == before - amount);
}

3. Manual review and economic invariants

No tool catches business logic bugs. Example: a function that lets you withdraw more than expected when combined with a discount. This needs a human auditor. At Meteora Web, when we audit for clients, we write a list of invariants (e.g., "the sum of all balance mappings must equal the contract's Ether balance") and check them one by one.

Sponsored Protocol

Additional tools

  • MythX: cloud-based analysis (paid), slower but deeper.
  • Echidna: property-based fuzzing, ideal for complex contracts.
  • 4nalyzer: reporting tool that combines outputs from multiple scanners.

What other common security bugs appear in smart contracts besides reentrancy and overflow?

Although our focus is reentrancy and overflow, a thorough audit covers at least these:

  • Timestamp dependence: using block.timestamp for randomness -> miner-extractable.
  • Tx.origin authentication: tx.origin == owner can be bypassed via intermediate contract.
  • Uncontrolled delegatecall: if a contract delegates calls to arbitrary addresses, the caller gains control.
  • Front-running: transactions visible in mempool; commit-reveal patterns can mitigate.

How much does an audit cost and how to decide if you need one?

A professional audit for a simple contract (a few hundred lines) starts around $3,000–5,000 USD; complex DeFi protocols can cost $50,000+.

Sponsored Protocol

But the cost of a bug is potentially unlimited: in 2016, a reentrancy in The DAO led to a $60 million theft. Today DeFi projects regularly lose millions to avoidable bugs. Our advice: if your contract handles real value, don't skip the audit. For prototypes, at least run Slither and fuzzing tests.

What to do now

  • Review your smart contract and apply the Checks-Effects-Interactions pattern.
  • Run slither . --detect reentrancy and fix every warning.
  • Check your compiler version: if < 0.8, upgrade or integrate SafeMath.
  • Write at least one fuzzing test with Foundry for every state-modifying function.
  • If you manage other people's funds, hire an external auditor for a formal review.

Deepen your knowledge with our Blockchain and Web3 for Developers pillar page for a complete overview.

Useful references: Official Solidity Documentation, Slither on GitHub, OpenZeppelin Contracts.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()