Skip to content

Formal reentrancy audit of all token::Client cross-contract calls #4

Description

@chonilius

Background / Context

Every payout path (escrow::release, escrow::refund, milestones::release_issue, milestones::cancel_milestone, maintenance_pool::withdraw) performs one or more token::Client::transfer calls to externally-supplied or previously-stored Addresses before or interleaved with the contract's own persistent-storage state mutation. For example, in escrow::release (contracts/escrow/src/lib.rs:97-133), token transfers to treasury and each recipient happen at lines 119-126, and escrow.status = EscrowStatus::Paid is only persisted at line 128-129 — after all transfers complete.

Problem Statement

Soroban's execution model differs from EVM's in ways that change (but do not necessarily eliminate) reentrancy risk: cross-contract calls are synchronous, and a malicious token or recipient contract address could, depending on Soroban's authorization/call-stack rules, attempt to call back into release/refund/withdraw mid-execution. Because the status flag (EscrowStatus::Paid, IssueStatus::Released) is written after transfers, a reentrant call during the transfer loop would still see EscrowStatus::Funded and could pass the match escrow.status guard again, potentially double-paying from the same escrow if a reentrant call path exists.
This needs a rigorous, Soroban-specific analysis (not an assumption imported from Ethereum reentrancy folklore) of whether this is actually exploitable given Soroban's host-enforced call semantics, and either a proof it's safe or a fix (checks-effects-interactions reordering: flip status to Paid/Released before performing transfers).

Requirements

  • Research and document Soroban's actual cross-contract call/reentrancy model: can a token::Client::transfer recipient (a custom SEP-41 token or a "recipient" that is itself a contract) execute arbitrary code that calls back into the calling contract during the transfer?
  • Build a proof-of-concept malicious contract (recipient or token) that attempts reentrant calls into release, refund, and withdraw and add it to the test suite to empirically validate or refute the theoretical risk.
  • Regardless of the empirical finding, refactor all four payout functions to follow strict checks-effects-interactions ordering (state mutation and persistence before external calls) as defense-in-depth, since this costs little and removes the class of bug entirely.
  • Add regression tests proving state is consistent even if a hypothetical reentrant call were possible.

Acceptance Criteria

  • Written analysis of Soroban's reentrancy model with citations/references to Soroban docs or host source
  • PoC malicious contract added under a test module, exercised against current code
  • release, refund, release_issue, cancel_milestone, withdraw all reordered to checks-effects-interactions
  • All existing and new tests pass
  • No change to the external function signatures/behavior for well-behaved tokens

Technical Notes / Hints

  • Files: contracts/escrow/src/lib.rs (release 97-133, refund 137-170), contracts/milestones/src/lib.rs (release_issue 127-177, cancel_milestone 181-208), contracts/maintenance-pool/src/lib.rs (withdraw 110-151).
  • Note compute_split is called and fully resolved before any transfer in release/release_issue already — the risk is specifically about status persistence timing, not the split math.

Difficulty Justification

Requires platform-specific expertise in Soroban's execution/authorization model (genuinely different from the EVM patterns most reentrancy guidance assumes), building working adversarial contracts in no_std Rust against soroban-sdk, and carefully refactoring five functions across three crates without altering their externally-observable success-case behavior.

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Stellar WaveIssues in the Stellar wave programsecuritySecurity-related issuetestingTesting/QA infrastructurevery hardVery difficult task, expert-level effort required

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions