Skip to content

feat: emergency pause, multi-pool router, loop-cap docs, proptest invariants#206

Open
obanai9 wants to merge 1 commit into
Dgetsylver:mainfrom
obanai9:main
Open

feat: emergency pause, multi-pool router, loop-cap docs, proptest invariants#206
obanai9 wants to merge 1 commit into
Dgetsylver:mainfrom
obanai9:main

Conversation

@obanai9
Copy link
Copy Markdown

@obanai9 obanai9 commented May 29, 2026

Summary

Implements four issues in a single PR, all changes confined to contracts/strategies/:

#45 – D4: Emergency pause admin role

  • storage.rs: added Admin and Paused keys to DataKey; new helpers set_admin, get_admin, set_paused, is_paused.
  • lib.rs: ninth constructor arg admin: Address; new public methods pause(), unpause(), paused(), get_admin().
  • pause() and unpause() are gated by the admin address set at init and emit an on-chain event (pause, state) → bool.
  • deposit() and harvest() return StrategyError::NotAuthorized when paused; withdraw() is unaffected so users can always exit.

#48 – D7: Multi-pool / multi-asset router contract

  • New crate contracts/strategies/router/ (Soroban cdylib).
  • PoolRouter stores a registry of strategy addresses with net-APY snapshots (bps) updated by the admin.
  • deposit(amount, from, preferred_strategy?) — queries the router's pre-deposit balance for accurate share pricing, picks the highest-APY strategy deterministically (or uses user override), issues proportional virtual shares.
  • withdraw(amount, from, to) — burns virtual shares, strategy sends equity directly to to.
  • balance(from) — returns user_vs / total_vs × strategy.balance(router).
  • Admin methods: add_strategy, remove_strategy, update_apy, set_admin.
  • README.md covers architecture, virtual-share accounting, routing algorithm, and migration path for existing single-pool depositors.

#49 – D8: Document (and name) the 20-loop hard cap

  • constants.rs: new MAX_LOOPS: u32 = 20 with a full rationale comment (Soroban instruction budget, diminishing returns, safety-ceiling vs. operator knob).
  • leverage.rs: loop_step_count uses MAX_LOOPS + 1 instead of bare 21; doc-comment references all three reasons.
  • README.md (new): module-level strategy documentation including a loop-progression table, full loop-cap rationale, init-arg reference, and key-invariants summary.

#50 – D9: Property / invariant tests for leverage math

  • Cargo.toml: added proptest = "1" to dev-dependencies.
  • New src/test_proptest.rs with ProptestConfig::with_cases(1_000) — seven invariants:
    1. total_supply >= total_borrow
    2. total_supply - total_borrow == initial (net equity)
    3. total_supply <= initial / (1 − c) (geometric-series bound)
    4. HF monotone in c_factor
    5. compute_step supply leg always equals balance
    6. compute_step final borrow is exactly 0
    7. No panic on extreme i128 inputs (saturating checked_mul)

Test plan

  • cargo test -p blend_leverage_strategy — existing unit tests pass; proptest runs 1 000 cases per property.
  • cargo check -p blend_leverage_router — router contract compiles without errors.
  • Verify BlendLeverageStrategy::pause() blocks deposit() and harvest(); withdraw() succeeds.
  • Verify PoolRouter::deposit routes to highest-APY strategy; user override selects preferred pool.
  • Confirm on-chain events are emitted for pause state changes and router deposits/withdrawals.

Closes #45
Closes #48
Closes #49
Closes #50

…iants

Implements four issues in one pass:

## Dgetsylver#45 – D4: Emergency pause admin role (blend_leverage)

- Added `Admin` and `Paused` keys to `DataKey` in `storage.rs`.
- Added `set_admin`, `get_admin`, `set_paused`, `is_paused` helpers in `storage.rs`.
- `__constructor` now accepts a ninth init arg (`admin: Address`) and persists it.
- New public methods on `BlendLeverageStrategy`:
  - `pause()` – admin-gated; blocks deposits and harvest, emits `(pause, state) → true`.
  - `unpause()` – admin-gated; resumes normal operation, emits `(pause, state) → false`.
  - `paused()` – read-only view returning current pause state.
  - `get_admin()` – read-only view returning the admin address.
- `deposit()` and `harvest()` return `StrategyError::NotAuthorized` immediately when paused;
  `withdraw()` is unaffected, preserving user access to funds at all times.

Closes Dgetsylver#45

## Dgetsylver#48 – D7: Multi-pool / multi-asset router contract

- New crate `contracts/strategies/router/` (cdylib, soroban-sdk 25.3.0).
- `PoolRouter` contract (`src/lib.rs`):
  - Maintains a registry of strategy addresses with net-APY snapshots (basis points).
  - `deposit(amount, from, preferred_strategy?)` – pulls asset from user, queries the
    router's existing balance in the chosen strategy for accurate share pricing, deposits
    via `StrategyClient`, and issues proportional virtual shares to the user.
  - Deterministic pool selection: iterates the registry and picks the highest
    `net_apy_bps`; ties broken by insertion order for stability.
  - User override: if `preferred_strategy` is `Some` and registered, it is used directly.
  - `withdraw(amount, from, to)` – burns virtual shares proportionally; strategy sends
    equity directly to `to` without routing through the router.
  - `balance(from)` – returns `user_vs / total_vs × strategy.balance(router)`.
  - Admin methods: `add_strategy`, `remove_strategy`, `update_apy`, `set_admin`.
  - View helpers: `best_strategy()`, `strategies()`, `admin()`, `asset()`.
  - Events: `(RouterDeposit, from)` and `(RouterWithdraw, from)`.
- `src/storage.rs`: `StrategyEntry`, `UserPosition`, per-strategy virtual-share tracking,
  per-user position persistence with 120-day TTL extension.
- `README.md`: architecture description, virtual-share accounting example, migration path
  for existing single-pool depositors (withdraw + re-deposit flow), routing algorithm docs.

Closes Dgetsylver#48

## Dgetsylver#49 – D8: Document (and name) the 20-loop hard cap (blend_leverage)

- Added `MAX_LOOPS: u32 = 20` constant to `constants.rs` with a full rationale comment
  covering (1) Soroban instruction budget, (2) diminishing marginal returns, and (3) the
  distinction between the safety ceiling and the operator-tunable `target_loops`.
- `leverage.rs`: `loop_step_count` now uses `MAX_LOOPS + 1` instead of the bare literal
  `21`, and carries an expanded doc-comment referencing all three reasons plus the README.
- `README.md` (new): module-level documentation for the blend_leverage strategy including
  a loop-progression table, the full loop-cap rationale, init-arg reference, and a summary
  of key invariants.

Closes Dgetsylver#49

## Dgetsylver#50 – D9: Property / invariant tests for leverage math (blend_leverage)

- Added `proptest = "1"` to `[dev-dependencies]` in `Cargo.toml`.
- New test module `src/test_proptest.rs` with `ProptestConfig::with_cases(1_000)`:
  1. `inv_total_supply_gte_total_borrow` – supply ≥ borrow for any (initial, c, n).
  2. `inv_net_equity_equals_initial` – supply − borrow = initial always.
  3. `inv_leverage_bounded_by_geometric_series` – supply ≤ initial / (1 − c).
  4. `inv_hf_monotone_in_c_factor` – higher c_factor → higher or equal HF for fixed
     b/d positions and rates.
  5. `inv_compute_step_supply_equals_balance` – supply leg always equals `balance`.
  6. `inv_compute_step_final_borrow_is_zero` – final step borrow is exactly 0.
  7. `inv_no_panic_on_extreme_inputs` – `compute_totals` does not panic on large i128
     values; saturating `checked_mul` keeps outputs non-negative.
- Registered the module in `lib.rs` under `#[cfg(test)]`.

Closes Dgetsylver#50
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 29, 2026

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

@hugo-heer
Copy link
Copy Markdown
Collaborator

Hey @obanai9 , thanks for your contribution ! Could you please make a PR per issue ?

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