From ca0b8c8b1d8e797d8f2e97f071131737b4a83ed9 Mon Sep 17 00:00:00 2001 From: Devadakene Date: Tue, 18 Aug 2026 07:02:26 +0100 Subject: [PATCH] fix(escrow): implement InsufficientBalance and remove NotExpired - Wired InsufficientBalance into release and refund as a defensive check against the contract's actual token balance, making the solvency invariant enforced at runtime. - Removed NotExpired as it was confirmed to be unused scaffolding. - Added tests to verify InsufficientBalance check. --- contracts/escrow/src/error.rs | 2 +- contracts/escrow/src/lib.rs | 9 +++++++ contracts/escrow/src/test.rs | 44 +++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/contracts/escrow/src/error.rs b/contracts/escrow/src/error.rs index 581ad25..5e33550 100644 --- a/contracts/escrow/src/error.rs +++ b/contracts/escrow/src/error.rs @@ -13,7 +13,7 @@ pub enum Error { AlreadyRefunded = 7, InvalidSplit = 8, InvalidAmount = 9, - NotExpired = 10, + // NotExpired = 10, removed in ABI-breaking cleanup as it was unused scaffolding InsufficientBalance = 11, InvalidFee = 12, InvalidDeadline = 13, diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 56af404..e3519b4 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -208,6 +208,10 @@ impl EscrowContract { let token_client = token::Client::new(&env, &escrow.token); let contract_address = env.current_contract_address(); + if token_client.balance(&contract_address) < escrow.amount { + return Err(Error::InsufficientBalance); + } + if payouts.fee > 0 { token_client.transfer(&contract_address, &treasury, &payouts.fee); } @@ -255,6 +259,11 @@ impl EscrowContract { let token_client = token::Client::new(&env, &escrow.token); let contract_address = env.current_contract_address(); + + if token_client.balance(&contract_address) < escrow.amount { + return Err(Error::InsufficientBalance); + } + for i in 0..escrow.contributor_count { let contribution_key = DataKey::Contribution(issue_id, i); let contribution: Contribution = diff --git a/contracts/escrow/src/test.rs b/contracts/escrow/src/test.rs index ea57571..fefd292 100644 --- a/contracts/escrow/src/test.rs +++ b/contracts/escrow/src/test.rs @@ -881,3 +881,47 @@ fn test_release_loses_race_to_refund_at_grace_period_boundary() { // The would-be recipient gets nothing. assert_eq!(token_client.balance(&contributor), 0); } + +#[test] +fn test_release_rejects_if_contract_balance_insufficient() { + let env = Env::default(); + env.mock_all_auths(); + let (_, _admin, _treasury, client) = setup(&env); + + let token_admin = Address::generate(&env); + let (token_addr, asset_client, token_client) = create_token(&env, &token_admin); + let sponsor = Address::generate(&env); + asset_client.mint(&sponsor, &10_000_000_000i128); + + client.fund(&202u64, &sponsor, &token_addr, &10_000_000_000i128, &200u64); + + // Drain the contract's balance manually to trigger the defensive check + // However, the test environment allows us to use `env.mock_all_auths()`. + token_client.transfer(&client.address, &sponsor, &5_000_000_000i128); + + let contributor = Address::generate(&env); + let recipients = vec![&env, (contributor.clone(), 10_000u32)]; + let err = client.try_release(&202u64, &recipients); + assert_eq!(err, Err(Ok(Error::InsufficientBalance))); +} + +#[test] +fn test_refund_rejects_if_contract_balance_insufficient() { + let env = Env::default(); + env.mock_all_auths(); + let (_, _admin, _treasury, client) = setup(&env); + + let token_admin = Address::generate(&env); + let (token_addr, asset_client, token_client) = create_token(&env, &token_admin); + let sponsor = Address::generate(&env); + asset_client.mint(&sponsor, &10_000_000_000i128); + + client.fund(&203u64, &sponsor, &token_addr, &10_000_000_000i128, &200u64); + + // Drain the contract's balance manually to trigger the defensive check + token_client.transfer(&client.address, &sponsor, &5_000_000_000i128); + + env.ledger().set_timestamp(300); + let err = client.try_refund(&203u64); + assert_eq!(err, Err(Ok(Error::InsufficientBalance))); +}