Skip to content

feat: implement CP-121 multi-hop cross-contract reentrancy detector - #112

Merged
Manuelshub merged 1 commit into
StellarChainproof:masterfrom
Manuelshub:feat/cp121-cross-contract-reentrancy
Aug 31, 2026
Merged

feat: implement CP-121 multi-hop cross-contract reentrancy detector#112
Manuelshub merged 1 commit into
StellarChainproof:masterfrom
Manuelshub:feat/cp121-cross-contract-reentrancy

Conversation

@Manuelshub

Copy link
Copy Markdown
Contributor

Closes #66

Summary

Implements CP-121, a new static analysis rule that detects multi-hop cross-contract reentrancy — the class of exploit where Contract A calls into Contract B (directly or through one or more intermediaries), and that chain eventually re-enters Contract A while it still has unfinalized state. This pattern is invisible to single-function detectors (CP-107, CP-107-X) and is responsible for several real-world vault/strategy drains.


Problem

ChainProof's existing reentrancy detectors reason within a single contract boundary. They catch:

VaultA.withdraw():
  reads  balances[user]
  calls  msg.sender.call{value: amount}("")   ← external call
  writes balances[user] = 0                  ← too late (intra-function)

But they miss the cross-contract variant, where the re-entry path spans multiple contracts:

VaultA.withdraw() ──calls──► AttackerB.execute()
AttackerB.execute() ──re-enters──► VaultA.withdraw()   ← balances still stale

And they miss deeper chains:

VaultX.withdraw() ──► RouterY.forward() ──► ReceiverZ.onReceive() ──► VaultX.withdraw()

The multi-file import-graph and call-graph infrastructure already existed in ChainProof but no rule was using it for cross-contract analysis.


Solution

New rule: packages/core/src/rules/cp121-cross-contract-reentrancy.ts

The detector works in three stages:

1. Cross-contract call graph construction

Builds a CrossContractCallGraph from all MergedContractView objects available at scan time. Edges are added for:

  • Typed external calls — state variables with a known contract type (RouterY public router; router.forward(...))
  • Explicit casts — IVault(addr).withdraw()
  • Low-level calls — msg.sender.call{value:...}("") recorded as unresolved edges (potential re-entry points)

2. Bounded DFS traversal

Runs a depth-first search from every node that has outgoing cross-contract edges. Handles two chain patterns:

  • Typed chain: A.f → B.g → A.h — entire path through typed edges
  • Hybrid chain: A.f → B.g → (low-level) → A.f — typed path reaches a node that makes a low-level call back to origin

Default traversal depth: 3 hops. Hard cap: 10 hops. Exceeding the hard cap emits a CP-121-DEPTH-CAP info finding.

3. Unfinalized state analysis

For each candidate origin function, collects state variable accesses in source order and checks:

  • Is the variable read before the first external call site?
  • Is it not written before that same call site?

If yes — the variable is unfinalized. No unfinalized vars → no finding (short-circuits traversal of safe chains).

Guard recognition suppresses findings when the origin function carries:

  • A modifier whose name contains nonReentrant (case-insensitive)
  • A hand-rolled require(!locked) / locked = true mutex pattern

Changes

New files

File Description
packages/core/src/rules/cp121-cross-contract-reentrancy.ts Main detector — graph construction, DFS, state analysis, guard detection, finding builder
packages/core/src/rules/__tests__/cp121-cross-contract-reentrancy.test.ts 30 unit tests
examples/contracts/cross-contract-reentrancy/TwoHopVulnerable.sol 2-hop exploit fixture
examples/contracts/cross-contract-reentrancy/ThreeHopVulnerable.sol 3-hop exploit fixture
examples/contracts/cross-contract-reentrancy/TwoHopGuarded.sol CEI-guarded safe fixture (zero findings expected)
examples/contracts/cross-contract-reentrancy/DeepChain.sol 5-hop depth-cap test fixture

Modified files

File Change
packages/core/src/scanner.ts Imports and invokes detectCrossContractReentrancy once per scan session over all collected MergedContractView objects; attributes findings to each originating contract's source file
README.md Adds CP-121 row to the vulnerability rules table, fixture reference table, and a dedicated "Multi-hop Cross-Contract Reentrancy (CP-121)" documentation section

- Add packages/core/src/rules/cp121-cross-contract-reentrancy.ts with:
  - CrossContractCallGraph construction from all MergedContractViews
  - Bounded DFS traversal (default 3 hops, hard cap 10) for typed + low-level
    re-entry chains
  - UnfinalizedState analysis (reads before writes before external call)
  - ReentrancyGuard recognition (nonReentrant modifier, hand-rolled mutex)
  - Configurable maxDepth with CP-121-DEPTH-CAP info finding when clamped
  - Full callPath, evidence, confidence, swcId in findings

- Integrate CP-121 into scanner.ts: runs once per session over all views,
  findings attributed to originating contract's source file

- Add fixture contracts under examples/contracts/cross-contract-reentrancy/:
  - TwoHopVulnerable.sol  (2-hop exploitable: VaultA → AttackerB → VaultA)
  - ThreeHopVulnerable.sol (3-hop: VaultX → RouterY → ReceiverZ → VaultX)
  - TwoHopGuarded.sol     (CEI-guarded, zero CP-121 findings expected)
  - DeepChain.sol         (5-hop depth-cap test fixture)

- Add 30 unit tests covering 2-hop, 3-hop, guarded, depth-limit, dedup,
  round-trip property, empty input, depth-cap clamping, findUnfinalizedVars,
  hasReentrancyGuard, buildCrossContractCallGraph, and file-based fixtures

- Update README: add CP-121 row to vulnerability rules table, fixture table,
  and Multi-hop Cross-Contract Reentrancy documentation section

All 369 core tests pass. Zero lint errors.
@Manuelshub

Copy link
Copy Markdown
Contributor Author

@Nanle-code Please review!!!

@Manuelshub
Manuelshub merged commit c867421 into StellarChainproof:master Aug 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-Contract Reentrancy Detection via Multi-Hop Call-Graph Traversal (CP-121)

1 participant