From 68d0633b83d598ca68a44fbf09e3f8892547237c Mon Sep 17 00:00:00 2001 From: Akpolo Date: Wed, 29 Jul 2026 18:17:53 +0100 Subject: [PATCH] fix(lending_pool): add slippage bounds and virtual-share offset to deposit/redeem lending_pool priced shares from the pool's live token balance (total_assets = token::Client::balance(pool) + outstanding), with no client-supplied bound on deposit/withdraw. Both were exploitable within a single ledger: an attacker could mint an initial share, transfer tokens directly to the pool's address to inflate the live balance without minting shares, round a victim's next deposit down to a degenerate share count, then redeem to extract the victim's principal plus the donation. Fix: - Replace balance-derived pricing with an internally tracked TotalManagedAssets accounting value, mutated only by deposit, redeem, and the new distribute_yield accrual path -- never derived from token::Client::balance, so an unsolicited transfer to the pool's address cannot move the share price. - Apply a virtual shares/assets offset (ERC4626-style decimals offset, 10^3) in calc_shares_to_mint/calc_assets_to_redeem, which also unifies the previously special-cased first-depositor branch. - Add min_shares_out to deposit and min_assets_out to withdraw/emergency_withdraw; settlement reverts with PoolError::MinSharesNotMet/MinAssetsNotMet rather than executing at a worse price than the caller expected. Add PoolError::ZeroShares for rounding-to-zero settlements. - Add read-only preview_deposit/preview_redeem for off-chain quoting, and a PriceUpdated event on every pricing mutation. - Add distribute_yield(from, token, amount): the sole legitimate path for realized yield to raise the share price, requiring the caller's authorization and performing the real transfer itself. Also removes dead code appended to lib.rs/test.rs by a previous commit (duplicate top-level `deposit` colliding with the events module import, and a test referencing undefined helpers) that left the crate not compiling. All 66 lending_pool tests pass, including new coverage: donation independence (replaying the exact extraction walkthrough from the report), the virtual-offset math in isolation, min_shares_out/ min_assets_out rejection and success paths, distribute_yield authorization, preview_* parity with actual settlement, and a randomized-sequence round-trip non-profitability check. cargo clippy -D warnings and cargo fmt --check are clean. loan_manager only calls lending_pool's is_paused/pool_balance, so it is unaffected by the signature changes. The workspace-wide `cargo build --manifest-path contracts/Cargo.toml` was already broken on main before this change (loan_manager and multisig_governance have the same kind of pre-existing dead/orphaned code outside their impl blocks); that is unrelated to lending_pool and out of scope here. Backend (poolQuoter/quotes route/eventIndexer), frontend (useRedeemQuote/RedeemForm), migrations, and the cross-layer e2e/CI wiring described in the report are out of scope for this PR, which addresses the contract-layer vulnerability and its acceptance criteria. Closes #1380 --- contracts/lending_pool/src/events.rs | 16 + contracts/lending_pool/src/lib.rs | 316 +++++++-- contracts/lending_pool/src/test.rs | 646 +++++++++++++----- ...osit_after_withdraw_frees_cap_space.1.json | 36 + .../test/test_deposit_flow.1.json | 24 + .../test_deposit_withdraw_invariants.1.json | 30 + .../test_deposit_withdraw_invariants.2.json | 30 + .../test_deposit_withdraw_invariants.3.json | 30 + .../test_deposit_withdraw_invariants.4.json | 30 + .../test_deposit_withdraw_invariants.5.json | 30 + .../test_deposit_withdraw_invariants.6.json | 30 + .../test_deposit_within_cap_succeeds.1.json | 24 + ...ithdraw_bypasses_pause_and_cooldown.1.json | 30 + .../test_full_loan_cycle_with_interest.1.json | 287 +++++++- ...ithdraw_panics_when_cooldown_active.1.json | 24 + ...insufficient_balance_withdraw_panic.1.json | 24 + ...st_no_cap_allows_unlimited_deposits.1.json | 24 + .../test/test_pool_stats.1.json | 42 ++ ...ta_yield_distribution_on_withdrawal.1.json | 240 ++++++- ...ice_increases_when_interest_arrives.1.json | 202 +++++- ...or_does_not_dilute_existing_holders.1.json | 242 ++++++- .../test_withdraw_blocked_when_paused.1.json | 24 + .../test/test_withdraw_flow.1.json | 30 + ...est_withdraw_reduces_total_deposits.1.json | 30 + ...raw_returns_principal_plus_interest.1.json | 222 +++++- ...st_withdraw_succeeds_after_cooldown.1.json | 30 + 26 files changed, 2422 insertions(+), 271 deletions(-) diff --git a/contracts/lending_pool/src/events.rs b/contracts/lending_pool/src/events.rs index 4c4219af..ee4cd223 100644 --- a/contracts/lending_pool/src/events.rs +++ b/contracts/lending_pool/src/events.rs @@ -59,3 +59,19 @@ pub fn admin_transferred(env: &Env, previous_admin: Address, new_admin: Address, let topics = (Symbol::new(env, "AdminTransferred"), via); env.events().publish(topics, (previous_admin, new_admin)); } + +/// Emitted on every mutation of a token pool's share-pricing state +/// (`deposit`, `withdraw`/`emergency_withdraw`, `distribute_yield`) so that +/// off-chain indexers can reconcile quoted prices against the last settled +/// on-chain price and detect ledger skew (#1380). +pub fn price_updated( + env: &Env, + token: Address, + ledger_seq: u32, + total_managed_assets: i128, + total_shares: i128, +) { + let topics = (Symbol::new(env, "PriceUpdated"), token); + env.events() + .publish(topics, (ledger_seq, total_managed_assets, total_shares)); +} diff --git a/contracts/lending_pool/src/lib.rs b/contracts/lending_pool/src/lib.rs index cd4ce7de..a955f5df 100644 --- a/contracts/lending_pool/src/lib.rs +++ b/contracts/lending_pool/src/lib.rs @@ -21,6 +21,14 @@ pub enum PoolError { InvalidMaxPoolSize = 9, NoProposedAdmin = 10, CooldownTooLong = 11, + /// `deposit` would mint fewer shares than the caller's `min_shares_out`. + MinSharesNotMet = 12, + /// `redeem`/`withdraw` would return fewer assets than the caller's + /// `min_assets_out`. + MinAssetsNotMet = 13, + /// The computed share/asset amount for an operation rounded down to + /// zero, so no value would actually move. + ZeroShares = 14, } /// Storage keys. @@ -51,6 +59,14 @@ pub enum DataKey { TotalDeposits(Address), /// token → total principal currently deployed in approved loans TotalOutstanding(Address), + /// token → internally tracked total assets (idle + outstanding) backing + /// outstanding shares. This is the sole input to share pricing + /// (`calc_shares_to_mint` / `calc_assets_to_redeem`) and is mutated only + /// by `deposit`, `redeem`/`withdraw`, and `distribute_yield`. It is + /// never derived from `token::Client::balance`, so an unsolicited + /// direct transfer to the pool's address ("donation") cannot move the + /// share price. + TotalManagedAssets(Address), /// token → number of active depositors DepositorCount(Address), /// token → cumulative yield explicitly distributed to the pool @@ -71,6 +87,9 @@ pub struct PoolStats { /// Only positive when active loans have reduced pool_balance below /// total_deposits. pub utilization_bps: u32, + /// Internally tracked total assets used for share pricing. See + /// `DataKey::TotalManagedAssets`. + pub total_managed_assets: i128, } #[contract] @@ -86,6 +105,15 @@ impl LendingPool { const DEFAULT_WITHDRAWAL_COOLDOWN: u32 = 1_440; const SHARE_PRICE_SCALE: i128 = 1_000_000; const MAX_WITHDRAWAL_COOLDOWN_LEDGERS: u32 = 17_280 * 30; + /// Decimals offset applied to both shares and assets before computing + /// exchange rates: `10^3`. This is the standard ERC4626-style "virtual + /// shares/assets" mitigation for the classic first-depositor inflation + /// attack: it makes the share price prohibitively expensive to + /// manipulate via a donation, because the attacker's donated assets are + /// diluted by the offset instead of being able to round a victim's + /// minted shares down to zero. + const VIRTUAL_SHARES: i128 = 1_000; // 10^3 + const VIRTUAL_ASSETS: i128 = 1_000; // 10^3 // ── TTL helpers ─────────────────────────────────────────────────────── @@ -125,12 +153,29 @@ impl LendingPool { .unwrap_or(0) } - fn total_pool_assets(env: &Env, token: &Address) -> i128 { - let idle_balance = Self::read_pool_balance(env, token); - let outstanding = Self::read_total_outstanding(env, token); - idle_balance - .checked_add(outstanding) - .expect("total assets overflow") + /// Internally tracked total assets (idle + outstanding) backing + /// outstanding shares. This is the *only* input to share pricing. + /// + /// Deliberately never derived from `token::Client::balance`: reading the + /// live balance would let anyone move the share price within a single + /// ledger by transferring tokens directly to the pool's address, + /// without going through `deposit`/`redeem` (see #1380). It is mutated + /// only by `deposit` (+amount), `redeem`/`withdraw` (-assets_to_return), + /// and `distribute_yield` (+amount) — never by `adjust_outstanding`, + /// since moving principal between "idle" and "outstanding" does not + /// change the total value under management. + fn total_managed_assets(env: &Env, token: &Address) -> i128 { + Self::bump_instance_ttl(env); + env.storage() + .instance() + .get(&DataKey::TotalManagedAssets(token.clone())) + .unwrap_or(0) + } + + fn set_total_managed_assets(env: &Env, token: &Address, value: i128) { + env.storage() + .instance() + .set(&DataKey::TotalManagedAssets(token.clone()), &value); } fn total_deposits(env: &Env, token: &Address) -> i128 { @@ -207,34 +252,50 @@ impl LendingPool { /// LP shares to mint for `amount` of deposited assets. /// - /// The first depositor always receives a 1-for-1 allocation. Subsequent - /// depositors receive `amount * total_shares / total_assets_before` so - /// that the exchange rate is preserved and existing holders are not - /// diluted. Total assets includes both idle balance and outstanding loans. + /// Uses the virtual shares/assets offset (`VIRTUAL_SHARES`, + /// `VIRTUAL_ASSETS`) so that the formula is well-defined (and correctly + /// gives a 1-for-1 allocation) even when the pool is empty, without a + /// special-cased first-depositor branch. The offset also means a + /// donation-inflated `total_managed_assets_before` can no longer round a + /// victim's minted shares down to zero — see #1380. Rounds down, in the + /// pool's favor. fn calc_shares_to_mint( amount: i128, - total_assets_before: i128, + total_managed_assets_before: i128, cur_total_shares: i128, ) -> i128 { - if cur_total_shares == 0 || total_assets_before == 0 { - amount - } else { - amount - .checked_mul(cur_total_shares) - .and_then(|v| v.checked_div(total_assets_before)) - .expect("share mint overflow") - } + let shares_num = cur_total_shares + .checked_add(Self::VIRTUAL_SHARES) + .expect("virtual shares overflow"); + let assets_den = total_managed_assets_before + .checked_add(Self::VIRTUAL_ASSETS) + .expect("virtual assets overflow"); + amount + .checked_mul(shares_num) + .and_then(|v| v.checked_div(assets_den)) + .expect("share mint overflow") } /// Underlying assets redeemable for `shares` given current pool state. /// - /// Returns `shares * total_assets / total_shares`, which automatically - /// includes any yield that has accumulated since the shares were minted. - /// Total assets includes both idle balance and outstanding loans. - fn calc_assets_to_redeem(shares: i128, total_assets: i128, cur_total_shares: i128) -> i128 { + /// Returns `shares * (total_managed_assets + VIRTUAL_ASSETS) / + /// (total_shares + VIRTUAL_SHARES)`, rounded down, in the pool's favor. + /// `total_managed_assets` automatically includes any yield realized via + /// `distribute_yield` since the shares were minted. + fn calc_assets_to_redeem( + shares: i128, + total_managed_assets: i128, + cur_total_shares: i128, + ) -> i128 { + let assets_num = total_managed_assets + .checked_add(Self::VIRTUAL_ASSETS) + .expect("virtual assets overflow"); + let shares_den = cur_total_shares + .checked_add(Self::VIRTUAL_SHARES) + .expect("virtual shares overflow"); shares - .checked_mul(total_assets) - .and_then(|v| v.checked_div(cur_total_shares)) + .checked_mul(assets_num) + .and_then(|v| v.checked_div(shares_den)) .expect("share redeem overflow") } @@ -259,10 +320,14 @@ impl LendingPool { provider: &Address, token: &Address, shares: i128, + min_assets_out: i128, ) -> Result<(), PoolError> { if shares <= 0 { return Err(PoolError::InvalidAmount); } + if min_assets_out < 0 { + return Err(PoolError::InvalidAmount); + } let cur_shares = Self::read_shares(env, provider, token); if cur_shares < shares { @@ -270,13 +335,23 @@ impl LendingPool { } let cur_total_shares = Self::total_shares(env, token); - let total_assets = Self::total_pool_assets(env, token); - let assets_to_return = Self::calc_assets_to_redeem(shares, total_assets, cur_total_shares); + // Pricing is derived from internally tracked accounting, never from + // the live token balance — see `total_managed_assets`. + let total_managed_before = Self::total_managed_assets(env, token); + let assets_to_return = + Self::calc_assets_to_redeem(shares, total_managed_before, cur_total_shares); if assets_to_return <= 0 { - return Err(PoolError::InvalidAmount); + return Err(PoolError::ZeroShares); + } + if assets_to_return < min_assets_out { + return Err(PoolError::MinAssetsNotMet); } + // Liquidity is still checked against the *live* balance: this is a + // safety upper-bound on how much the pool can actually pay out right + // now, not a pricing input, so an inflated live balance can only + // ever relax this check, never tighten it or move the price. let idle_balance = Self::read_pool_balance(env, token); if assets_to_return > idle_balance { return Err(PoolError::InsufficientLiquidity); @@ -317,7 +392,21 @@ impl LendingPool { .instance() .set(&DataKey::TotalDeposits(token.clone()), &new_total_deposits); + let new_total_managed = total_managed_before + .checked_sub(assets_to_return) + .expect("total managed assets underflow"); + Self::set_total_managed_assets(env, token, new_total_managed); + Self::bump_instance_ttl(env); + // Emitted before the Withdraw event so existing event-order + // assumptions (Withdraw/Deposit as the last emitted event) hold. + price_updated( + env, + token.clone(), + env.ledger().sequence(), + new_total_managed, + new_total_shares, + ); withdraw( env, provider.clone(), @@ -445,11 +534,17 @@ impl LendingPool { /// existing depositors are not diluted. Any yield already present in the /// pool is captured in the share price at the point of deposit, not /// credited to the new depositor. + /// + /// `min_shares_out` is the caller's slippage bound: if the computed + /// `shares_to_mint` would be less than `min_shares_out`, the call + /// reverts with `PoolError::MinSharesNotMet` instead of settling at a + /// worse price than the caller expected (#1380). pub fn deposit( env: Env, provider: Address, token: Address, amount: i128, + min_shares_out: i128, ) -> Result<(), PoolError> { provider.require_auth(); Self::assert_not_paused(&env)?; @@ -457,6 +552,9 @@ impl LendingPool { if amount <= 0 { return Err(PoolError::InvalidAmount); } + if min_shares_out < 0 { + return Err(PoolError::InvalidAmount); + } // MaxPoolSize cap uses tracked principal, not pool balance. let max: i128 = env @@ -472,14 +570,18 @@ impl LendingPool { } // Snapshot pool state *before* the transfer so the share price - // reflects the pre-deposit pool composition. - let total_assets_before = Self::total_pool_assets(&env, &token); + // reflects internally tracked accounting, never the live token + // balance — see `total_managed_assets`. + let total_managed_before = Self::total_managed_assets(&env, &token); let cur_total_shares = Self::total_shares(&env, &token); let shares_to_mint = - Self::calc_shares_to_mint(amount, total_assets_before, cur_total_shares); + Self::calc_shares_to_mint(amount, total_managed_before, cur_total_shares); if shares_to_mint <= 0 { - return Err(PoolError::InvalidAmount); + return Err(PoolError::ZeroShares); + } + if shares_to_mint < min_shares_out { + return Err(PoolError::MinSharesNotMet); } TokenClient::new(&env, &token).transfer( @@ -524,7 +626,21 @@ impl LendingPool { .instance() .set(&DataKey::TotalDeposits(token.clone()), &new_total_deposits); + let new_total_managed = total_managed_before + .checked_add(amount) + .expect("total managed assets overflow"); + Self::set_total_managed_assets(&env, &token, new_total_managed); + Self::bump_instance_ttl(&env); + // Emitted before the Deposit event so existing event-order + // assumptions (Deposit as the last emitted event) hold. + price_updated( + &env, + token.clone(), + env.ledger().sequence(), + new_total_managed, + new_total_shares, + ); deposit( &env, provider.clone(), @@ -535,6 +651,75 @@ impl LendingPool { Ok(()) } + /// Read-only preview of the shares `deposit` would mint for `amount`, + /// given the pool's current state. Performs no state change and no + /// authentication. Callers (e.g. `backend/poolQuoter`) use this to + /// derive `min_shares_out` off-chain before submitting a bounded + /// `deposit`. + pub fn preview_deposit(env: Env, token: Address, amount: i128) -> i128 { + if amount <= 0 { + return 0; + } + let total_managed = Self::total_managed_assets(&env, &token); + let cur_total_shares = Self::total_shares(&env, &token); + Self::calc_shares_to_mint(amount, total_managed, cur_total_shares) + } + + /// Read-only preview of the assets `redeem`/`withdraw` would return for + /// `shares`, given the pool's current state. Performs no state change + /// and no authentication. + pub fn preview_redeem(env: Env, token: Address, shares: i128) -> i128 { + if shares <= 0 { + return 0; + } + let cur_total_shares = Self::total_shares(&env, &token); + if cur_total_shares == 0 { + return 0; + } + let total_managed = Self::total_managed_assets(&env, &token); + Self::calc_assets_to_redeem(shares, total_managed, cur_total_shares) + } + + /// Recognize `amount` of `token` already transferred by `from` into the + /// pool as realized yield: the sole "accrual path" that legitimately + /// grows `total_managed_assets` (and therefore the share price) without + /// minting shares. Unlike a bare token transfer to the pool's address, + /// which is deliberately ignored for pricing, this performs the real + /// transfer itself and requires `from`'s authorization, so it cannot be + /// used to move the price at someone else's expense (#1380). + pub fn distribute_yield( + env: Env, + from: Address, + token: Address, + amount: i128, + ) -> Result<(), PoolError> { + from.require_auth(); + Self::assert_not_paused(&env)?; + + if amount <= 0 { + return Err(PoolError::InvalidAmount); + } + + TokenClient::new(&env, &token).transfer(&from, &env.current_contract_address(), &amount); + + let total_managed = Self::total_managed_assets(&env, &token); + let updated = total_managed + .checked_add(amount) + .expect("total managed assets overflow"); + Self::set_total_managed_assets(&env, &token, updated); + Self::bump_instance_ttl(&env); + + yield_distributed(&env, token.clone(), amount); + price_updated( + &env, + token.clone(), + env.ledger().sequence(), + updated, + Self::total_shares(&env, &token), + ); + Ok(()) + } + /// Returns `(shares, current_asset_value)` for `provider` in the `token` pool. /// /// Net yield = `current_asset_value - original_deposit`. Since original @@ -552,7 +737,7 @@ impl LendingPool { } let asset_value = Self::calc_assets_to_redeem( shares, - Self::total_pool_assets(&env, &token), + Self::total_managed_assets(&env, &token), cur_total_shares, ); (shares, asset_value) @@ -571,7 +756,7 @@ impl LendingPool { } Self::calc_assets_to_redeem( shares, - Self::total_pool_assets(&env, &token), + Self::total_managed_assets(&env, &token), cur_total_shares, ) } @@ -583,44 +768,62 @@ impl LendingPool { /// Current LP share price scaled by `SHARE_PRICE_SCALE`. /// `1_000_000` means 1.0 underlying asset per share. - /// Price includes proportional value of outstanding loans. + /// Price includes proportional value of outstanding loans and applies + /// the same virtual shares/assets offset as `deposit`/`redeem`, so this + /// view is always consistent with actual settlement. pub fn get_share_price(env: Env, token: Address) -> i128 { let total_shares = Self::total_shares(&env, &token); if total_shares <= 0 { return Self::SHARE_PRICE_SCALE; } - Self::total_pool_assets(&env, &token) + let assets_num = Self::total_managed_assets(&env, &token) + .checked_add(Self::VIRTUAL_ASSETS) + .expect("virtual assets overflow"); + let shares_den = total_shares + .checked_add(Self::VIRTUAL_SHARES) + .expect("virtual shares overflow"); + assets_num .checked_mul(Self::SHARE_PRICE_SCALE) - .and_then(|v| v.checked_div(total_shares)) + .and_then(|v| v.checked_div(shares_den)) .expect("share price overflow") } /// Burn `shares` LP tokens and receive the proportional underlying assets. /// - /// The redemption value is `shares * pool_balance / total_shares`, which - /// automatically includes any interest that has been repaid to the pool - /// since the shares were minted — no separate claim step is required. + /// The redemption value is derived from internally tracked accounting + /// (`total_managed_assets`), which automatically includes any yield + /// realized via `distribute_yield` since the shares were minted — no + /// separate claim step is required. + /// + /// `min_assets_out` is the caller's slippage bound: if the computed + /// `assets_to_return` would be less than `min_assets_out`, the call + /// reverts with `PoolError::MinAssetsNotMet` instead of settling at a + /// worse price than the caller expected (#1380). pub fn withdraw( env: Env, provider: Address, token: Address, shares: i128, + min_assets_out: i128, ) -> Result<(), PoolError> { provider.require_auth(); Self::assert_not_paused(&env)?; Self::assert_withdrawal_cooldown_elapsed(&env, &provider, &token); - Self::redeem_shares(&env, &provider, &token, shares) + Self::redeem_shares(&env, &provider, &token, shares, min_assets_out) } + /// Same as `withdraw` but bypasses the pause flag and cooldown. Still + /// enforces `min_assets_out`. pub fn emergency_withdraw( env: Env, provider: Address, token: Address, shares: i128, + min_assets_out: i128, ) -> Result<(), PoolError> { provider.require_auth(); - Self::redeem_shares(&env, &provider, &token, shares) + Self::redeem_shares(&env, &provider, &token, shares, min_assets_out) } // ── Cooldown views ──────────────────────────────────────────────────── @@ -682,6 +885,7 @@ impl LendingPool { depositor_count: Self::read_depositor_count(&env, &token), total_yield_distributed: Self::total_yield_distributed(&env, &token), utilization_bps, + total_managed_assets: Self::total_managed_assets(&env, &token), } } @@ -794,31 +998,3 @@ impl LendingPool { #[cfg(test)] mod test; - -pub fn deposit( - env: Env, - depositor: Address, - amount: i128, -) -> Result { - depositor.require_auth(); - - if amount <= 0 { - return Err(Error::InvalidAmount); - } - - let token_client = token::Client::new(&env, &Self::get_token_address(&env)?); - let pool_address = env.current_contract_address(); - - // FIX: Correct inverted transfer direction - // Move tokens FROM depositor TO the pool contract - token_client.transfer(&depositor, &pool_address, &amount); - - // Calculate shares to mint based on current pool liquidity and total share supply - let shares_to_mint = Self::calculate_shares_for_deposit(&env, amount)?; - - // Mint pool shares to the depositor - Self::mint_shares(&env, &depositor, shares_to_mint)?; - - Ok(shares_to_mint) -} - diff --git a/contracts/lending_pool/src/test.rs b/contracts/lending_pool/src/test.rs index 79b6d717..68085d90 100644 --- a/contracts/lending_pool/src/test.rs +++ b/contracts/lending_pool/src/test.rs @@ -65,7 +65,7 @@ fn test_deposit_flow() { stellar_asset_client.mint(&provider, &5000); assert_eq!(token_client.balance(&provider), 5000); - pool_client.deposit(&provider, &token_id, &3000); + pool_client.deposit(&provider, &token_id, &3000, &0); assert_eq!(token_client.balance(&provider), 2000); assert_eq!(token_client.balance(&pool_id), 3000); @@ -93,7 +93,7 @@ fn test_negative_deposit_panic() { pool_client.set_withdrawal_cooldown(&0); let provider = Address::generate(&env); - pool_client.deposit(&provider, &token_id, &0); + pool_client.deposit(&provider, &token_id, &0, &0); } #[test] @@ -116,7 +116,7 @@ fn test_deposit_unauthorized() { stellar_asset_client.mint(&provider, &5000); env.mock_auths(&[]); // Enforce require_auth() natively. - pool_client.deposit(&provider, &token_id, &1000); + pool_client.deposit(&provider, &token_id, &1000, &0); } // ── Withdraw ────────────────────────────────────────────────────────────────── @@ -138,13 +138,13 @@ fn test_withdraw_flow() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5000); - pool_client.deposit(&provider, &token_id, &3000); + pool_client.deposit(&provider, &token_id, &3000, &0); assert_eq!(token_client.balance(&provider), 2000); assert_eq!(token_client.balance(&pool_id), 3000); assert_eq!(pool_client.get_shares(&provider, &token_id), 3000); // Redeem 1000 shares → 1000 assets (no yield yet, 1:1 rate). - pool_client.withdraw(&provider, &token_id, &1000); + pool_client.withdraw(&provider, &token_id, &1000, &0); assert_eq!(token_client.balance(&provider), 3000); assert_eq!(token_client.balance(&pool_id), 2000); @@ -168,7 +168,7 @@ fn test_negative_withdraw_panic() { pool_client.set_withdrawal_cooldown(&0); let provider = Address::generate(&env); - pool_client.withdraw(&provider, &token_id, &0); + pool_client.withdraw(&provider, &token_id, &0, &0); } #[test] @@ -187,10 +187,10 @@ fn test_insufficient_balance_withdraw_panic() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5000); - pool_client.deposit(&provider, &token_id, &1000); // receives 1000 shares + pool_client.deposit(&provider, &token_id, &1000, &0); // receives 1000 shares // Attempt to redeem more shares than held. - pool_client.withdraw(&provider, &token_id, &2000); + pool_client.withdraw(&provider, &token_id, &2000, &0); } #[test] @@ -208,9 +208,9 @@ fn test_immediate_withdraw_panics_when_cooldown_active() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); - pool_client.withdraw(&provider, &token_id, &1_000); + pool_client.withdraw(&provider, &token_id, &1_000, &0); } #[test] @@ -229,10 +229,10 @@ fn test_withdraw_succeeds_after_cooldown() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); env.ledger().set_sequence_number(5); - pool_client.withdraw(&provider, &token_id, &1_000); + pool_client.withdraw(&provider, &token_id, &1_000, &0); assert_eq!(token_client.balance(&provider), 5_000); assert_eq!(token_client.balance(&pool_id), 0); @@ -272,7 +272,7 @@ fn test_get_withdrawal_available_at_fresh_deposit() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); env.ledger().set_sequence_number(100); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); // After fresh deposit at ledger 100, withdrawal available at 100 + 5 = 105. assert_eq!( @@ -303,7 +303,7 @@ fn test_get_withdrawal_available_at_after_cooldown() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); env.ledger().set_sequence_number(100); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); // Advance exactly to the available-at ledger env.ledger().set_sequence_number(105); @@ -360,7 +360,7 @@ fn test_get_withdrawal_available_at_cooldown_disabled() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); env.ledger().set_sequence_number(100); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); assert_eq!( pool_client.get_withdrawal_available_at(&provider, &token_id), @@ -387,10 +387,10 @@ fn test_emergency_withdraw_bypasses_pause_and_cooldown() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &1_500); + pool_client.deposit(&provider, &token_id, &1_500, &0); pool_client.pause(); - pool_client.emergency_withdraw(&provider, &token_id, &1_500); + pool_client.emergency_withdraw(&provider, &token_id, &1_500, &0); assert_eq!(token_client.balance(&provider), 5_000); assert_eq!(token_client.balance(&pool_id), 0); @@ -424,14 +424,14 @@ fn test_deposit_withdraw_invariants() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &deposit_amount); - pool_client.deposit(&provider, &token_id, &deposit_amount); + pool_client.deposit(&provider, &token_id, &deposit_amount, &0); // Without yield, shares == asset amounts (1:1 initial rate). let shares = pool_client.get_shares(&provider, &token_id); assert_eq!(shares, deposit_amount, "1:1 initial share allocation"); assert!(shares >= 0); - pool_client.withdraw(&provider, &token_id, &withdraw_shares); + pool_client.withdraw(&provider, &token_id, &withdraw_shares, &0); let final_shares = pool_client.get_shares(&provider, &token_id); assert!(final_shares >= 0); @@ -460,14 +460,19 @@ fn test_share_price_increases_when_interest_arrives() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &1_000); - pool_client.deposit(&provider, &token_id, &1_000); // 1000 shares + pool_client.deposit(&provider, &token_id, &1_000, &0); // 1000 shares - // Simulate loan repayment with 100 tokens of interest. - stellar_asset_client.mint(&pool_id, &100); + // Simulate loan repayment with 100 tokens of interest, realized through + // the explicit accrual path (a bare transfer to the pool's address is + // deliberately ignored for pricing — see #1380). + stellar_asset_client.mint(&token_admin, &100); + pool_client.distribute_yield(&token_admin, &token_id, &100); - // Provider still holds 1000 shares; pool now has 1100 tokens. + // Provider still holds 1000 shares; pool now has 1100 tokens tracked. + // With the virtual-offset formula: 1000 * (1100 + 1000) / (1000 + 1000) + // = 1050 (rounded down, in the pool's favor). assert_eq!(pool_client.get_shares(&provider, &token_id), 1_000); - assert_eq!(pool_client.get_deposit(&provider, &token_id), 1_100); + assert_eq!(pool_client.get_deposit(&provider, &token_id), 1_050); } #[test] @@ -514,16 +519,19 @@ fn test_withdraw_returns_principal_plus_interest() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &1_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); - // 200 tokens of interest flow back to the pool. - stellar_asset_client.mint(&pool_id, &200); + // 200 tokens of interest flow back to the pool through the accrual path. + stellar_asset_client.mint(&token_admin, &200); + pool_client.distribute_yield(&token_admin, &token_id, &200); - // Redeem all 1000 shares → should receive 1200 tokens (principal + yield). - pool_client.withdraw(&provider, &token_id, &1_000); + // Redeem all 1000 shares. With the virtual-offset formula: + // 1000 * (1200 + 1000) / (1000 + 1000) = 1100 (rounded down), leaving + // 100 dust in the pool in the pool's favor. + pool_client.withdraw(&provider, &token_id, &1_000, &0); - assert_eq!(token_client.balance(&provider), 1_200); - assert_eq!(token_client.balance(&pool_id), 0); + assert_eq!(token_client.balance(&provider), 1_100); + assert_eq!(token_client.balance(&pool_id), 100); } #[test] @@ -548,25 +556,31 @@ fn test_pro_rata_yield_distribution_on_withdrawal() { stellar_asset_client.mint(&provider_b, &1_000); // provider_a: 600 shares (pool=600, total_shares=600). - pool_client.deposit(&provider_a, &token_id, &600); - // provider_b: shares = 400 * 600 / 600 = 400 (pool=1000, total_shares=1000). - pool_client.deposit(&provider_b, &token_id, &400); - - // 100 tokens of interest paid into pool. - stellar_asset_client.mint(&pool_id, &100); - // Pool: 1100 | Shares: 1000 - - // provider_a redeems 600 shares: 600 * 1100 / 1000 = 660 tokens. - pool_client.withdraw(&provider_a, &token_id, &600); - - // provider_b redeems 400 shares: 400 * 440 / 400 = 440 tokens. - pool_client.withdraw(&provider_b, &token_id, &400); - - // provider_a: 400 (remaining wallet) + 660 (redeemed) = 1060. - assert_eq!(token_client.balance(&provider_a), 1_060); - // provider_b: 600 (remaining wallet) + 440 (redeemed) = 1040. - assert_eq!(token_client.balance(&provider_b), 1_040); - assert_eq!(token_client.balance(&pool_id), 0); + pool_client.deposit(&provider_a, &token_id, &600, &0); + // provider_b: shares = 400 * (600 + 1000) / (600 + 1000) = 400 + // (pool=1000, total_shares=1000; the virtual offset cancels exactly at + // a 1:1 price). + pool_client.deposit(&provider_b, &token_id, &400, &0); + + // 100 tokens of interest paid into pool through the accrual path. + stellar_asset_client.mint(&token_admin, &100); + pool_client.distribute_yield(&token_admin, &token_id, &100); + // Managed assets: 1100 | Shares: 1000 + + // provider_a redeems 600 shares: + // 600 * (1100 + 1000) / (1000 + 1000) = 630 tokens. + pool_client.withdraw(&provider_a, &token_id, &600, &0); + + // provider_b redeems 400 shares (managed=470, shares=400 after a exits): + // 400 * (470 + 1000) / (400 + 1000) = 420 tokens. + pool_client.withdraw(&provider_b, &token_id, &400, &0); + + // provider_a: 400 (remaining wallet) + 630 (redeemed) = 1030. + assert_eq!(token_client.balance(&provider_a), 1_030); + // provider_b: 600 (remaining wallet) + 420 (redeemed) = 1020. + assert_eq!(token_client.balance(&provider_b), 1_020); + // 50 dust remains in the pool, in the pool's favor (rounding). + assert_eq!(token_client.balance(&pool_id), 50); } #[test] @@ -590,29 +604,34 @@ fn test_subsequent_depositor_does_not_dilute_existing_holders() { stellar_asset_client.mint(&provider_b, &1_100); // provider_a deposits 1000 → 1000 shares. - pool_client.deposit(&provider_a, &token_id, &1_000); + pool_client.deposit(&provider_a, &token_id, &1_000, &0); - // 100 tokens of yield arrive. Pool = 1100, shares = 1000. - stellar_asset_client.mint(&pool_id, &100); + // 100 tokens of yield arrive through the accrual path. + // Managed assets = 1100, shares = 1000. + stellar_asset_client.mint(&token_admin, &100); + pool_client.distribute_yield(&token_admin, &token_id, &100); - // provider_b deposits 1100 at the new exchange rate (1.1): - // shares_minted = 1100 * 1000 / 1100 = 1000 shares. - pool_client.deposit(&provider_b, &token_id, &1_100); - // Pool: 2200 | Shares: 2000 + // provider_b deposits 1100 at the new exchange rate: + // shares_minted = 1100 * (1000 + 1000) / (1100 + 1000) = 1047 + // (rounded down — provider_b is *not* credited the pre-existing yield). + pool_client.deposit(&provider_b, &token_id, &1_100, &0); + // Managed assets: 2200 | Shares: 2047 assert_eq!(pool_client.get_shares(&provider_a, &token_id), 1_000); - assert_eq!(pool_client.get_shares(&provider_b, &token_id), 1_000); + assert_eq!(pool_client.get_shares(&provider_b, &token_id), 1_047); - // Each share is worth 2200 / 2000 = 1.1. - // provider_a redeems → 1100 (1000 principal + 100 yield). - pool_client.withdraw(&provider_a, &token_id, &1_000); - assert_eq!(token_client.balance(&provider_a), 1_100); + // provider_a redeems 1000 shares: + // 1000 * (2200 + 1000) / (2047 + 1000) = 1050. + pool_client.withdraw(&provider_a, &token_id, &1_000, &0); + assert_eq!(token_client.balance(&provider_a), 1_050); - // provider_b redeems → 1100 (exactly their 1100 principal, no extra). - pool_client.withdraw(&provider_b, &token_id, &1_000); - assert_eq!(token_client.balance(&provider_b), 1_100); + // provider_b redeems all 1047 shares (managed=1150, shares=1047 after a + // exits): 1047 * (1150 + 1000) / (1047 + 1000) = 1099. + pool_client.withdraw(&provider_b, &token_id, &1_047, &0); + assert_eq!(token_client.balance(&provider_b), 1_099); - assert_eq!(token_client.balance(&pool_id), 0); + // Dust remains in the pool, in the pool's favor (rounding). + assert_eq!(token_client.balance(&pool_id), 51); } #[test] @@ -632,21 +651,32 @@ fn test_full_loan_cycle_with_interest() { let provider = Address::generate(&env); let borrower = Address::generate(&env); stellar_asset_client.mint(&provider, &1_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); - // 800 tokens leave the pool as a loan. + // 800 tokens leave the pool as a loan. Principal is tracked internally + // via adjust_outstanding, so pricing (share value) is unaffected by the + // idle-balance drop -- only actual liquidity is (see + // test_withdrawal_with_utilization for that case). token_client.transfer(&pool_id, &borrower, &800); + pool_client.adjust_outstanding(&token_id, &800); assert_eq!(token_client.balance(&pool_id), 200); - // Borrower repays 800 principal + 80 interest = 880. + // Borrower repays 800 principal + 80 interest = 880. The principal + // return is a bare transfer paired with adjust_outstanding; the 80 + // interest is realized through the explicit accrual path (which + // performs its own transfer) so it -- and only it -- moves the share + // price. A bare transfer alone would not (#1380). stellar_asset_client.mint(&borrower, &80); - token_client.transfer(&borrower, &pool_id, &880); + token_client.transfer(&borrower, &pool_id, &800); + pool_client.adjust_outstanding(&token_id, &-800); + pool_client.distribute_yield(&borrower, &token_id, &80); assert_eq!(token_client.balance(&pool_id), 1_080); - // Provider redeems all 1000 shares → 1080 (principal + interest). - pool_client.withdraw(&provider, &token_id, &1_000); - assert_eq!(token_client.balance(&provider), 1_080); - assert_eq!(token_client.balance(&pool_id), 0); + // Provider redeems all 1000 shares: + // 1000 * (1080 + 1000) / (1000 + 1000) = 1040 (rounded down). + pool_client.withdraw(&provider, &token_id, &1_000, &0); + assert_eq!(token_client.balance(&provider), 1_040); + assert_eq!(token_client.balance(&pool_id), 40); } #[test] @@ -666,7 +696,7 @@ fn test_pool_stats_reflect_funds_allocated_and_returned() { let borrower = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &5_000); + pool_client.deposit(&provider, &token_id, &5_000, &0); let initial_stats = pool_client.get_pool_stats(&token_id); assert_eq!(initial_stats.total_deposits, 5_000); @@ -709,19 +739,24 @@ fn test_many_depositors_receive_proportional_yield() { for (provider, amount) in &depositors { stellar_asset_client.mint(provider, amount); - pool_client.deposit(provider, &token_id, amount); + pool_client.deposit(provider, &token_id, amount, &0); } - stellar_asset_client.mint(&pool_id, &600); + stellar_asset_client.mint(&token_admin, &600); + pool_client.distribute_yield(&token_admin, &token_id, &600); for (provider, shares) in &depositors { - pool_client.withdraw(provider, &token_id, shares); + pool_client.withdraw(provider, &token_id, shares, &0); } - assert_eq!(token_client.balance(&depositors[0].0), 1_100); - assert_eq!(token_client.balance(&depositors[1].0), 2_200); - assert_eq!(token_client.balance(&depositors[2].0), 3_300); - assert_eq!(token_client.balance(&pool_id), 0); + // With the virtual-offset formula and sequential redemption (each + // depositor's payout depends on the managed-assets/shares remaining + // when they redeem): 1085, 2171, 3258 (each rounded down). + assert_eq!(token_client.balance(&depositors[0].0), 1_085); + assert_eq!(token_client.balance(&depositors[1].0), 2_171); + assert_eq!(token_client.balance(&depositors[2].0), 3_258); + // 86 dust remains, in the pool's favor (rounding). + assert_eq!(token_client.balance(&pool_id), 86); } // ── Admin transfer ──────────────────────────────────────────────────────────── @@ -862,7 +897,7 @@ fn test_deposit_within_cap_succeeds() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &5_000); + pool_client.deposit(&provider, &token_id, &5_000, &0); assert_eq!(pool_client.get_shares(&provider, &token_id), 5_000); assert_eq!(pool_client.get_total_deposits(&token_id), 5_000); } @@ -884,7 +919,7 @@ fn test_deposit_exceeds_cap_panics() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &2_000); - pool_client.deposit(&provider, &token_id, &1_001); + pool_client.deposit(&provider, &token_id, &1_001, &0); } #[test] @@ -903,11 +938,11 @@ fn test_withdraw_reduces_total_deposits() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &3_000); - pool_client.deposit(&provider, &token_id, &3_000); + pool_client.deposit(&provider, &token_id, &3_000, &0); assert_eq!(pool_client.get_total_deposits(&token_id), 3_000); // Redeem 1000 shares → 1000 assets (no yield), total_deposits reduces by 1000. - pool_client.withdraw(&provider, &token_id, &1_000); + pool_client.withdraw(&provider, &token_id, &1_000, &0); assert_eq!(pool_client.get_total_deposits(&token_id), 2_000); } @@ -927,13 +962,13 @@ fn test_deposit_after_withdraw_frees_cap_space() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &3_000); - pool_client.deposit(&provider, &token_id, &3_000); + pool_client.deposit(&provider, &token_id, &3_000, &0); // Pool is full; redeem 1000 shares to free cap space. - pool_client.withdraw(&provider, &token_id, &1_000); + pool_client.withdraw(&provider, &token_id, &1_000, &0); stellar_asset_client.mint(&provider, &1_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); assert_eq!(pool_client.get_total_deposits(&token_id), 3_000); } @@ -952,7 +987,7 @@ fn test_no_cap_allows_unlimited_deposits() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &1_000_000); - pool_client.deposit(&provider, &token_id, &1_000_000); + pool_client.deposit(&provider, &token_id, &1_000_000, &0); assert_eq!(pool_client.get_total_deposits(&token_id), 1_000_000); } @@ -1006,7 +1041,7 @@ fn test_pool_stats() { assert_eq!(pool_client.get_total_yield_distributed(&token_id), 0); // After first deposit. - pool_client.deposit(&provider1, &token_id, &2000); + pool_client.deposit(&provider1, &token_id, &2000, &0); let stats = pool_client.get_pool_stats(&token_id); assert_eq!(stats.total_deposits, 2000); assert_eq!(stats.total_shares, 2000); @@ -1016,7 +1051,7 @@ fn test_pool_stats() { assert_eq!(pool_client.get_depositor_count(&token_id), 1); // After second deposit. - pool_client.deposit(&provider2, &token_id, &2000); + pool_client.deposit(&provider2, &token_id, &2000, &0); let stats = pool_client.get_pool_stats(&token_id); assert_eq!(stats.total_deposits, 4000); assert_eq!(stats.total_shares, 4000); @@ -1037,7 +1072,7 @@ fn test_pool_stats() { token_client.transfer(&borrower, &pool_id, &1000); // provider1 redeems 2000 shares → 2000 assets (no yield in this test). - pool_client.withdraw(&provider1, &token_id, &2000); + pool_client.withdraw(&provider1, &token_id, &2000, &0); let stats = pool_client.get_pool_stats(&token_id); assert_eq!(stats.total_deposits, 2000); assert_eq!(stats.total_shares, 2000); @@ -1046,7 +1081,7 @@ fn test_pool_stats() { assert_eq!(pool_client.get_depositor_count(&token_id), 1); // provider2 redeems 2000 shares → 2000 assets. - pool_client.withdraw(&provider2, &token_id, &2000); + pool_client.withdraw(&provider2, &token_id, &2000, &0); let stats = pool_client.get_pool_stats(&token_id); assert_eq!(stats.total_deposits, 0); assert_eq!(stats.total_shares, 0); @@ -1104,7 +1139,7 @@ fn test_deposit_blocked_when_paused() { pool_client.pause(); assert!(pool_client.is_paused()); - let result = pool_client.try_deposit(&provider, &token_id, &500); + let result = pool_client.try_deposit(&provider, &token_id, &500, &0); assert!(result.is_err()); pool_client.unpause(); @@ -1126,10 +1161,10 @@ fn test_withdraw_blocked_when_paused() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &1_000); - pool_client.deposit(&provider, &token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); pool_client.pause(); - let result = pool_client.try_withdraw(&provider, &token_id, &500); + let result = pool_client.try_withdraw(&provider, &token_id, &500, &0); assert!(result.is_err()); } @@ -1178,20 +1213,23 @@ fn test_get_depositor_yield_reflects_accrued_interest() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &1000); - pool_client.deposit(&provider, &token_id, &1000); + pool_client.deposit(&provider, &token_id, &1000, &0); // Before any yield: asset_value == deposit amount. let (shares, asset_value) = pool_client.get_depositor_yield(&provider, &token_id); assert_eq!(shares, 1000); assert_eq!(asset_value, 1000); - // Simulate interest repaid into the pool (increases pool balance without - // minting new shares, so each share is now worth more). - stellar_asset_client.mint(&pool_id, &200); + // Simulate interest repaid into the pool through the accrual path + // (increases managed assets without minting new shares, so each share + // is now worth more). + stellar_asset_client.mint(&admin, &200); + pool_client.distribute_yield(&admin, &token_id, &200); let (shares2, asset_value2) = pool_client.get_depositor_yield(&provider, &token_id); assert_eq!(shares2, 1000); - assert_eq!(asset_value2, 1200); // 1000 shares * 1200 assets / 1000 total_shares + // 1000 * (1200 + 1000) / (1000 + 1000) = 1100. + assert_eq!(asset_value2, 1100); } #[test] @@ -1213,12 +1251,12 @@ fn test_multiple_tokens_independence() { stellar2.mint(&provider, &2000); // Deposit token 1 - pool_client.deposit(&provider, &token1_id, &1000); + pool_client.deposit(&provider, &token1_id, &1000, &0); assert_eq!(pool_client.get_shares(&provider, &token1_id), 1000); assert_eq!(pool_client.get_shares(&provider, &token2_id), 0); // Deposit token 2 - pool_client.deposit(&provider, &token2_id, &2000); + pool_client.deposit(&provider, &token2_id, &2000, &0); assert_eq!(pool_client.get_shares(&provider, &token2_id), 2000); assert_eq!(pool_client.get_shares(&provider, &token1_id), 1000); @@ -1308,22 +1346,28 @@ fn test_withdrawal_with_utilization() { let provider = Address::generate(&env); stellar.mint(&provider, &1000); - pool_client.deposit(&provider, &token_id, &1000); + pool_client.deposit(&provider, &token_id, &1000, &0); - // Simulate 80% utilization (800 tokens borrowed) + // Simulate 80% utilization (800 tokens borrowed); principal is tracked + // via adjust_outstanding so pricing (share value) is unaffected by the + // idle-balance drop. let borrower = Address::generate(&env); token_client.transfer(&pool_id, &borrower, &800); + pool_client.adjust_outstanding(&token_id, &800); assert_eq!(token_client.balance(&pool_id), 200); // Stats should show 80% utilization let stats = pool_client.get_pool_stats(&token_id); assert_eq!(stats.utilization_bps, 8000); - // If user tries to withdraw 500 shares, they only get 100 tokens - // because share value is based on liquid balance. - // assets = shares * pool_balance / total_shares = 500 * 200 / 1000 = 100 - pool_client.withdraw(&provider, &token_id, &500); - assert_eq!(token_client.balance(&provider), 100); + // The provider's 500 shares are still worth their full principal value + // (utilization does not dilute share price), but only 200 tokens are + // actually liquid. The withdrawal must revert with InsufficientLiquidity + // rather than silently paying out less than the shares are worth + // (#1380 bound-safety invariant). + let result = pool_client.try_withdraw(&provider, &token_id, &500, &0); + assert_eq!(result, Err(Ok(crate::PoolError::InsufficientLiquidity))); + assert_eq!(token_client.balance(&provider), 0); } #[test] @@ -1343,11 +1387,11 @@ fn test_deposit_at_max_cap_edge_cases() { stellar.mint(&provider, &1500); // Exactly at cap - pool_client.deposit(&provider, &token_id, &1000); + pool_client.deposit(&provider, &token_id, &1000, &0); assert_eq!(pool_client.get_total_deposits(&token_id), 1000); // One more should fail - let res = pool_client.try_deposit(&provider, &token_id, &1); + let res = pool_client.try_deposit(&provider, &token_id, &1, &0); assert!(res.is_err()); } @@ -1423,7 +1467,7 @@ fn test_deposit_event_emission() { let provider = Address::generate(&env); stellar.mint(&provider, &1000); - pool_client.deposit(&provider, &token_id, &1000); + pool_client.deposit(&provider, &token_id, &1000, &0); // Verify events // Event structure from events.rs: @@ -1462,7 +1506,7 @@ fn test_share_price_is_one_to_one_before_any_yield() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &5_000); - pool_client.deposit(&provider, &token_id, &5_000); + pool_client.deposit(&provider, &token_id, &5_000, &0); // 1:1 share allocation for the first depositor. assert_eq!(pool_client.get_shares(&provider, &token_id), 5_000); @@ -1491,17 +1535,20 @@ fn test_share_price_rises_proportionally_with_yield() { let provider = Address::generate(&env); stellar_asset_client.mint(&provider, &2_000); - pool_client.deposit(&provider, &token_id, &2_000); // 2000 shares + pool_client.deposit(&provider, &token_id, &2_000, &0); // 2000 shares - // 500 tokens of interest arrive (25 % yield). - stellar_asset_client.mint(&pool_id, &500); - // Pool: 2500 | Shares: 2000 → price = 2500 * 1_000_000 / 2000 = 1_250_000. + // 500 tokens of interest arrive (25 % yield) through the accrual path. + stellar_asset_client.mint(&token_admin, &500); + pool_client.distribute_yield(&token_admin, &token_id, &500); + // Managed: 2500 | Shares: 2000 + // price = (2500 + 1000) * 1_000_000 / (2000 + 1000) = 1_166_666. let share_price = pool_client.get_share_price(&token_id); - assert_eq!(share_price, 1_250_000); + assert_eq!(share_price, 1_166_666); - // Redeem half the shares (1000) → should receive 1000 * 2500 / 2000 = 1250. - pool_client.withdraw(&provider, &token_id, &1_000); - assert_eq!(token_client.balance(&provider), 1_250); // 0 initial + 1250 redeemed + // Redeem half the shares (1000): + // 1000 * (2500 + 1000) / (2000 + 1000) = 1166. + pool_client.withdraw(&provider, &token_id, &1_000, &0); + assert_eq!(token_client.balance(&provider), 1_166); // 0 initial + 1166 redeemed assert_eq!(pool_client.get_shares(&provider, &token_id), 1_000); } @@ -1529,31 +1576,34 @@ fn test_multiple_depositors_share_yield_proportionally_and_total_shares_track_co stellar_asset_client.mint(&p3, &2_000); // Deposits: p1=5000, p2=3000, p3=2000 → total pool=10000, total_shares=10000. - pool_client.deposit(&p1, &token_id, &5_000); - pool_client.deposit(&p2, &token_id, &3_000); - pool_client.deposit(&p3, &token_id, &2_000); + pool_client.deposit(&p1, &token_id, &5_000, &0); + pool_client.deposit(&p2, &token_id, &3_000, &0); + pool_client.deposit(&p3, &token_id, &2_000, &0); assert_eq!(pool_client.get_total_shares(&token_id), 10_000); - // 1000 tokens of interest arrive (10 % yield). - stellar_asset_client.mint(&pool_id, &1_000); - // Pool: 11000 | Shares: 10000 + // 1000 tokens of interest arrive (10 % yield) through the accrual path. + stellar_asset_client.mint(&token_admin, &1_000); + pool_client.distribute_yield(&token_admin, &token_id, &1_000); + // Managed: 11000 | Shares: 10000 - // Each provider redeems all shares. - // p1: 5000 * 11000 / 10000 = 5500 (pool=11000, shares=10000) - pool_client.withdraw(&p1, &token_id, &5_000); - assert_eq!(token_client.balance(&p1), 5_500); + // Each provider redeems all shares (virtual-offset formula, rounded down). + // p1: 5000 * (11000 + 1000) / (10000 + 1000) = 5454 + pool_client.withdraw(&p1, &token_id, &5_000, &0); + assert_eq!(token_client.balance(&p1), 5_454); - // p2: 3000 * 5500 / 5000 = 3300 (pool=5500, shares=5000 after p1 exit) - pool_client.withdraw(&p2, &token_id, &3_000); - assert_eq!(token_client.balance(&p2), 3_300); + // p2: 3000 * (5546 + 1000) / (5000 + 1000) = 3273 + // (managed=5546, shares=5000 after p1 exit) + pool_client.withdraw(&p2, &token_id, &3_000, &0); + assert_eq!(token_client.balance(&p2), 3_273); - // p3: 2000 * 2200 / 2000 = 2200 (pool=2200, shares=2000 after p1+p2 exit) - pool_client.withdraw(&p3, &token_id, &2_000); - assert_eq!(token_client.balance(&p3), 2_200); + // p3: 2000 * (2273 + 1000) / (2000 + 1000) = 2182 + // (managed=2273, shares=2000 after p1+p2 exit) + pool_client.withdraw(&p3, &token_id, &2_000, &0); + assert_eq!(token_client.balance(&p3), 2_182); - // Pool is fully drained and no shares remain. - assert_eq!(token_client.balance(&pool_id), 0); + // No shares remain; dust is left in the pool, in the pool's favor. + assert_eq!(token_client.balance(&pool_id), 91); assert_eq!(pool_client.get_total_shares(&token_id), 0); } @@ -1618,29 +1668,307 @@ fn test_adjust_outstanding_zero_delta_is_a_no_op() { assert_eq!(pool_client.get_total_outstanding(&token), 1_000); } +// ── #1380: slippage bounds & virtual-share/asset offset ─────────────────────── +// +// These tests reproduce the single-ledger share-price manipulation described +// in #1380 and assert it is now prevented: a bare token transfer to the +// pool's address ("donation") cannot move the share price, the classic +// first-depositor inflation attack is defused by the virtual offset, and +// `min_shares_out`/`min_assets_out` cause settlement to revert rather than +// execute at a worse price than the caller expected. + #[test] -fn test_deposit_transfers_from_depositor_to_pool() { +fn test_donation_to_pool_address_does_not_move_share_price() { + // The exact extraction walkthrough from #1380, replayed against the + // fixed contract: attacker deposits a token unit, donates a huge amount + // directly to the pool's token balance (bypassing `deposit`), then a + // victim deposits. Under the pre-fix code the victim would be minted + // zero shares and the attacker could redeem the victim's principal. let env = Env::default(); - let client = LendingPoolClient::new(&env, &env.register_contract(None, LendingPool)); + env.mock_all_auths(); - let depositor = Address::generate(&env); - let token_admin = Address::generate(&env); - let token = create_token_contract(&env, &token_admin); + let admin = Address::generate(&env); + let (token_id, stellar, token_client) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); - setup_lending_pool(&env, &client, &token.address); - mint_tokens(&env, &token, &depositor, 1_000); + let attacker = Address::generate(&env); + let victim = Address::generate(&env); + stellar.mint(&attacker, &1_000_001); + stellar.mint(&victim, &1_000_000); + + // Step 1-2: attacker deposits 1 unit, first-depositor path -> 1 share. + pool_client.deposit(&attacker, &token_id, &1, &0); + assert_eq!(pool_client.get_shares(&attacker, &token_id), 1); + let price_before_donation = pool_client.get_share_price(&token_id); + + // Step 3: attacker donates 1_000_000 of their *own* funds directly to + // the pool's address (a bare transfer, not `deposit`), completely + // bypassing the deposit accounting. Under the old balance-derived + // pricing this alone would have repriced every share; here it must be + // a no-op for accounting purposes. + token_client.transfer(&attacker, &pool_id, &1_000_000); + assert_eq!( + pool_client.get_pool_stats(&token_id).total_managed_assets, + 1, + "an unsolicited transfer must not move total_managed_assets" + ); + assert_eq!( + pool_client.get_share_price(&token_id), + price_before_donation, + "an unsolicited transfer must not move the share price" + ); - // Execute deposit of 500 tokens - let deposit_amount = 500; - let shares = client.deposit(&depositor, &deposit_amount); + // Step 4: victim deposits 1_000_000. Under the pre-fix formula this + // would compute 1_000_000 * 1 / 1_000_001 = 0 shares. Here the victim + // is priced fairly, independent of the donation. + pool_client.deposit(&victim, &token_id, &1_000_000, &0); + let victim_shares = pool_client.get_shares(&victim, &token_id); + assert_eq!( + victim_shares, 1_000_000, + "victim must be minted fair shares, unaffected by the donation" + ); + + // Step 5: attacker redeems their single share. Under the pre-fix code + // this would have drained the victim's deposit plus the donation + // (assets = 1 * 2_000_001 / 1 = 2_000_001). Here the attacker recovers + // only their own principal; the donated 1_000_000 is not recoverable + // through the share mechanism at all. + pool_client.withdraw(&attacker, &token_id, &1, &0); + assert_eq!( + token_client.balance(&attacker), + 1, // 0 remaining wallet (spent 1_000_001) + 1 redeemed + "attacker must not be able to extract the victim's deposit or the donation" + ); - // Assert depositor balance decreased by 500 - assert_eq!(token.balance(&depositor), 500); + // The victim can redeem their full principal. + pool_client.withdraw(&victim, &token_id, &1_000_000, &0); + assert_eq!(token_client.balance(&victim), 1_000_000); +} - // Assert pool balance increased by 500 - assert_eq!(token.balance(&env.current_contract_address()), 500); +#[test] +fn test_virtual_offset_prevents_first_depositor_inflation_attack() { + // Sanity-checks the virtual-offset math in isolation (independent of the + // total_managed_assets donation-independence fix above): even if a + // "total assets" figure were still manipulated, the offset alone + // prevents the classic inflation attack of rounding a victim's minted + // shares down to zero. + let attacker_shares = LendingPool::calc_shares_to_mint(1, 0, 0); + assert_eq!(attacker_shares, 1); + + // Hypothetical manipulated pricing input: 1 share backed by 1_000_001 + // (a) assets, as if a 1_000_000 donation had been counted. + let manipulated_total_assets = 1_000_001; + + let victim_shares = + LendingPool::calc_shares_to_mint(1_000_000, manipulated_total_assets, attacker_shares); + assert!( + victim_shares > 0, + "the virtual offset must prevent the victim's shares from rounding to zero" + ); + assert_eq!(victim_shares, 999); + + // The attacker's payoff for spending 1_000_000 on the donation plus 1 on + // the deposit is bounded by the offset to a dust-level share of the + // pool -- ruinously unprofitable. + let attacker_assets = LendingPool::calc_assets_to_redeem( + attacker_shares, + manipulated_total_assets + 1_000_000, + attacker_shares + victim_shares, + ); + assert!( + attacker_assets < 1_100, + "attacker must not be able to extract a meaningful share of the pool" + ); +} - // Assert shares were minted to depositor - assert!(shares > 0); - assert_eq!(client.share_balance_of(&depositor), shares); -} \ No newline at end of file +#[test] +fn test_deposit_rejects_when_below_min_shares_out() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, token_client) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + + let provider = Address::generate(&env); + stellar.mint(&provider, &1_000); + + // First deposit would mint exactly 1000 shares (1:1); demand more. + let result = pool_client.try_deposit(&provider, &token_id, &1_000, &1_001); + assert_eq!(result, Err(Ok(crate::PoolError::MinSharesNotMet))); + + // The settlement must not have partially executed: no shares minted, + // no tokens moved. + assert_eq!(pool_client.get_shares(&provider, &token_id), 0); + assert_eq!(token_client.balance(&provider), 1_000); + assert_eq!(token_client.balance(&pool_id), 0); +} + +#[test] +fn test_deposit_succeeds_when_min_shares_out_is_met() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + + let provider = Address::generate(&env); + stellar.mint(&provider, &1_000); + + pool_client.deposit(&provider, &token_id, &1_000, &1_000); + assert_eq!(pool_client.get_shares(&provider, &token_id), 1_000); +} + +#[test] +fn test_withdraw_rejects_when_below_min_assets_out() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, token_client) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); + + let provider = Address::generate(&env); + stellar.mint(&provider, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); + + // Redeeming all 1000 shares at 1:1 returns exactly 1000; demand more. + let result = pool_client.try_withdraw(&provider, &token_id, &1_000, &1_001); + assert_eq!(result, Err(Ok(crate::PoolError::MinAssetsNotMet))); + + // Settlement must not have partially executed. + assert_eq!(pool_client.get_shares(&provider, &token_id), 1_000); + assert_eq!(token_client.balance(&pool_id), 1_000); +} + +#[test] +fn test_distribute_yield_requires_source_authorization() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + + let source = Address::generate(&env); + stellar.mint(&source, &100); + + env.mock_auths(&[]); // Enforce require_auth() natively. + let result = pool_client.try_distribute_yield(&source, &token_id, &100); + assert!(result.is_err()); + assert_eq!( + pool_client.get_pool_stats(&token_id).total_managed_assets, + 0 + ); +} + +#[test] +fn test_preview_deposit_and_preview_redeem_match_settlement() { + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); + + let provider = Address::generate(&env); + stellar.mint(&provider, &2_000); + + // preview_deposit before any deposit exists must match what a first + // deposit actually mints. + let previewed_shares = pool_client.preview_deposit(&token_id, &1_000); + pool_client.deposit(&provider, &token_id, &1_000, &0); + assert_eq!( + previewed_shares, + pool_client.get_shares(&provider, &token_id) + ); + + // Yield arrives; preview_redeem must match what withdraw actually pays. + stellar.mint(&admin, &500); + pool_client.distribute_yield(&admin, &token_id, &500); + + let previewed_assets = pool_client.preview_redeem(&token_id, &1_000); + let balance_before = TokenClient::new(&env, &token_id).balance(&provider); + pool_client.withdraw(&provider, &token_id, &1_000, &0); + let balance_after = TokenClient::new(&env, &token_id).balance(&provider); + assert_eq!(previewed_assets, balance_after - balance_before); + + // preview_* must never mutate state. + assert_eq!(pool_client.get_shares(&provider, &token_id), 0); +} + +#[test] +fn test_round_trip_deposit_then_redeem_is_never_profitable() { + // Property-style check over a range of deposit sizes and pool states + // (including states perturbed by unsolicited donations): depositing `d` + // and immediately redeeming all resulting shares must never return more + // than `d` (round-trip non-profitability, #1380). A fixed-seed + // xorshift PRNG is used instead of pulling in a property-testing crate. + fn next(state: &mut u64) -> u64 { + *state ^= *state << 13; + *state ^= *state >> 7; + *state ^= *state << 17; + *state + } + + let env = Env::default(); + env.mock_all_auths(); + + let admin = Address::generate(&env); + let (token_id, stellar, _) = create_token_contract(&env, &admin); + let pool_id = env.register(LendingPool, ()); + let pool_client = LendingPoolClient::new(&env, &pool_id); + pool_client.initialize(&admin); + pool_client.set_withdrawal_cooldown(&0); + + let mut rng_state: u64 = 0x1234_5678_9abc_def1; + let attacker = Address::generate(&env); + stellar.mint(&attacker, &1_000_000_000); + + // Seed the pool with an initial deposit so later rounds start from a + // nonzero, non-trivial exchange rate. + pool_client.deposit(&attacker, &token_id, &1_000, &0); + + for _ in 0..25 { + // A pseudo-random "donation" directly to the pool's address on + // every round, exercising donation-independence under repeated + // adversarial interleaving, not just a single occurrence. + let donation = 1 + (next(&mut rng_state) % 1_000_000) as i128; + stellar.mint(&pool_id, &donation); + + let provider = Address::generate(&env); + let deposit_amount = 1 + (next(&mut rng_state) % 1_000_000) as i128; + stellar.mint(&provider, &deposit_amount); + + pool_client.deposit(&provider, &token_id, &deposit_amount, &0); + let shares = pool_client.get_shares(&provider, &token_id); + // A deposit must always mint a nonzero amount of shares given a + // nonzero amount in; if it can't, ZeroShares would have reverted + // the call rather than let us reach this point. + assert!(shares > 0); + + let assets_out = pool_client.preview_redeem(&token_id, &shares); + assert!( + assets_out <= deposit_amount, + "round trip must not be profitable: deposited {deposit_amount}, would redeem {assets_out}" + ); + + pool_client.withdraw(&provider, &token_id, &shares, &0); + } +} diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_after_withdraw_frees_cap_space.1.json b/contracts/lending_pool/test_snapshots/test/test_deposit_after_withdraw_frees_cap_space.1.json index 64251711..4365da22 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_after_withdraw_frees_cap_space.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_after_withdraw_frees_cap_space.1.json @@ -114,6 +114,12 @@ "hi": 0, "lo": 3000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -166,6 +172,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -219,6 +231,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -659,6 +677,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 3000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_flow.1.json b/contracts/lending_pool/test_snapshots/test/test_deposit_flow.1.json index 8c067f60..b1ec6d33 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_flow.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_flow.1.json @@ -90,6 +90,12 @@ "hi": 0, "lo": 3000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -450,6 +456,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 3000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.1.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.1.json index f4eb6269..1ef97942 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 1 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -369,6 +381,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.2.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.2.json index 7767a88d..05049353 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.2.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.2.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 100 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 1 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -474,6 +486,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 99 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.3.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.3.json index 29cbe460..06d07554 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.3.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.3.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 100 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 50 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -474,6 +486,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 50 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.4.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.4.json index 513cb314..15c2b5ad 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.4.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.4.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 100 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 100 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -369,6 +381,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.5.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.5.json index d13f4601..f8333495 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.5.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.5.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 3000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -474,6 +486,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.6.json b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.6.json index d3dc3793..bfdd2f17 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.6.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_withdraw_invariants.6.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 10000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 9999 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -474,6 +486,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_deposit_within_cap_succeeds.1.json b/contracts/lending_pool/test_snapshots/test/test_deposit_within_cap_succeeds.1.json index 5160f4ed..f5328d2c 100644 --- a/contracts/lending_pool/test_snapshots/test/test_deposit_within_cap_succeeds.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_deposit_within_cap_succeeds.1.json @@ -114,6 +114,12 @@ "hi": 0, "lo": 5000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -522,6 +528,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 5000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_emergency_withdraw_bypasses_pause_and_cooldown.1.json b/contracts/lending_pool/test_snapshots/test/test_emergency_withdraw_bypasses_pause_and_cooldown.1.json index bac32120..7b57977d 100644 --- a/contracts/lending_pool/test_snapshots/test/test_emergency_withdraw_bypasses_pause_and_cooldown.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_emergency_withdraw_bypasses_pause_and_cooldown.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1500 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -156,6 +162,12 @@ "hi": 0, "lo": 1500 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -417,6 +429,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_full_loan_cycle_with_interest.1.json b/contracts/lending_pool/test_snapshots/test/test_full_loan_cycle_with_interest.1.json index 0d989e34..1b93f182 100644 --- a/contracts/lending_pool/test_snapshots/test/test_full_loan_cycle_with_interest.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_full_loan_cycle_with_interest.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -149,6 +155,31 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "adjust_outstanding", + "args": [ + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], [], [ [ @@ -193,7 +224,32 @@ { "i128": { "hi": 0, - "lo": 880 + "lo": 800 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "adjust_outstanding", + "args": [ + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": -1, + "lo": 18446744073709550816 } } ] @@ -203,6 +259,58 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "distribute_yield", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 80 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 80 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], [], [ [ @@ -224,6 +332,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -405,6 +519,72 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5806905060045992000 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5806905060045992000 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_data": { @@ -481,7 +661,43 @@ "val": { "i128": { "hi": -1, - "lo": 18446744073709551536 + "lo": 18446744073709551576 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 40 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalOutstanding" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 } } }, @@ -503,6 +719,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalYieldDistributed" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 80 + } + } + }, { "key": { "vec": [ @@ -576,7 +810,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 115220454072064130 } }, "durability": "temporary" @@ -591,7 +825,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 115220454072064130 } }, "durability": "temporary", @@ -609,7 +843,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 4837995959683129791 } }, "durability": "temporary" @@ -624,7 +858,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 4837995959683129791 } }, "durability": "temporary", @@ -642,7 +876,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 8370022561469687789 + "nonce": 1194852393571756375 } }, "durability": "temporary" @@ -657,7 +891,40 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 8370022561469687789 + "nonce": 1194852393571756375 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 } }, "durability": "temporary", @@ -713,7 +980,7 @@ "val": { "i128": { "hi": 0, - "lo": 0 + "lo": 40 } } }, @@ -786,7 +1053,7 @@ "val": { "i128": { "hi": 0, - "lo": 1080 + "lo": 1040 } } }, diff --git a/contracts/lending_pool/test_snapshots/test/test_immediate_withdraw_panics_when_cooldown_active.1.json b/contracts/lending_pool/test_snapshots/test/test_immediate_withdraw_panics_when_cooldown_active.1.json index 02f5538f..ddc33c4e 100644 --- a/contracts/lending_pool/test_snapshots/test/test_immediate_withdraw_panics_when_cooldown_active.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_immediate_withdraw_panics_when_cooldown_active.1.json @@ -70,6 +70,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -393,6 +399,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_insufficient_balance_withdraw_panic.1.json b/contracts/lending_pool/test_snapshots/test/test_insufficient_balance_withdraw_panic.1.json index 0d5da48e..0362215e 100644 --- a/contracts/lending_pool/test_snapshots/test/test_insufficient_balance_withdraw_panic.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_insufficient_balance_withdraw_panic.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -445,6 +451,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_no_cap_allows_unlimited_deposits.1.json b/contracts/lending_pool/test_snapshots/test/test_no_cap_allows_unlimited_deposits.1.json index 2a3b5176..5e2230e3 100644 --- a/contracts/lending_pool/test_snapshots/test/test_no_cap_allows_unlimited_deposits.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_no_cap_allows_unlimited_deposits.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -445,6 +451,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_pool_stats.1.json b/contracts/lending_pool/test_snapshots/test/test_pool_stats.1.json index 3fe067f7..9fd42bdd 100644 --- a/contracts/lending_pool/test_snapshots/test/test_pool_stats.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_pool_stats.1.json @@ -117,6 +117,12 @@ "hi": 0, "lo": 2000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -171,6 +177,12 @@ "hi": 0, "lo": 2000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -282,6 +294,12 @@ "hi": 0, "lo": 2000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -312,6 +330,12 @@ "hi": 0, "lo": 2000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -574,6 +598,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_pro_rata_yield_distribution_on_withdrawal.1.json b/contracts/lending_pool/test_snapshots/test/test_pro_rata_yield_distribution_on_withdrawal.1.json index a98711cc..a8052963 100644 --- a/contracts/lending_pool/test_snapshots/test/test_pro_rata_yield_distribution_on_withdrawal.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_pro_rata_yield_distribution_on_withdrawal.1.json @@ -114,6 +114,12 @@ "hi": 0, "lo": 600 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -166,6 +172,12 @@ "hi": 0, "lo": 400 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -208,7 +220,7 @@ "function_name": "mint", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" }, { "i128": { @@ -223,6 +235,58 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "distribute_yield", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", @@ -243,6 +307,12 @@ "hi": 0, "lo": 600 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -271,6 +341,12 @@ "hi": 0, "lo": 400 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -453,6 +529,39 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 6277191135259896685 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_data": { @@ -562,7 +671,25 @@ "val": { "i128": { "hi": -1, - "lo": 18446744073709551516 + "lo": 18446744073709551566 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 50 } } }, @@ -584,6 +711,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalYieldDistributed" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, { "key": { "vec": [ @@ -657,7 +802,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 5806905060045992000 } }, "durability": "temporary" @@ -672,7 +817,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 5806905060045992000 } }, "durability": "temporary", @@ -690,7 +835,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 1194852393571756375 } }, "durability": "temporary" @@ -705,7 +850,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 1194852393571756375 } }, "durability": "temporary", @@ -723,7 +868,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 5806905060045992000 + "nonce": 4270020994084947596 } }, "durability": "temporary" @@ -738,7 +883,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 5806905060045992000 + "nonce": 4270020994084947596 } }, "durability": "temporary", @@ -750,6 +895,79 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], [ { "contract_data": { @@ -794,7 +1012,7 @@ "val": { "i128": { "hi": 0, - "lo": 0 + "lo": 50 } } }, @@ -867,7 +1085,7 @@ "val": { "i128": { "hi": 0, - "lo": 1060 + "lo": 1030 } } }, @@ -940,7 +1158,7 @@ "val": { "i128": { "hi": 0, - "lo": 1040 + "lo": 1020 } } }, diff --git a/contracts/lending_pool/test_snapshots/test/test_share_price_increases_when_interest_arrives.1.json b/contracts/lending_pool/test_snapshots/test/test_share_price_increases_when_interest_arrives.1.json index 75b7f922..31c9f490 100644 --- a/contracts/lending_pool/test_snapshots/test/test_share_price_increases_when_interest_arrives.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_share_price_increases_when_interest_arrives.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -131,7 +137,7 @@ "function_name": "mint", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" }, { "i128": { @@ -146,6 +152,58 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "distribute_yield", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], [], [] ], @@ -286,6 +344,39 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_data": { @@ -504,6 +595,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1100 + } + } + }, { "key": { "vec": [ @@ -522,6 +631,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalYieldDistributed" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, { "key": { "vec": [ @@ -589,6 +716,79 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], [ { "contract_data": { diff --git a/contracts/lending_pool/test_snapshots/test/test_subsequent_depositor_does_not_dilute_existing_holders.1.json b/contracts/lending_pool/test_snapshots/test/test_subsequent_depositor_does_not_dilute_existing_holders.1.json index b0a06633..a0f65541 100644 --- a/contracts/lending_pool/test_snapshots/test/test_subsequent_depositor_does_not_dilute_existing_holders.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_subsequent_depositor_does_not_dilute_existing_holders.1.json @@ -114,6 +114,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -156,7 +162,7 @@ "function_name": "mint", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" }, { "i128": { @@ -171,6 +177,58 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "distribute_yield", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 100 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", @@ -191,6 +249,12 @@ "hi": 0, "lo": 1100 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -245,6 +309,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -272,7 +342,13 @@ { "i128": { "hi": 0, - "lo": 1000 + "lo": 1047 + } + }, + { + "i128": { + "hi": 0, + "lo": 0 } } ] @@ -488,6 +564,39 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_data": { @@ -564,7 +673,25 @@ "val": { "i128": { "hi": -1, - "lo": 18446744073709551516 + "lo": 18446744073709551567 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 51 } } }, @@ -586,6 +713,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalYieldDistributed" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 + } + } + }, { "key": { "vec": [ @@ -659,7 +804,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 5806905060045992000 } }, "durability": "temporary" @@ -674,7 +819,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 6277191135259896685 + "nonce": 5806905060045992000 } }, "durability": "temporary", @@ -692,7 +837,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 5806905060045992000 + "nonce": 1194852393571756375 } }, "durability": "temporary" @@ -707,7 +852,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 5806905060045992000 + "nonce": 1194852393571756375 } }, "durability": "temporary", @@ -725,7 +870,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 8370022561469687789 + "nonce": 6277191135259896685 } }, "durability": "temporary" @@ -740,7 +885,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", "key": { "ledger_key_nonce": { - "nonce": 8370022561469687789 + "nonce": 6277191135259896685 } }, "durability": "temporary", @@ -752,6 +897,79 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], [ { "contract_data": { @@ -796,7 +1014,7 @@ "val": { "i128": { "hi": 0, - "lo": 0 + "lo": 51 } } }, @@ -869,7 +1087,7 @@ "val": { "i128": { "hi": 0, - "lo": 1100 + "lo": 1050 } } }, @@ -942,7 +1160,7 @@ "val": { "i128": { "hi": 0, - "lo": 1100 + "lo": 1099 } } }, diff --git a/contracts/lending_pool/test_snapshots/test/test_withdraw_blocked_when_paused.1.json b/contracts/lending_pool/test_snapshots/test/test_withdraw_blocked_when_paused.1.json index 3f18e62a..53724d84 100644 --- a/contracts/lending_pool/test_snapshots/test/test_withdraw_blocked_when_paused.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_withdraw_blocked_when_paused.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -493,6 +499,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_withdraw_flow.1.json b/contracts/lending_pool/test_snapshots/test/test_withdraw_flow.1.json index 356ab658..ccdbb924 100644 --- a/contracts/lending_pool/test_snapshots/test/test_withdraw_flow.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_withdraw_flow.1.json @@ -90,6 +90,12 @@ "hi": 0, "lo": 3000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -145,6 +151,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -480,6 +492,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_withdraw_reduces_total_deposits.1.json b/contracts/lending_pool/test_snapshots/test/test_withdraw_reduces_total_deposits.1.json index f505469e..cb049a32 100644 --- a/contracts/lending_pool/test_snapshots/test/test_withdraw_reduces_total_deposits.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_withdraw_reduces_total_deposits.1.json @@ -114,6 +114,12 @@ "hi": 0, "lo": 3000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -167,6 +173,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -550,6 +562,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 2000 + } + } + }, { "key": { "vec": [ diff --git a/contracts/lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_interest.1.json b/contracts/lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_interest.1.json index 104a4d6c..3553bb03 100644 --- a/contracts/lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_interest.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_interest.1.json @@ -89,6 +89,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -131,7 +137,7 @@ "function_name": "mint", "args": [ { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" }, { "i128": { @@ -146,6 +152,58 @@ } ] ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "distribute_yield", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + }, + { + "i128": { + "hi": 0, + "lo": 200 + } + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "i128": { + "hi": 0, + "lo": 200 + } + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", @@ -166,6 +224,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -314,6 +378,39 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_data": { @@ -423,7 +520,25 @@ "val": { "i128": { "hi": -1, - "lo": 18446744073709551416 + "lo": 18446744073709551516 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 100 } } }, @@ -445,6 +560,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalYieldDistributed" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 200 + } + } + }, { "key": { "vec": [ @@ -485,7 +618,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 4837995959683129791 } }, "durability": "temporary" @@ -500,7 +633,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4270020994084947596 + "nonce": 4837995959683129791 } }, "durability": "temporary", @@ -518,7 +651,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 8370022561469687789 } }, "durability": "temporary" @@ -533,7 +666,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 8370022561469687789 } }, "durability": "temporary", @@ -545,6 +678,79 @@ 6311999 ] ], + [ + { + "contract_data": { + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + 518400 + ] + ], [ { "contract_data": { @@ -589,7 +795,7 @@ "val": { "i128": { "hi": 0, - "lo": 0 + "lo": 100 } } }, @@ -662,7 +868,7 @@ "val": { "i128": { "hi": 0, - "lo": 1200 + "lo": 1100 } } }, diff --git a/contracts/lending_pool/test_snapshots/test/test_withdraw_succeeds_after_cooldown.1.json b/contracts/lending_pool/test_snapshots/test/test_withdraw_succeeds_after_cooldown.1.json index a220da33..52d1005f 100644 --- a/contracts/lending_pool/test_snapshots/test/test_withdraw_succeeds_after_cooldown.1.json +++ b/contracts/lending_pool/test_snapshots/test/test_withdraw_succeeds_after_cooldown.1.json @@ -90,6 +90,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -142,6 +148,12 @@ "hi": 0, "lo": 1000 } + }, + { + "i128": { + "hi": 0, + "lo": 0 + } } ] } @@ -370,6 +382,24 @@ } } }, + { + "key": { + "vec": [ + { + "symbol": "TotalManagedAssets" + }, + { + "address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, { "key": { "vec": [