Skip to content

feat(vault): sweep_idle_balance dry-run view#588

Open
Osuolale1 wants to merge 2 commits into
CalloraOrg:mainfrom
Osuolale1:feature/sweep-dryrun
Open

feat(vault): sweep_idle_balance dry-run view#588
Osuolale1 wants to merge 2 commits into
CalloraOrg:mainfrom
Osuolale1:feature/sweep-dryrun

Conversation

@Osuolale1

Copy link
Copy Markdown

CLOSE #537

Summary
The vault occasionally accumulates untracked on-ledger USDC surplus (direct transfers that bypass deposit, rounding remnants, manually credited recovery funds). The admin can recover it via distribute() with an admin-specified amount, but until now there was no read-only way to ask the contract what that amount currently is. Indexers and admin tooling had to query the token contract and read meta.balance separately and compute the difference off-chain.

This PR adds dry_run_sweep_idle_balance(), a read-only view that returns a structured SweepPreview describing exactly what a sweep would move.

Changes
contracts/vault/src/views.rs (new) — defines SweepPreview { on_ledger_balance, tracked_balance, idle_balance, has_idle } as a #[contracttype], and implements dry_run_sweep_idle_balance(env) -> Result<SweepPreview, VaultError> inside a #[contractimpl] impl CalloraVault block.

contracts/vault/src/lib.rs — adds mod views; and re-exports SweepPreview.

contracts/vault/tests/sweep_dryrun.rs (new) — 7 integration tests:

  • dry_run_before_init_returns_not_initialized
  • dry_run_no_surplus_reports_zero_idle
  • dry_run_with_surplus_reports_correct_amount
  • dry_run_reports_cumulative_surplus_across_transfers
  • dry_run_saturates_when_tracked_exceeds_on_ledger
  • dry_run_does_not_mutate_observable_state
  • dry_run_requires_no_auth

README.md — added the view to the vault API listing.

Note on semantics
The function reports max(on_ledger - tracked, 0). It does NOT pick a destination address — there is no canonical sweep recipient in the current contract, and distribute() takes the destination as a parameter. Callers of this view supply the destination themselves when they subsequently call distribute(_, _, preview.idle_balance).

Note on test-environment caveat
The saturation test (tracked > on_ledger) is constructed by transferring USDC out of the vault under mock_all_auths, simulating the pathological case where on-ledger drops below tracked. Clawback was the more natural primitive but requires asset-level clawback flags; transfer is sufficient to exercise the defensive branch.

Acceptance criteria
dry_run_sweep_idle_balance() emits no events, requires no auth, mutates no storage
SweepPreview payload accurately reports the four documented fields under both surplus and non-surplus states
Behaviour saturates safely under the tracked > on_ledger pathological case
All 7 tests pass: cargo test -p callora-vault --test sweep_dryrun
README.md updated; NatSpec-style /// rustdoc on the view, the struct, and every field
No breaking ABI change (additive: new view, new exported type)

Test plan
cargo test -p callora-vault --test sweep_dryrun → 7 passed
CI green (note: settlement crate currently has its own pre-existing merge breakage from prior -X theirs merges; if CI is red for that reason it is not a regression from this PR)

Stacked alongside #<set-admin-PR-#> and #<upgrade-events-PR-#>
This PR's diff temporarily includes the lib.rs cleanup commit from those predecessors (cherry-picked from #<set-admin-PR-#>). Once either predecessor merges into main, this PR's diff narrows automatically to just the four new sweep-dryrun files.

Recent "-X theirs" auto-resolutions in PRs CalloraOrg#578/CalloraOrg#579 left contracts/vault/src/lib.rs
in a non-compiling state with 107 errors. This commit reconstructs the intended state:

- remove duplicate get_max_deduct (was defined at L376 and L1305; kept the
  documented copy at L376)
- remove panic-version broadcast (kept the Result-returning version that
  matches the surrounding contract style and reuses VaultError::MetadataTooLong)
- replace inline VaultError enum with the canonical one in errors.rs via
  `mod errors; pub use errors::VaultError;` (the inline copy was missing the
  contracterror macro import and was diverging from errors.rs)
- declare `mod validators;` so validators.rs is wired into the crate
- fix two StorageKey::Meta typos (variant is MetaKey)
- add the missing `&ut` (USDC token) argument to two
  SettlementClient::receive_payment calls (the settlement trait takes 5 args,
  the call sites were passing 4)
- change `max_fee_bps: u16` -> `u32` in `deduct` (Soroban contract ABI does
  not support u16; updated the u16::MAX sentinel to u32::MAX accordingly)

Verified with `cargo check -p callora-vault --lib`.
Add a read-only view that reports how much untracked on-ledger USDC a
hypothetical sweep would move, without mutating any state or moving any
funds. Useful for indexers, dashboards, and admins inspecting recovery
candidates before signing a `distribute` transaction.

`dry_run_sweep_idle_balance() -> Result<SweepPreview, VaultError>`
returns a structured payload containing:

- on_ledger_balance: actual USDC held by the vault contract
- tracked_balance:   the vault's internal accounting (meta.balance)
- idle_balance:      max(on_ledger - tracked, 0) -- saturates defensively
- has_idle:          true iff idle_balance > 0

No auth. No TTL bump. Single cross-contract balance() read.

Tests cover: pre-init error path, zero-surplus path, positive-surplus
path, cumulative surplus across multiple direct transfers, defensive
saturation when tracked > on_ledger, observable-state preservation, and
no-auth invocation.
@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

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

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.

Add vault sweep_idle_balance dry-run view

1 participant