Skip to content

Commit 9218bf2

Browse files
committed
Add invariant assertion tests for share price and pool conservation
1 parent 614081d commit 9218bf2

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

contracts/vault/src/test.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,3 +858,82 @@ fn test_upgrade_contract() {
858858
// Version should increment
859859
assert_eq!(vault.version(), 2);
860860
}
861+
862+
// ─── Invariant Tests (Issue #258) ───────────────────────────────────────────────
863+
864+
#[test]
865+
fn test_invariant_pool_conservation_and_monotonic_price() {
866+
let env = Env::default();
867+
env.mock_all_auths();
868+
869+
let (vault, _, usdc_sa, admin) = setup_vault(&env);
870+
871+
let user1 = Address::generate(&env);
872+
let user2 = Address::generate(&env);
873+
let user3 = Address::generate(&env);
874+
875+
usdc_sa.mint(&user1, &1_000_000_000);
876+
usdc_sa.mint(&user2, &1_000_000_000);
877+
usdc_sa.mint(&user3, &1_000_000_000);
878+
usdc_sa.mint(&admin, &500_000_000);
879+
880+
let assert_pool_conservation = |v: &YieldVaultClient| {
881+
let shares = v.total_shares();
882+
let assets = v.total_assets();
883+
let price = v.get_share_price();
884+
885+
if shares == 0 {
886+
assert_eq!(assets, 0);
887+
} else {
888+
let reconstructed_assets = (shares.checked_mul(price).unwrap()) / SCALE;
889+
let diff = (assets - reconstructed_assets).abs();
890+
assert!(diff <= 1, "Pool conservation invariant failed. Expected: {}, Got: {}", assets, reconstructed_assets);
891+
}
892+
};
893+
894+
// 1. Zero deposits edge case
895+
assert_pool_conservation(&vault);
896+
let mut last_price = vault.get_share_price();
897+
assert_eq!(last_price, SCALE);
898+
899+
// 2. Initial deposits
900+
vault.deposit(&user1, &100_000);
901+
assert_pool_conservation(&vault);
902+
assert!(vault.get_share_price() >= last_price, "Price decreased");
903+
last_price = vault.get_share_price();
904+
905+
vault.deposit(&user2, &200_000);
906+
assert_pool_conservation(&vault);
907+
assert!(vault.get_share_price() >= last_price, "Price decreased");
908+
last_price = vault.get_share_price();
909+
910+
// 3. Yield Accrual (Monotonically non-decreasing price)
911+
vault.accrue_yield(&50_000);
912+
assert_pool_conservation(&vault);
913+
assert!(vault.get_share_price() >= last_price, "Price decreased");
914+
last_price = vault.get_share_price();
915+
916+
// 4. Partial withdrawal
917+
vault.withdraw(&user1, &50_000);
918+
assert_pool_conservation(&vault);
919+
assert!(vault.get_share_price() >= last_price, "Price decreased");
920+
last_price = vault.get_share_price();
921+
922+
// 5. Another yield accrual
923+
vault.accrue_yield(&20_000);
924+
assert_pool_conservation(&vault);
925+
assert!(vault.get_share_price() >= last_price, "Price decreased");
926+
last_price = vault.get_share_price();
927+
928+
// 6. Max deposit cap / large deposit
929+
vault.deposit(&user3, &500_000_000);
930+
assert_pool_conservation(&vault);
931+
assert!(vault.get_share_price() >= last_price, "Price decreased");
932+
last_price = vault.get_share_price();
933+
934+
// 7. Full withdrawals
935+
let user2_balance = vault.balance(&user2);
936+
vault.withdraw(&user2, &user2_balance);
937+
assert_pool_conservation(&vault);
938+
assert!(vault.get_share_price() >= last_price, "Price decreased");
939+
}

0 commit comments

Comments
 (0)