feat: implement CP-121 multi-hop cross-contract reentrancy detector - #112
Merged
Manuelshub merged 1 commit intoAug 31, 2026
Conversation
- 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.
Contributor
Author
|
@Nanle-code Please review!!! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
But they miss the cross-contract variant, where the re-entry path spans multiple contracts:
And they miss deeper chains:
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.tsThe detector works in three stages:
1. Cross-contract call graph construction
Builds a
CrossContractCallGraphfrom allMergedContractViewobjects available at scan time. Edges are added for:RouterY public router; router.forward(...))IVault(addr).withdraw()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:
A.f → B.g → A.h— entire path through typed edgesA.f → B.g → (low-level) → A.f— typed path reaches a node that makes a low-level call back to originDefault traversal depth: 3 hops. Hard cap: 10 hops. Exceeding the hard cap emits a
CP-121-DEPTH-CAPinfo finding.3. Unfinalized state analysis
For each candidate origin function, collects state variable accesses in source order and checks:
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:
nonReentrant(case-insensitive)require(!locked)/locked = truemutex patternChanges
New files
packages/core/src/rules/cp121-cross-contract-reentrancy.tspackages/core/src/rules/__tests__/cp121-cross-contract-reentrancy.test.tsexamples/contracts/cross-contract-reentrancy/TwoHopVulnerable.solexamples/contracts/cross-contract-reentrancy/ThreeHopVulnerable.solexamples/contracts/cross-contract-reentrancy/TwoHopGuarded.solexamples/contracts/cross-contract-reentrancy/DeepChain.solModified files
packages/core/src/scanner.tsdetectCrossContractReentrancyonce per scan session over all collectedMergedContractViewobjects; attributes findings to each originating contract's source fileREADME.md