DEFI RISK AND SMART CONTRACT SECURITY

The Reentrancy Checklist for Secure DeFi Deployment

8 min read
#Ethereum #Smart Contract #DeFi Security #Security Practices #Audit
The Reentrancy Checklist for Secure DeFi Deployment

Reentrancy attacks, as explored in Reentrancy Attacks Unveiled, are among the most feared and widely exploited vulnerabilities in decentralized finance.
Because many DeFi protocols rely on complex interactions between contracts, a single unchecked reentrancy point can drain funds, rewrite state, or trigger logic that should never execute.
Below is a detailed, step‑by‑step checklist, similar to the approach in a Developers Blueprint to Prevent Reentrancy Attacks in DeFi, that developers, auditors, and security engineers can use to ensure that a DeFi deployment is resilient against reentrancy attacks.


Why Reentrancy Matters

Reentrancy occurs when an external contract calls back into the calling contract before the first call has finished.
In Solidity, the low‑level call methods (call, delegatecall, send) forward the remaining gas to the callee, giving the external contract the opportunity to invoke functions of the original contract again.
If the original contract has not yet updated its internal state, the external contract can repeatedly execute the same logic, often draining the contract’s balance.

The DAO hack, the Parity wallet incident, and numerous recent flash loan exploits all demonstrate that reentrancy is not a theoretical risk but a practical attack vector that can wipe out millions of dollars of capital.


Core Concepts of Reentrancy

Concept Explanation
Entry Point The function that external contracts can call, often withdraw or transfer.
State Update The change to internal variables (e.g., balances[msg.sender] -= amount).
External Call The moment the contract sends Ether or calls another contract.
Reentrancy Window The period between the external call and the subsequent state update.
Reentrancy Attack Pattern An attacker calls back during the window, executing the same logic again.

A safe contract must either eliminate the reentrancy window entirely or guard against reentry through deterministic checks.


Typical Attack Scenarios

Reentrancy attacks usually follow a simple three‑step pattern:

  1. Setup – The attacker creates a malicious contract with a fallback function that calls back into the target contract.
  2. Trigger – The attacker invokes the vulnerable function on the target contract, passing in enough gas to execute the fallback.
  3. Exploit – While the target contract is still processing, the fallback reenters the vulnerable function, consuming balance each time until the target’s funds are depleted.

These patterns can be further abused with flash loans, governance proposals, or side‑channel attacks.
Understanding the full spectrum of reentrancy is essential for writing a comprehensive checklist.


Safe Interaction Patterns

The most reliable method to prevent reentrancy is to avoid the problematic pattern of calling external contracts before updating internal state.
This is known as the Checks‑Effects‑Interactions pattern, which is detailed in Building Safe Smart Contracts Avoiding Reentrancy Traps.

Checks

Verify all pre‑conditions first (e.g., require(balances[msg.sender] >= amount)).
Do not rely on user input alone; always confirm the state.

Effects

Update the contract’s storage before any external call.
Set balances, flags, or other critical variables to their final state.

Interactions

Only after the state has been updated should you send Ether or call another contract.
If the external call fails, the state has already been secured.

By strictly enforcing this order, you close the reentrancy window.


Solidity Safe Practices

1. Use transfer or send with Caution

transfer forwards only 2300 gas, which is usually insufficient for a reentrancy callback.
However, if the receiving contract has a complex fallback, it may revert.
Because send returns a boolean, always check the result.

2. Prefer call with a Gas Limit

If you need to forward more gas, use call{value: amount, gas: gasLimit}("").
Set gasLimit to the minimum required to perform the intended action.
This reduces the attacker’s ability to execute nested calls.

3. Avoid delegatecall to Untrusted Code

delegatecall executes code in the context of the calling contract, meaning state can be altered unpredictably.
Never delegatecall into contracts that you do not control.

4. Protect State with Locks

Implement a simple mutex to block reentrant calls:

bool private locked;

modifier noReentrancy() {
    require(!locked, "No reentrancy");
    locked = true;
    _;
    locked = false;
}

Apply the modifier to every public function that can change state or send Ether.
This approach aligns with the guidance in How to Stop Reentrancy Loops Before They Strike.


Reentrancy Guard Pattern

A reentrancy guard is a widely adopted design pattern that ensures only one execution of a function can occur at a time.
Below is an implementation that follows best practices:

contract ReentrancyGuard {
    uint256 private _status;

    constructor() {
        _status = 1; // _NOT_ENTERED
    }

    modifier nonReentrant() {
        require(_status != 2, "ReentrancyGuard: reentrant call");
        _status = 2; // _ENTERED
        _;
        _status = 1; // _NOT_ENTERED
    }
}

Inherit from ReentrancyGuard and apply nonReentrant to vulnerable functions.
This pattern is robust and inexpensive in terms of gas.


Upgradeable Contracts Considerations

When deploying proxy contracts, the reentrancy guard must be set correctly in the implementation contract.
If the guard is defined in the proxy, upgrades may inadvertently reset the guard state, opening a window for reentrancy.
Ensure that the guard’s storage slot is preserved across upgrades and that the initialization function respects the guard.


Automated Testing and Fuzzing

Testing is essential to catch reentrancy bugs early.
Adopt a multi‑layered testing approach:

Unit Tests

  • Write tests that call vulnerable functions with enough gas to trigger a fallback.
  • Verify that the contract’s balance does not change after a failed reentrancy attempt.

Integration Tests

  • Deploy a mock attacker contract that attempts to reenter during a withdrawal.
  • Assert that the attacker receives no funds and that the original contract remains solvent.

Fuzzing

  • Use tools such as Echidna or MythX to automatically generate inputs that exercise reentrancy paths.
  • Configure fuzzers to monitor state changes after each transaction.

Manual Auditing Checklist

During a security audit—outlined in Securing DeFi Platforms by Mitigating Reentrancy Vulnerabilities—a checklist ensures that no reentrancy gap is left unexplored.

  • Identify All External Calls
    List every call, delegatecall, send, or transfer in the contract.

  • Check the Order of Operations
    Verify that every external call follows a state update.

  • Confirm the Presence of a Reentrancy Guard
    Ensure that a nonReentrant or similar modifier protects critical functions.

  • Review Upgrade Mechanisms
    Confirm that the guard’s state is not reset during an upgrade.

  • Test Against Common Attack Vectors

    • Reenter during a withdrawal.
    • Reenter during a token transfer callback.
    • Reenter through a flash loan.
  • Analyze Gas Requirements
    Verify that the amount of gas forwarded cannot allow an attacker to execute multiple reentrancy loops.

  • Check for Reentrancy in Library Calls
    Even if the main contract is safe, external libraries it calls may introduce reentrancy.

  • Document All Findings
    Record each potential reentrancy point, the mitigation applied, and the test coverage achieved.


Deployment Strategies

Even with a perfect codebase, deployment practices can introduce vulnerabilities.

  • Set a Safe Initial Balance
    Deploy with a minimal balance, adding funds only after the contract passes all tests.

  • Use a Timelock for Major Functions
    Critical functions that modify governance or fees should require a delay, preventing instant exploitation.

  • Restrict Direct Ether Transfer
    If the contract is not intended to receive Ether via receive or fallback, disable these functions.

  • Monitor Gas Prices
    Sudden spikes in gas prices can make attacks cheaper.
    Consider implementing dynamic gas cost checks.


Monitoring and Response

Post‑deployment vigilance is essential.

  • Real‑Time Alerts
    Use tools like Tenderly or Chainlink Keepers to trigger alerts when a function is called unexpectedly or a balance drops below a threshold.

  • Event Logging
    Emit events before and after state updates.
    Anomalies in event patterns may indicate a reentrancy attempt.

  • Emergency Pause
    Include an emergency pause function (pause) that can be called by governance to halt all operations if suspicious activity is detected.

  • Periodic Re‑Audits
    Re‑audit the contract after any significant change, including upgrades or integration of new modules.


Summary

Reentrancy is a persistent threat to DeFi contracts.
By adhering to the Checks‑Effects‑Interactions principle, employing reentrancy guards, rigorously testing, and maintaining vigilant monitoring, developers can dramatically reduce the risk of successful attacks.

The checklist above covers the full lifecycle of a secure DeFi deployment—from code design through post‑deployment operations.
Use it as a living document: update it whenever you introduce new patterns, libraries, or upgrade mechanisms.

A secure contract protects its users, preserves market confidence, and contributes to the overall resilience of the blockchain ecosystem.

Emma Varela
Written by

Emma Varela

Emma is a financial engineer and blockchain researcher specializing in decentralized market models. With years of experience in DeFi protocol design, she writes about token economics, governance systems, and the evolving dynamics of on-chain liquidity.

Contents