diff --git a/INVARIANTS.md b/INVARIANTS.md index f1277cbb..b4ccd10c 100644 --- a/INVARIANTS.md +++ b/INVARIANTS.md @@ -1,12 +1,12 @@ ## Vault Balance Invariant -**Invariant**: For every reachable state of the `CalloraVault` contract, the stored balance in `VaultMeta.balance` is always **greater than or equal to 0**. +**Invariant**: For every reachable state of the `CalloraVault` contract, the stored balance in `VaultMeta.balance` is always **greater than or equal to 0** and **less than or equal to i128::MAX**. - **Storage field**: `VaultMeta.balance : i128` - **Accessors**: - `get_meta(env: Env) -> VaultMeta` - `balance(env: Env) -> i128` -- **Guarantee**: Any value returned by `get_meta(env).balance` or `balance(env)` is **never negative**. +- **Guarantee**: Any value returned by `get_meta(env).balance` or `balance(env)` is **never negative** and **cannot overflow** the `i128` numeric boundary. Any operation that would cause an overflow (e.g., `deposit` past `i128::MAX`) will panic and revert the transaction. This document lists all functions that can change the stored balance and the pre-/post-conditions that preserve this invariant. diff --git a/contracts/vault/src/test.rs b/contracts/vault/src/test.rs index 72c5c391..92a8170b 100644 --- a/contracts/vault/src/test.rs +++ b/contracts/vault/src/test.rs @@ -1997,6 +1997,36 @@ fn get_revenue_pool_consistency_with_zero_balance() { assert_eq!(client.get_revenue_pool(), Some(pool)); } +#[test] +fn deposit_max_balance_overflow_panic() { + // Explicit test for max-balance overflow near i128::MAX. + // Exercises the checked_add(...).unwrap_or_else(|| panic!("balance overflow")) path. + let env = Env::default(); + let owner = Address::generate(&env); + let (vault_address, client) = create_vault(&env); + let (usdc, usdc_client, usdc_admin) = create_usdc(&env, &owner); + + env.mock_all_auths(); + + // 1. Setup vault balance near i128::MAX + let near_max = i128::MAX - 1; + let overflow_amount = 2; + + fund_vault(&usdc_admin, &vault_address, near_max); + client.init(&owner, &usdc, &Some(near_max), &None, &None, &None, &None); + + // 2. Prepare overflow deposit + usdc_admin.mint(&owner, &overflow_amount); + usdc_client.approve(&owner, &vault_address, &overflow_amount, &1000); + + // 3. Confirm it panics safely on overflow + let result = client.try_deposit(&owner, &overflow_amount); + assert!( + result.is_err(), + "contract must fail safely when balance would overflow i128::MAX" + ); +} + #[test] fn get_revenue_pool_after_multiple_sequential_updates() { // Test multiple sequential set/clear operations before query