reentrancy-pattern-analysis

Reentrancy Pattern Analysis

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "reentrancy-pattern-analysis" with this command: npx skills add quillai-network/qs_skills/quillai-network-qs-skills-reentrancy-pattern-analysis

Reentrancy Pattern Analysis

Systematically detect all variants of reentrancy vulnerabilities by mapping the relationship between external calls and state changes across the entire contract system.

When to Use

  • Auditing any contract that makes external calls (ETH transfers, token interactions, cross-contract calls)

  • Reviewing contracts integrating with callback-enabled token standards (ERC-777, ERC-1155)

  • Analyzing DeFi protocols with multi-contract architectures

  • Verifying reentrancy guard coverage across all entry points

  • When traditional tools only check for classic reentrancy but miss cross-function or read-only variants

When NOT to Use

  • Pure state variable analysis without external calls (use state-invariant-detection)

  • Access control consistency checking (use semantic-guard-analysis)

  • Full multi-dimensional audit (use behavioral-state-analysis, which orchestrates this skill)

Core Concept: The CEI Invariant

Checks-Effects-Interactions (CEI) is the fundamental safety pattern:

  1. CHECKS — Validate all conditions (require statements, access control)
  2. EFFECTS — Update all state variables
  3. INTERACTIONS — Make external calls (ETH transfers, token calls, cross-contract)

Any function that performs INTERACTIONS before completing all EFFECTS is potentially vulnerable to reentrancy.

The Five Reentrancy Variants

Variant 1: Classic Single-Function Reentrancy

The original and most well-known pattern. A function makes an external call before updating its own state, allowing the callee to re-enter the same function.

// VULNERABLE function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); (bool success, ) = msg.sender.call{value: amount}(""); // INTERACTION before EFFECT require(success); balances[msg.sender] -= amount; // State update AFTER external call }

Detection: Find functions where state writes to variables used in require checks occur AFTER external calls.

Variant 2: Cross-Function Reentrancy

Two or more functions share state, and an attacker re-enters through a DIFFERENT function than the one making the external call.

function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount); (bool success, ) = msg.sender.call{value: amount}(""); require(success); balances[msg.sender] -= amount; }

// Attacker re-enters HERE during withdraw's external call function transfer(address to, uint256 amount) public { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; balances[to] += amount; }

Detection: For each external call in function F, check if any OTHER public function reads/writes the same state variables that F modifies after the call.

Variant 3: Cross-Contract Reentrancy

The re-entry occurs through a different contract that shares state or trust relationships with the vulnerable contract.

// Contract A function withdrawFromVault() public { uint256 shares = vault.balanceOf(msg.sender); vault.burn(msg.sender, shares); // External call — attacker can re-enter Contract B (bool success, ) = msg.sender.call{value: shares * pricePerShare}(""); require(success); }

// Contract B (attacker re-enters here) function borrow() public { uint256 collateral = vault.balanceOf(msg.sender); // Reads stale state! // Shares not yet burned, so collateral appears inflated uint256 loanAmount = collateral * maxLTV; token.transfer(msg.sender, loanAmount); }

Detection: Map all cross-contract dependencies. For each external call, identify which other contracts read the state that should have been updated.

Variant 4: Read-Only Reentrancy

A view/pure function returns stale state during a reentrancy callback. No state is modified during re-entry — the attacker exploits the READING of inconsistent state by a third-party contract.

// Pool contract function removeLiquidity() external { uint256 shares = balances[msg.sender]; // Burns LP tokens (updates internal accounting) _burn(msg.sender, shares); // External call BEFORE updating reserves (bool success, ) = msg.sender.call{value: ethAmount}(""); // Reserves updated AFTER the call totalReserves -= ethAmount; }

// This view function returns stale data during the callback function getRate() public view returns (uint256) { return totalReserves / totalSupply(); // totalReserves not yet updated! }

// Third-party contract reads the inflated rate function priceOracle() external view returns (uint256) { return pool.getRate(); // Returns wrong value during reentrancy }

Detection: For each external call, identify view functions that read state variables modified AFTER the call. Check if any external protocol depends on those view functions.

Variant 5: ERC-777 / ERC-1155 Callback Reentrancy

Token standards with built-in callback hooks that execute arbitrary code on the receiver during transfers.

// ERC-777: tokensReceived() hook called on recipient // ERC-1155: onERC1155Received() hook called on recipient // ERC-721: onERC721Received() hook called on recipient

function deposit(uint256 amount) public { token.transferFrom(msg.sender, address(this), amount); // Triggers callback! // If token is ERC-777, msg.sender's tokensReceived() runs HERE balances[msg.sender] += amount; // State update after callback }

Detection: Identify all token transfer /transferFrom /safeTransfer calls. Check if the token could be ERC-777/ERC-1155/ERC-721. Verify state updates happen before the transfer.

Three-Phase Detection Architecture

Phase 1: Call Graph Construction

Build a complete map of all external interactions.

For each function, extract:

Function: withdraw() ├── External Calls: │ ├── msg.sender.call{value: amount}("") at line 45 │ ├── token.transfer(user, amount) at line 48 │ └── oracle.getPrice() at line 42 ├── State Writes: │ ├── balances[msg.sender] -= amount at line 50 │ └── totalWithdrawn += amount at line 51 ├── State Reads (in requires): │ └── balances[msg.sender] at line 41 └── Modifiers: └── nonReentrant: NO

Call Classification:

Call Type Reentrancy Risk Examples

ETH transfer via call

HIGH addr.call{value: x}("")

Token transfer /transferFrom

MEDIUM-HIGH ERC-777 hooks, ERC-1155 callbacks

safeTransferFrom (NFT) MEDIUM ERC-721 onERC721Received callback

Cross-contract function call MEDIUM otherContract.doSomething()

staticcall / view calls LOW Cannot modify state but can trigger read-only reentrancy in callers

delegatecall

HIGH Executes in caller's context

Phase 2: CEI Violation Detection

For each function with external calls, verify CEI ordering.

Algorithm:

For each function F with external calls:

  1. E = set of all state variables written by F
  2. C = set of all state variables read in require/if checks
  3. I = position of each external call in F
  4. For each external call at position P: a. W_after = state writes that occur AFTER position P b. If W_after ∩ (E ∪ C) ≠ ∅: → CEI VIOLATION: state modified after external call c. Classify violation:
    • W_after ∩ C ≠ ∅ → Classic reentrancy (check variable modified after call)
    • W_after ∩ E ≠ ∅ → State inconsistency window

Cross-Function Extension:

For each external call in function F at position P: W_before = state variables NOT yet updated at position P For each OTHER public function G: R_G = state variables read by G W_G = state variables written by G If R_G ∩ W_before ≠ ∅ OR W_G ∩ W_before ≠ ∅: → CROSS-FUNCTION REENTRANCY: G can be called during F's external call with inconsistent state

Phase 3: Guard Coverage Verification

Check that reentrancy protections are correctly applied.

Guard Types:

Guard Coverage Limitations

nonReentrant modifier (OpenZeppelin) Single contract, all functions with modifier Does not protect cross-contract reentrancy

CEI pattern compliance Per-function Must be verified for every function individually

transfer() / send() (2300 gas) Limits callback gas NOT safe — EIP-1884 changed gas costs; don't rely on this

Pull payment pattern Eliminates external calls from state changes Requires architectural change

Verification:

For each function F with CEI violations:

  1. Check if F has nonReentrant modifier → Mitigated (single-contract only)
  2. Check if ALL functions sharing state also have nonReentrant → Mitigated (cross-function)
  3. Check if cross-contract consumers are protected → Requires manual review
  4. If no guard → VULNERABLE

Workflow

Task Progress:

  • Step 1: Identify all external calls in every function (ETH transfers, token calls, cross-contract)
  • Step 2: Build call graph with state read/write positions relative to each call
  • Step 3: Detect CEI violations (state writes after external calls)
  • Step 4: Detect cross-function reentrancy (shared state across functions)
  • Step 5: Detect callback vectors (ERC-777, ERC-1155, ERC-721 token interactions)
  • Step 6: Detect read-only reentrancy (view functions reading stale state)
  • Step 7: Verify guard coverage (nonReentrant, CEI compliance, pull patterns)
  • Step 8: Score findings and generate report

Output Format

Reentrancy Analysis Report

Finding: [Title]

Function: functionName() at Contract.sol:L42 Variant: [Classic | Cross-Function | Cross-Contract | Read-Only | Callback] Severity: [CRITICAL | HIGH | MEDIUM] Guard Status: [Unguarded | Partially Guarded | Guarded]

CEI Violation:

  • External call at line [X]: [call expression]
  • State write AFTER call at line [Y]: [state variable] = [expression]

Re-Entry Path:

  1. Attacker calls functionName()
  2. External call triggers callback to attacker contract
  3. Attacker re-enters via [re-entry function]
  4. State variable [name] still has pre-update value
  5. [Exploit consequence]

Impact: [Funds drained, state corrupted, price manipulated, etc.]

Recommendation: [Specific fix — reorder state updates, add nonReentrant, use pull pattern]

Severity Classification

Variant State Modified Funds at Risk Severity

Classic — ETH drain Yes Yes CRITICAL

Cross-function — balance manipulation Yes Yes CRITICAL

Cross-contract — oracle/price manipulation Indirectly Yes HIGH

Read-only — stale price in third-party No (view only) Possibly HIGH

Callback — ERC-777 deposit inflation Yes Possibly HIGH

Any variant with nonReentrant on target Mitigated No LOW/INFO

Advanced Detection: Transitive Reentrancy

Trace reentrancy through multiple contract hops:

Contract A calls Contract B Contract B calls Contract C Contract C calls back to Contract A (or reads A's stale state)

Detection: Build transitive call graph across all contracts in scope. For each call chain A → B → ... → X: If X can call back to any contract in the chain → TRANSITIVE REENTRANCY

Quick Detection Checklist

When analyzing a contract, immediately check:

  • Does any function make an external call (ETH transfer, token transfer, cross-contract) BEFORE completing all state updates?

  • Are there multiple public functions that modify the same state variables, where at least one makes an external call?

  • Does the contract interact with ERC-777, ERC-1155, or ERC-721 tokens (callback hooks)?

  • Do view functions read state that is only partially updated during an external call?

  • Is nonReentrant applied to ALL functions that share state with a function making external calls, not just the calling function itself?

  • Does the contract rely on transfer() or send() for reentrancy protection? (Unsafe assumption)

For detailed variant taxonomy, see {baseDir}/references/reentrancy-variants.md. For real-world case studies, see {baseDir}/references/case-studies.md.

Rationalizations to Reject

  • "We use transfer() so reentrancy is impossible" → EIP-1884 changed gas costs; transfer is no longer considered safe

  • "The function has nonReentrant " → Check cross-function and cross-contract paths; one modifier doesn't protect everything

  • "It's just a view function" → Read-only reentrancy can manipulate prices and oracles in third-party contracts

  • "We only interact with standard ERC20 tokens" → ERC-777 is backward-compatible with ERC20; token type may change

  • "The external call is to a trusted contract" → Trust boundaries shift; verify the actual code path through all intermediaries

  • "State is updated right after the call" → "Right after" is too late; the call already happened

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Research

semantic-guard-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

behavioral-state-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
General

state-invariant-detection

No summary provided by upstream source.

Repository SourceNeeds Review
Research

learn-anything-in-one-hour

Teach users any new skill/knowledge X in ~1 hour using a fixed 4-step workflow optimized for complete beginners, focusing on 80/20 rule for maximum value in minimum time. Triggers when user asks to learn something new quickly, or mentions "learn X in one hour".

Archived SourceRecently Updated