Skip to content

feat(wrapper): track pending (not-yet-compounded) rewards (#718) - #841

Open
ASIDISG wants to merge 1 commit into
BCPathway:mainfrom
ASIDISG:feature/issue-718-pending-rewards-storage
Open

feat(wrapper): track pending (not-yet-compounded) rewards (#718)#841
ASIDISG wants to merge 1 commit into
BCPathway:mainfrom
ASIDISG:feature/issue-718-pending-rewards-storage

Conversation

@ASIDISG

@ASIDISG ASIDISG commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #718.

Adds a PendingRewards storage slot to the wrapper (vault) contract, per the epic's storage-bucket scope: "Store pending_rewards. Sync variable after fee wrappers distribute yield."

  • DataKey::PendingRewards (contracts/wrapper/src/lib.rs): instance-storage i128, defaulting to 0 when never written, mirroring the existing Supply slot's read/write helper pattern.
  • Synced inside distribute_rewards(): after the reward transfer completes, pending_rewards is incremented by the distributed amount via checked_add. This is a running total tracking cumulative not-yet-compounded rewards, kept separate from total_assets/calculate_share_price (which already reflect the transfer immediately via the underlying token balance, unchanged by this PR) — it exists for whatever compounding step the epic adds in a later issue. Nothing in this PR consumes or resets it.
  • pending_rewards() getter: mirrors total_assets()/supply()'s existing style (plain i128, panics via ensure_initialized when uninitialized).
  • Overflow safety: the checked_add releases the reentrancy lock before returning InvalidAmount on overflow — matching the early-return-after-acquire_lock() pattern already used by wrap()/unwrap()/withdraw() in this file, so a guard firing after the lock is held can never leave it stuck.
  • TypeScript SDK: added a matching getPendingRewards() method to WrapperClient for parity with getTotalAssets()/calculateSharePrice().

Acceptance criteria (from #718)

  • pending_rewards stored and synced after distribute_rewards
  • Code compiles and follows the file's existing Soroban/Rust/TypeScript conventions
  • Unit tests cover happy paths and error states

Test plan

Added 7 Rust unit tests in contracts/wrapper/src/test.rs:

  • starts at 0 before any distribution
  • syncs to the distributed amount after one distribute_rewards call
  • accumulates correctly across multiple distributions
  • unaffected by wrap/unwrap/withdraw (only reward distributions move it)
  • not synced when distribute_rewards is rejected (invalid amount, or paused) — and confirms the reentrancy lock isn't left stuck by either rejected call, by successfully distributing again afterward
  • uninitialized contract fails
  • extended the existing test_uninitialized_access_panics to also cover pending_rewards()

Plus a TypeScript SDK surface test confirming getPendingRewards is exposed on WrapperClient.

Verified locally:

  • cargo check -p bc-forge-wrapper --tests — clean
  • cargo fmt -p bc-forge-wrapper -- --check — clean
  • cargo clippy -p bc-forge-wrapper --all-targets --all-features -- -D warnings — clean
  • npm test (sdk) — 23/23 passing
  • npx eslint on the two changed SDK files — no new issues (the same two pre-existing issues from the [Math] Pro-rata reward calculation #727 PR — a CRLF/prettier diff in untouched withdraw() and an unused keypair var in an untouched test — are still present and still predate this change)

Note: this dev machine's native Rust linker is broken for both the GNU and MSVC toolchains (confirmed unrelated to any of these changes — it also fails on the pre-existing, untouched bc-forge-rate-limit crate), so cargo test couldn't be run to completion locally; cargo check --tests type-checks the full test module against the real Soroban SDK, and CI runs on ubuntu-latest with a working linker.

@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@ASIDISG 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

@p3ris0n

p3ris0n commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

@ASIDISG please resolve conflicts

)

Adds a PendingRewards instance-storage slot to the wrapper contract,
synced in distribute_rewards() after the reward transfer: it
accumulates the cumulative amount distributed that has not yet been
compounded, as a running total separate from total_assets/share price.

The sync uses checked_add and releases the reentrancy lock before
returning on overflow, matching the early-return pattern used
elsewhere in this file for guards that fire after acquire_lock().

Adds a pending_rewards() getter (mirroring total_assets()/supply()'s
style) and a matching TypeScript SDK getPendingRewards() method.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ASIDISG
ASIDISG force-pushed the feature/issue-718-pending-rewards-storage branch from fca0b03 to 8a29244 Compare August 27, 2026 11:58
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.

[Storage] Define PendingRewards mapping

2 participants