Skip to content

feat(vesting): release_all and single-call vesting overview; pin WASM-hash and release event audit trails (#409, #410, #408, #407) - #425

Merged
ritaifeoluwa merged 4 commits into
SmartDropLabs:mainfrom
Dinma179:fix/issues-409-410-408-407
Sep 27, 2026
Merged

ritaifeoluwa merged 4 commits into
SmartDropLabs:mainfrom
Dinma179:fix/issues-409-410-408-407

Conversation

@Dinma179

Copy link
Copy Markdown
Contributor

Implements #409, #410, #408, #407 — one commit per issue.

Read this first: three of these four issues report problems that were already fixed in main before this wave started. git log -S confirms it:

Issue Reported Reality in main Commit date
#410 set_pool_wasm_hash doesn't emit the old hash lib.rs:1342 already publishes (old_hash, new_hash) 2026-07-19
#408 release doesn't emit an event lib.rs:268 already publishes vest/released 2026-06-28
#409 no way to query the full schedule in one call get_vesting_schedule already exists 2026-08-28

Rather than skip them, each commit adds the missing guarantee the issue was really asking for — a regression test that pins the behaviour so it cannot silently regress, plus (for #409) the part of the request that genuinely was not there.

Also note the issue bodies cite contracts/…; the workspace is at soroban/contracts/….

#410 — factory: WASM hash audit trail

The event already carries both hashes, but nothing tested it: a future edit to the payload could drop old_hash and CI would stay green, which is precisely the "audit trail incomplete" outcome the issue describes.

  • test_set_pool_wasm_hash_event_carries_old_and_new_hash — asserts the exact wasm_set event, so the pair (old_hash, new_hash) is pinned.
  • test_set_pool_wasm_hash_event_old_hash_matches_superseded_value — performs two consecutive changes and asserts the second event's old hash is the first change's new hash, i.e. the rollback chain is reconstructable from events alone.

#408 — vesting: release event payload

The event exists, but the existing tests only asserted !events.is_empty(), which passes even if every value in the payload is wrong. An indexer rebuilding vesting progress needs all three fields.

  • test_release_event_payload_identifies_beneficiary_and_amounts — pins topic ("vest","released") and the (beneficiary, amount_this_call, cumulative_total) payload.
  • test_release_event_with_cumulative_total — two releases, asserting the second event reports 250 and a running 750.
  • test_release_with_nothing_releasable_emits_no_event — a release with nothing vested must not emit, so an indexer can't record a release that never happened.

I deliberately did not add env.ledger().timestamp() as the issue's snippet suggests: this schedule is ledger-sequence based throughout (start_ledger, cliff_ledger, end_ledger, and compute_vested reads env.ledger().sequence()), so a wall-clock timestamp would be inconsistent with how the contract reasons about time. The cumulative released total is more useful to an indexer anyway.

#409 — vesting: single-call schedule and progress

get_vesting_schedule returns the configured parameters only. The issue's actual ask — start, end, cliff, total and released in one call — was still unmet: a frontend needed four round trips (get_vesting_schedule, vested_amount, released_amount, releasable), read at different ledgers.

  • Added VestingOverview (types.rs) and get_vesting_overview() (lib.rs), returning the full schedule plus revoked, vested_amount, released_amount and releasable_amount atomically.
  • A separate type, not extra fields on VestingSchedule. VestingSchedule is a #[contracttype] already decoded by clients; adding fields would change its XDR and break them. VestingOverview is additive and leaves the existing layout untouched.
  • releasable_amount uses saturating_sub: after emergency_withdraw zeroes the frozen vested amount while released is non-zero, a plain subtraction would hand callers a negative "releasable".
  • Kept the issue's suggested read_start/read_end/… helper style in spirit by reusing the module's existing get_* helpers, which is what the contract already uses.

Eight tests, including one asserting the combined call agrees with the three individual reads, and the revocation/emergency-withdraw saturation cases.

#407 — vesting: release_all

Implemented, but not with the issue's signature. The issue proposes release_all(env, beneficiary) calling release(env, beneficiary, total); this contract's release takes no beneficiary argument — it reads the beneficiary from storage and requires that account's authorization specifically so third parties cannot force a release at a time the beneficiary did not choose. Passing a beneficiary in would either be ignored or reopen exactly the hole release's doc comment warns about.

  • pub fn release_all(env: Env) -> Result<i128, VestingError> delegates to Self::release(env), so the arithmetic, the authorization and the vest/released event stay on the single existing path — no second code path to drift.
  • Note that release() already transfers the whole vested-but-unclaimed balance in one transfer, so this is a self-documenting entry point rather than new machinery; the code says so explicitly instead of pretending otherwise.

Nine tests: claims everything vested in one call, zero before the cliff, full amount after end, idempotent after a full claim, emits the same event as release, requires beneficiary auth, rejects an unauthorized caller, NotInitialized on an uninitialized wallet, and counts as a release operation.

Verification

Read-only verification per the task rules — no builds, installs or package-manager commands were run, so cargo test has not been executed. I compensated by verifying the arithmetic by hand against compute_vested (recomputing every expected vested/released value in the new tests with the same formula), and by matching the existing test idioms exactly: Events::all() tuple comparisons, try_* error matching, MockAuthInvoke for authorization, and the extern crate std shim the factory and farming-pool tests use. New tests are collected by the existing cargo test.

Closes #409
Closes #410
Closes #408
Closes #407

@netlify

netlify Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for sdcontracts ready!

Name Link
🔨 Latest commit 6c78357
🔍 Latest deploy log https://app.netlify.com/projects/sdcontracts/deploys/6ab9676a52316d0008c1ec41
😎 Deploy Preview https://deploy-preview-425--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.

@ritaifeoluwa
ritaifeoluwa merged commit 23bda8c into SmartDropLabs:main Sep 27, 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