Skip to content

feat(contracts): guard payment streams against reentrancy - #565

Open
numdinkushi wants to merge 1 commit into
Fundable-Protocol:mainfrom
numdinkushi:feature/514-reentrancy-guards
Open

feat(contracts): guard payment streams against reentrancy#565
numdinkushi wants to merge 1 commit into
Fundable-Protocol:mainfrom
numdinkushi:feature/514-reentrancy-guards

Conversation

@numdinkushi

@numdinkushi numdinkushi commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Closes #514.

Adds reentrancy protection across all state-mutating payment and NFT stream contract functions.

Changes

  • Added a reusable RAII-based reentrancy guard.
  • Added explicit ReentrantCall contract errors.
  • Protected stream creation, deposits, withdrawals, delegation, pause/resume, cancellation, claims, transfers, and administrative mutations.
  • Improved NFT stream storage TTL management.
  • Added malicious callback and lock lifecycle tests.
  • Added a reproducible Cargo lockfile.
  • Resolved contract workspace lint warnings.

Testing

  • 58 unit tests pass.
  • Strict Clippy checks pass with zero warnings.
  • Native release build passes.
  • WASM build was not run because the local Homebrew Rust installation does not include wasm32-unknown-unknown.

Summary by CodeRabbit

  • New Features

    • Added reentrancy protection to NFT and payment stream operations.
    • Added safeguards against reentrant token callbacks.
    • Added automatic storage lifetime extensions to help keep stream data available.
    • Added a shared contract utility for preventing nested calls.
  • Bug Fixes

    • Improved validation and storage handling across stream operations.
  • Tests

    • Added coverage and execution snapshots for reentrancy protection and lock lifecycle behavior.

Add shared state locks, TTL handling, and malicious callback coverage for payment stream mutations while pinning compatible contract dependencies.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@numdinkushi Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared Soroban reentrancy guard, integrates it into NFT and payment stream contracts, extends NFT stream storage TTLs, adds callback rejection tests, updates workspace wiring, and performs distributor compatibility cleanup.

Changes

Contract safety and workspace integration

Layer / File(s) Summary
Shared guard crate and workspace wiring
.gitignore, contracts/Cargo.toml, contracts/libs/common/*
Adds the contract-common workspace crate with a storage-backed ReentrancyGuard, automatic scope release, and tests with generated snapshots.
NFT stream protection and TTL updates
contracts/nft-stream/*
Adds reentrancy protection and TTL extension across NFT stream operations and read paths, with a token callback rejection test and snapshot.
Payment stream protection and withdrawal flow
contracts/payment-stream/*
Guards payment stream entrypoints, extracts shared withdrawal logic for guarded callers, and adds a reentrant token callback test with snapshot.
Distributor compatibility cleanup
contracts/distributor/src/lib.rs
Removes unused distribution code, updates Soroban symbol construction and test registration, and adjusts unused test bindings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StreamContract
  participant TokenContract
  participant ReentrancyGuard
  StreamContract->>ReentrancyGuard: acquire lock
  StreamContract->>TokenContract: invoke token transfer
  TokenContract->>StreamContract: invoke callback
  StreamContract->>ReentrancyGuard: acquire existing lock
  ReentrancyGuard-->>StreamContract: reject acquisition
  StreamContract-->>TokenContract: return ReentrantCall
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and matches the main change: adding reentrancy protection to contracts.
Linked Issues check ✅ Passed The PR adds reentrancy guards, error variants, tests, and TTL updates across the stream contracts described in the issue.
Out of Scope Changes check ✅ Passed The common-crate, workspace, .gitignore, and NFT-stream edits support the shared guard and TTL work rather than unrelated features.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
contracts/libs/common/src/lib.rs (1)

7-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add rustdoc for the public guard API.

Document ReentrancyGuard and ReentrancyGuard::acquire, including that the guard releases the instance lock when dropped.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/libs/common/src/lib.rs` around lines 7 - 12, Add rustdoc comments
for the public ReentrancyGuard struct and its acquire method. Document the
guard’s purpose, acquisition behavior, and that dropping the returned guard
releases the instance lock.
contracts/nft-stream/src/lib.rs (1)

150-197: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Move the escrow transfer after stream persistence (checks-effects-interactions).

token_client.transfer(...) at lines 164-165 runs before the stream counter, ownership record, and Stream are persisted (lines 167-197). The reentrancy guard already blocks any reentrant call into a guarded entrypoint during this window, so this isn't currently exploitable, but it diverges from payment-stream's safer pattern (its create_stream transfers tokens only after all state is written). Moving the transfer to the end hardens against any future path that bypasses the guard (e.g., a new unguarded mutator) and keeps both contracts consistent.

♻️ Suggested reordering
-        let token_client = token::Client::new(&env, &token);
-        token_client.transfer(&sender, &env.current_contract_address(), &total_amount);
-
         let stream_id: u64 = env
             .storage()
             .instance()
             .get(&DataKey::StreamCounter)
             .unwrap_or(0);
@@
         env.storage()
             .persistent()
             .set(&DataKey::Stream(new_stream_id), &stream);
         Self::extend_persistent_ttl(&env, &DataKey::Stream(new_stream_id));
         Self::extend_instance_ttl(&env);
+
+        let token_client = token::Client::new(&env, &token);
+        token_client.transfer(&sender, &env.current_contract_address(), &total_amount);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/nft-stream/src/lib.rs` around lines 150 - 197, Move the
token_client.transfer call in the stream-creation function to after the stream
counter, ownership record, Stream persistence, and TTL updates complete. Keep
all validation and state construction unchanged, and retain the transfer using
the existing token_client and amount.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@contracts/libs/common/src/lib.rs`:
- Around line 7-12: Add rustdoc comments for the public ReentrancyGuard struct
and its acquire method. Document the guard’s purpose, acquisition behavior, and
that dropping the returned guard releases the instance lock.

In `@contracts/nft-stream/src/lib.rs`:
- Around line 150-197: Move the token_client.transfer call in the
stream-creation function to after the stream counter, ownership record, Stream
persistence, and TTL updates complete. Keep all validation and state
construction unchanged, and retain the transfer using the existing token_client
and amount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dcd4627-9e2a-4cba-9ce5-b46ba9fb0e17

📥 Commits

Reviewing files that changed from the base of the PR and between 375c936 and a06bce2.

⛔ Files ignored due to path filters (1)
  • contracts/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .gitignore
  • contracts/Cargo.toml
  • contracts/distributor/src/lib.rs
  • contracts/libs/common/Cargo.toml
  • contracts/libs/common/src/lib.rs
  • contracts/libs/common/test_snapshots/tests/rejects_nested_acquisition.1.json
  • contracts/libs/common/test_snapshots/tests/releases_lock_when_scope_ends.1.json
  • contracts/nft-stream/Cargo.toml
  • contracts/nft-stream/src/lib.rs
  • contracts/nft-stream/test_snapshots/tests/reentrant_token_callback_is_rejected.1.json
  • contracts/payment-stream/Cargo.toml
  • contracts/payment-stream/src/lib.rs
  • contracts/payment-stream/src/test.rs
  • contracts/payment-stream/test_snapshots/test/test/test_reentrant_token_callback_is_rejected.1.json

@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

1 similar comment
@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

@coderabbitai coderabbitai Bot mentioned this pull request Aug 10, 2026
10 tasks
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.

[Contract] Add Reentrancy Protection Guards Across All Payment Stream Functions

2 participants