Skip to content

Add beneficiary / whitelist_count getters, total_tvl aggregate, and old-value multiplier event (#247–#250) - #273

Merged
ritaifeoluwa merged 11 commits into
SmartDropLabs:mainfrom
bade22brazy:feat/getters-and-tvl-aggregate-247-250
Aug 31, 2026
Merged

Add beneficiary / whitelist_count getters, total_tvl aggregate, and old-value multiplier event (#247–#250)#273
ritaifeoluwa merged 11 commits into
SmartDropLabs:mainfrom
bade22brazy:feat/getters-and-tvl-aggregate-247-250

Conversation

@bade22brazy

@bade22brazy bade22brazy commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Implements the four getter/event issues below across the vesting-wallet, farming-pool, and factory contracts. 10 commits, one logical unit each (feature + its tests paired).

#247 — public beneficiary getter (vesting-wallet)

get_beneficiary was internal only. Adds beneficiary() -> Result<Address, VestingError> (guarded by require_initialized, bumps instance TTL) plus admin-style aliasing conventions already used elsewhere in the crate are not needed here.

#248whitelist_count getter (farming-pool)

Adds whitelist_count() / get_whitelist_count() returning the number of currently whitelisted addresses. Design note: rather than a parallel DataKey::WhitelistCount counter incremented/decremented on every path (the issue's sketch), the value is read from the canonical WhitelistedUsers instance list that add_to_whitelist, remove_from_whitelist, and both batch variants already maintain and de-duplicate. That list is the single source of truth for get_whitelisted_users().total, so the count can never drift out of step with it. Vec::len() on an instance entry is O(1) from the caller's perspective.

#249total_tvl aggregate (factory) — hard

A factory receives no callback when users stake/unstake in a deployed pool, and a live sum over every pool would need an unbounded cross-contract fan-out that does not fit Soroban's per-invocation footprint limit. So this uses the incremental-accumulator approach the issue suggests:

  • DataKey::TotalTvl — aggregate, instance storage; DataKey::PoolTvl(id) — per-pool last-synced snapshot, persistent.
  • create_pool seeds a new pool's snapshot at 0 (a fresh pool holds nothing).
  • sync_pool_tvl(id) / sync_all_pool_tvls(start_id, limit)permissionless maintenance calls that read a pool's live TVL (one cross-contract call to total_staked, which already includes locked balances) and fold the delta versus the stored snapshot into TotalTvl. sync_all_pool_tvls walks a bounded window, mirroring refresh_pool_ttls. Emits a tvl_sync = (pool_id, old_snapshot, new_tvl) event on change.
  • total_tvl() -> i128 — O(1) read of the accumulator. Reflects the last sync; dashboards that need a fresh figure call sync_all_pool_tvls first.
  • pool_tvl(id) — live per-pool read-through; pool_tvl_synced(id) — the currently-folded snapshot.
  • New FactoryError::PoolQueryFailed for a pool that doesn't answer total_staked; FactoryError is now re-exported from the crate root.

#250set_global_multiplier event includes old value (farming-pool)

Captures old_multiplier before the write and publishes (old_multiplier, multiplier) on the boost/mult_set topic.

Tests

Unit tests added for #247/#248/#250; integration tests for #249 in factory/tests/factory_pool_integration.rs (drives factory-deployed pools through stake/lock/unstake). New integration tests will generate fresh files under factory/test_snapshots/ on first run — not included here.

Not built or run in this environment. Highest-risk review spots: the try_invoke_contract::<i128, soroban_sdk::Error> decode in query_pool_tvl (mirrors upgrade_pool's existing admin cross-contract call), and the assumption that FarmingPool::total_staked is the correct single TVL term (verified: lock_assets credits both TotalStaked and TotalLocked, so total_locked is a subset).

Closes #247, Closes #248, Closes #249, Closes #250

`get_beneficiary` was an internal helper only. Expose a `beneficiary()`
entry point (guarded by `require_initialized`) so frontends can read the
recipient address without unpacking `get_vesting_schedule` or probing a
failing call.

Refs SmartDropLabs#247
Asserts the getter returns the configured address, follows
`transfer_beneficiary`, and returns `NotInitialized` before init.

Refs SmartDropLabs#247
Adds `whitelist_count()` / `get_whitelist_count()` returning the number of
addresses currently whitelisted. The value is derived from the canonical
`WhitelistedUsers` list that every add/remove/batch path already maintains
and dedupes, so it cannot drift the way a parallel counter could.

Refs SmartDropLabs#248
Checks the count across single adds/removes (including duplicate adds and
no-op removes), agrees with `get_whitelisted_users().total`, and returns
`NotInitialized` before init.

Refs SmartDropLabs#248
…ier event

The `mult_set` event only carried the new value. Capture the old multiplier
before the write and publish `(old_multiplier, multiplier)` so off-chain
indexers have both terms for audit trails and rollback scenarios.

Refs SmartDropLabs#250
…d new

Verifies the emitted tuple is `(old, new)` and that a subsequent change
reports the just-superseded value as the old one.

Refs SmartDropLabs#250
Introduces `DataKey::TotalTvl` (aggregate) and `DataKey::PoolTvl(id)`
(per-pool snapshot), plus `total_tvl()` and `pool_tvl_synced(id)` read-only
getters and a `PoolQueryFailed` error code. `total_tvl` is an O(1) read of
an incrementally-maintained accumulator rather than an unbounded
cross-contract fan-out. Also re-exports `FactoryError` from the crate root.

Refs SmartDropLabs#249
… create_pool

Adds `query_pool_tvl` (one cross-contract call to the pool's `total_staked`,
which already includes locked balances) and the `pool_tvl(id)` live
read-through getter. `create_pool` now records a zero TVL baseline for the
new pool so the first sync is a clean delta.

Refs SmartDropLabs#249
…alls

Permissionless calls that re-read a pool's live TVL and fold the change into
the `total_tvl` accumulator (`total_tvl += live - previous_snapshot`),
emitting a `tvl_sync` event on change. `sync_all_pool_tvls` walks a bounded
window of pool IDs, mirroring `refresh_pool_ttls`.

Refs SmartDropLabs#249
Drives factory-deployed pools through stake/lock/unstake and asserts
`total_tvl`, `pool_tvl`, `pool_tvl_synced`, `sync_pool_tvl`, and
`sync_all_pool_tvls` behave, including the unknown-pool error path.

Refs SmartDropLabs#249
@netlify

netlify Bot commented Aug 30, 2026

Copy link
Copy Markdown

Deploy Preview for sdcontracts ready!

Name Link
🔨 Latest commit ff40ea4
🔍 Latest deploy log https://app.netlify.com/projects/sdcontracts/deploys/6a950f8e14754e0007425b89
😎 Deploy Preview https://deploy-preview-273--sdcontracts.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

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

@ritaifeoluwa
ritaifeoluwa merged commit 7a45040 into SmartDropLabs:main Aug 31, 2026
4 checks 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

2 participants