Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
dd3831a
perf(cancel): compact escrow lock storage key
zeemscript Aug 26, 2026
eb702a3
test: record cancel escrow auth snapshot
zeemscript Aug 26, 2026
52025d9
feat: Optimize storage keys footprint for milestone_time_extension (#…
Toyosi5566 Aug 26, 2026
90cad44
feat: Optimize storage keys footprint for milestone_time_extension (#…
Toyosi5566 Aug 26, 2026
190b548
feat: Optimize storage keys footprint for milestone_time_extension (#…
Toyosi5566 Aug 26, 2026
13308e1
feat(cancel_escrow): add business rule validations and comprehensive …
Aug 27, 2026
151e4eb
Merge branch 'main' into feat/issue-288-optimize-storage-keys-footpri…
godamongstmen897 Aug 27, 2026
85d9e3a
Merge branch 'main' into fix/issue-296
godamongstmen897 Aug 27, 2026
01f8b8c
feat(cancel_escrow): enrich structured event and add indexer parsing …
Aug 27, 2026
6b1fe04
Merge branch 'main' into feat/cancel-escrow-structured-events
Jessepriase Aug 27, 2026
51e977c
Harden auth and precondition guards (#351, #345, #336, #350)
miss-yusrah Aug 27, 2026
61a1ea3
fix(tax_withholding_deductions): reject Disputed milestones (#298)
larryjay007 Aug 27, 2026
9b6693c
feat: harden caller authorization and precondition guards in admin_ov…
Ceejaytech25 Aug 28, 2026
41610a7
feat: harden caller authorization and precondition guards in admin_ov…
Ceejaytech25 Aug 28, 2026
7c27b8a
Merge pull request #412 from Ceejaytech25/feat/issue-330-341-harden-a…
godamongstmen897 Aug 28, 2026
bce737d
feat: add multisig_transfer_admin test suite and harden override-refu…
EDOHWARES Aug 28, 2026
1c9340a
Fix: reduce ledger storage footprint of admin_override_cancel_refund
zinodict121 Aug 28, 2026
2d96869
Merge pull request #413 from EDOHWARES/feat/issues-327-337-multisig-a…
godamongstmen897 Aug 28, 2026
28779cd
test: add i128::MIN boundary test for escrow_interest_yield
zinodict121 Aug 28, 2026
b3a3d77
solved
KayMuna Aug 28, 2026
600f1b3
fix(msadm): reduce multisig_admin_override_release storage footprint
GreatShinro Aug 28, 2026
ba4f76b
fix(msadm): harden multisig_admin_override_refund arithmetic
GreatShinro Aug 28, 2026
e905f12
refactor(storage): consolidate admin_set_yield_rate ledger footprint …
Aug 28, 2026
f8beddc
refactor(upgrade): enforce auth and state preconditions before WASM u…
Aug 28, 2026
466a1f8
feat: harden caller authorization and precondition guards in multisig…
Aug 28, 2026
4507da7
fix(#383,#386): reduce cancel_release ledger footprint and add checke…
Sarietgold Aug 28, 2026
a41aa5d
Merge pull request #422 from Sarietgold/fix/issues-383-386-admin-over…
godamongstmen897 Aug 29, 2026
e1068cd
fix: resolve issue #328
issue-solver-bot Aug 29, 2026
715af21
Minor Admin Changes
Adeswalla Aug 29, 2026
9e58a16
Optimize multisig refund lock storage
Adeswalla Aug 29, 2026
1b1a32f
fix(cancel): align cancel lock key with upstream API
zeemscript Aug 29, 2026
773d178
fix: add storage non-mutation coverage to multisig_approval_init guar…
Yerimahjr Aug 29, 2026
68aea6a
fix-checked-arithmetic-footprint
Jumongweb Aug 30, 2026
f25d756
test(multisig_split_refund): add extreme-value boundary tests for che…
qa-eden Aug 30, 2026
83b6192
fix(emergency_pause_admin_override): remove unnecessary EmergencyPaus…
qa-eden Aug 30, 2026
800671d
fix: repair milestone-escrow CI build and tests
iexwr Aug 30, 2026
cb6a9fb
feat(#321): guard emergency_pause_claim_refund on zero balance
iexwr Aug 30, 2026
4d77f85
fix: repair milestone-escrow CI build and tests
iexwr Aug 30, 2026
44ec225
fix(#323): optimize emergency-pause storage keys to reduce on-ledger …
iexwr Aug 30, 2026
28cb973
update
Aug 30, 2026
537c1c8
feat: Emit a structured event recording the outcome of apply_dispute_…
Aug 30, 2026
b5e1dfe
feat: implement Reduce the ledger storage footprint of pf_alloc_admin…
Aug 30, 2026
7eb3b03
feat: Emit a structured event recording the outcome of apply_dispute_…
Aug 30, 2026
129f44d
# Commit message
Aug 30, 2026
6a0188b
# Commit message
Aug 30, 2026
f381617
feat: harden caller auth and precondition guards in admin_set_yield_rate
woodlonestar-lang Aug 31, 2026
6843ce3
feat: emit structured events during platform_fee_allocation (#309)
ciscokwiz Aug 31, 2026
18078cc
feat: harden caller authorization and precondition guards in admin_ta…
esthertitilayo-dev Aug 31, 2026
0b584c0
feat: harden caller authorization and precondition guards in execute_…
esthertitilayo-dev Aug 31, 2026
fa778ca
feat: harden caller authorization and precondition guards in admin_pa…
esthertitilayo-dev Aug 31, 2026
b95ee23
Harden caller authorization and precondition guards in admin_override…
josephinejose-alt Aug 31, 2026
6958a60
fix(ci): align cancel event contract tests
Aug 31, 2026
da596c0
Merge pull request #429 from iexwr/fix/ci-test-build
godamongstmen897 Sep 1, 2026
5857a94
Merge branch 'main' into fix/353-harden-multisig-approval-init-guards
Yerimahjr Sep 1, 2026
ee17672
Merge branch 'main' into fix/issue-296
godamongstmen897 Sep 1, 2026
3eb3520
Merge branch 'main' into feat/cancel-escrow-structured-events
godamongstmen897 Sep 1, 2026
f381a46
Merge branch 'main' into feature/issue-298-tax-withholding-validation
godamongstmen897 Sep 1, 2026
cc959ac
Merge branch 'main' into solver/issue-328-79-a0
godamongstmen897 Sep 1, 2026
683d32a
Merge branch 'main' into fix/checked-arithmetic-multisig-split-refund
godamongstmen897 Sep 1, 2026
2b84307
Merge branch 'main' into Emit_structured_events_during_platform_fee_a…
godamongstmen897 Sep 1, 2026
efbdfc3
Merge branch 'main' into security/335-admin-tax-withholding-guards
godamongstmen897 Sep 1, 2026
67c3054
Merge pull request #368 from iexwr/issue-321-323-emergency-pause
godamongstmen897 Sep 1, 2026
479665f
Merge pull request #358 from Beneaththesky001/fix/issue-296
godamongstmen897 Sep 1, 2026
1a71d19
Merge pull request #414 from zinodict121/fix-admin-override
godamongstmen897 Sep 1, 2026
73c3f0b
Merge pull request #415 from zinodict121/fix-yield-rate-checked-math
godamongstmen897 Sep 1, 2026
233ff44
Merge pull request #416 from KayMuna/feat/issues
godamongstmen897 Sep 1, 2026
c91a7a6
Merge pull request #417 from GreatShinro/feat/multisig-override-relea…
godamongstmen897 Sep 1, 2026
7686d26
Merge pull request #419 from Awosdot/feature/issue-407-reduce-yield-r…
godamongstmen897 Sep 1, 2026
18b76a5
Merge pull request #420 from Awosdot/feature/issue-352-upgrade-author…
godamongstmen897 Sep 1, 2026
5f45c90
Merge pull request #421 from Awosdot/feature/issue-354-harden-multisi…
godamongstmen897 Sep 1, 2026
438828c
Merge pull request #430 from iexwr/feat/321-empty-balance
godamongstmen897 Sep 1, 2026
c73ab35
Merge pull request #377 from Jessepriase/feat/cancel-escrow-structure…
godamongstmen897 Sep 1, 2026
b4b2f5c
Merge pull request #423 from Darktan242/solver/issue-328-79-a0
godamongstmen897 Sep 1, 2026
5952d16
Merge pull request #427 from qa-eden/fix/checked-arithmetic-multisig-…
godamongstmen897 Sep 1, 2026
82bdb06
Merge pull request #436 from ciscokwiz/Emit_structured_events_during_…
godamongstmen897 Sep 1, 2026
3c113f9
Merge pull request #437 from esthertitilayo-dev/security/335-admin-ta…
godamongstmen897 Sep 1, 2026
33f4740
Merge branch 'main' into fix/353-harden-multisig-approval-init-guards
Yerimahjr Sep 1, 2026
e92e42e
Merge branch 'refs/heads/pr/418' into work-418
godamongstmen897 Sep 1, 2026
cd1cd27
Merge main into #418 and fix the event assertion ordering
godamongstmen897 Sep 1, 2026
f4475d5
Merge pull request #418 from GreatShinro/feat/multisig-override-refun…
godamongstmen897 Sep 1, 2026
aa02993
Merge branch 'refs/heads/pr/367' into work-367
godamongstmen897 Sep 1, 2026
1e14998
Merge main into #367 and drop a field that does not exist
godamongstmen897 Sep 1, 2026
2b0d7f9
Merge pull request #367 from Toyosi5566/feat/issue-288-optimize-stora…
godamongstmen897 Sep 1, 2026
a960400
Merge remote-tracking branch 'upstream/main' into tmp-merge-check
larryjay007 Sep 1, 2026
46f9732
Merge main into #375: correct the pause key and the released-mileston…
godamongstmen897 Sep 1, 2026
70ec5e4
Merge pull request #375 from Jessepriase/feat/cancel-escrow-validation
godamongstmen897 Sep 1, 2026
1c5c477
Merge branch 'refs/heads/pr/425' into work-425
godamongstmen897 Sep 1, 2026
92bf39f
Merge main into #425 and restore two tests it dropped
godamongstmen897 Sep 1, 2026
8b94a76
Merge pull request #425 from Yerimahjr/fix/353-harden-multisig-approv…
godamongstmen897 Sep 1, 2026
770b990
Merge main into #438 and fix the event assertion ordering
godamongstmen897 Sep 1, 2026
16216af
Merge pull request #438 from esthertitilayo-dev/security/347-execute-…
godamongstmen897 Sep 1, 2026
3881799
Merge main into #411, taking the exhaustive status match
godamongstmen897 Sep 1, 2026
74c8bfd
Merge branch 'pr411f' into work-411
godamongstmen897 Sep 1, 2026
7d6bdc0
Merge branch 'main' into security/349-admin-pause-escrow-guards
esthertitilayo-dev Sep 1, 2026
3c2fede
Merge pull request #411 from larryjay007/feature/issue-298-tax-withho…
godamongstmen897 Sep 1, 2026
70775a9
Merge main into #435 and close the pause gap its tests found
godamongstmen897 Sep 1, 2026
0e33f90
Merge pull request #435 from Esbeevybz/feat/harden-admin-set-yield-ra…
godamongstmen897 Sep 1, 2026
ae192d8
Merge main into #428, keeping the lock removal
godamongstmen897 Sep 1, 2026
2694574
Merge pull request #428 from qa-eden/fix/reduce-emergency-pause-admin…
godamongstmen897 Sep 1, 2026
1b4f127
Merge branch 'refs/heads/pr/439' into work-439
godamongstmen897 Sep 1, 2026
8dc85bf
Merge main into #439 and repair admin_pause_escrow
godamongstmen897 Sep 1, 2026
e138716
Merge pull request #439 from esthertitilayo-dev/security/349-admin-pa…
godamongstmen897 Sep 1, 2026
060d7c5
Merge PR #434: emit PlatformFeeAllocationOverrideEvent from pf_alloc_…
godamongstmen897 Sep 1, 2026
665e4e4
Merge pull request #434 from CollinsC1O/event-recording
godamongstmen897 Sep 1, 2026
a809e64
Merge PR #432: drop the redundant re-entrancy guard from pf_alloc_adm…
godamongstmen897 Sep 1, 2026
90c3d38
Merge pull request #432 from CollinsC1O/storage
godamongstmen897 Sep 1, 2026
a9dab7f
Merge PR #426: persistent-storage fallback for milestone time extensions
godamongstmen897 Sep 1, 2026
592ce51
Merge pull request #426 from Jumongweb/fix-checked-arithmetic-footprint
godamongstmen897 Sep 1, 2026
452d9f1
Merge PR #424: drop redundant ledger writes from three admin paths
godamongstmen897 Sep 1, 2026
dc86484
Merge pull request #424 from Adeswalla/Admin-Changes
godamongstmen897 Sep 1, 2026
e252ced
Merge PR #440: reject admin_override_refund while the escrow is paused
godamongstmen897 Sep 1, 2026
71cd2ba
Merge pull request #440 from josephinejose-alt/feature/harden-admin-o…
godamongstmen897 Sep 1, 2026
63cd54e
Merge PR #433: report the replaced flag in EmergencyPauseAdminOverrid…
godamongstmen897 Sep 1, 2026
9def641
Merge pull request #433 from CollinsC1O/emergency-pause
godamongstmen897 Sep 1, 2026
19719eb
Merge PR #431: ArbitrationSplitAppliedEvent (#400)
godamongstmen897 Sep 1, 2026
b1a3d10
Merge pull request #431 from CollinsC1O/structure-event
godamongstmen897 Sep 1, 2026
5e8c048
Merge PR #378: harden auth and precondition guards (#351, #345, #336,…
godamongstmen897 Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ MIT

Explorer: `https://stellar.expert/explorer/testnet/contract/CDD5WKK3WT3QVKXMXTJNDIXE4T73FK6GGXDSD6UTJAH6YYZU52SQ4MUH`
- Update README with latest progress

364 changes: 364 additions & 0 deletions contracts/milestone-escrow/src/admin_override_cancel_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
//! Tests for `admin_override_cancel_release` (issue #383) and
//! `admin_override_cancel_refund` (issue #386).
//!
//! Issue #383 - Reduce ledger storage footprint of admin_override_cancel_release
//! The MilestoneReleased(u32) temporary key is no longer written inside
//! admin_override_cancel_release. These tests verify:
//! - All existing happy-path behaviour (tokens transferred, milestone status
//! updated, CancelLock cleared) is unchanged.
//! - The temporary MilestoneReleased flag is NOT set after the call,
//! confirming the redundant write was removed.
//!
//! Issue #386 - Replace unchecked arithmetic in admin_override_cancel_refund
//! All arithmetic now uses checked operations and explicit non-negativity
//! guards. These tests verify:
//! - Valid amounts produce results identical to the previous behaviour.
//! - Error::InvalidAmount is returned (not a panic) for edge-case amounts.
//! - Terminal milestones are correctly skipped in every scenario.

#![cfg(test)]

use super::*;
use crate::test::setup_funded_escrow;
use crate::{DataKey, Error, MilestoneEscrowClient, MilestoneStatus};
use soroban_sdk::{testutils::Address as _, token, vec, Address, Env};

// ────────────────────────────────────────────────────────────────────────────
// Issue #383: admin_override_cancel_release storage footprint
// ────────────────────────────────────────────────────────────────────────────

/// Happy path: cancel-locked escrow with two pending milestones is fully
/// released to the freelancer; persistent milestone status is Released and
/// the temporary MilestoneReleased flag is absent (issue #383 optimisation).
#[test]
fn test_cancel_release_happy_path_no_temporary_released_flag() {
let env = Env::default();
env.mock_all_auths();

let (_, freelancer_addr, _, admin_addr, token_id, contract_id, client) =
setup_funded_escrow(&env, vec![&env, 3_000_i128, 7_000_i128]);

client.cancel_escrow(&freelancer_addr);

let token = token::Client::new(&env, &token_id);
let freelancer_before = token.balance(&freelancer_addr);
let contract_before = token.balance(&client.address);

client.admin_override_cancel_release(&admin_addr);

// token balances
assert_eq!(token.balance(&freelancer_addr), freelancer_before + 10_000);
assert_eq!(token.balance(&client.address), contract_before - 10_000);

// milestone persistent state
let job = client.get_job();
for idx in 0..2u32 {
let ms = job.milestones.get(idx).unwrap();
assert_eq!(ms.status, MilestoneStatus::Released);
assert_eq!(ms.released_amount, ms.amount);
}

// CancelLock cleared
let still_locked: bool = env.as_contract(&contract_id, || {
env.storage()
.instance()
.get(&DataKey::CancelLock)
.unwrap_or(false)
});
assert!(!still_locked);

// MilestoneReleased temporary flag must NOT be set (issue #383)
for idx in 0..2u32 {
let flag: Option<bool> = env.as_contract(&contract_id, || {
env.storage()
.temporary()
.get(&DataKey::MilestoneReleased(idx))
});
assert_eq!(flag, None);
}
}

/// Terminal milestones (already Released) are skipped; only pending ones count
/// toward the total and no temporary flag is written for any of them.
#[test]
fn test_cancel_release_skips_terminal_milestones_no_flag() {
let env = Env::default();
env.mock_all_auths();

let amounts = vec![&env, 2_000_i128, 3_000_i128, 5_000_i128];
let (client_addr, freelancer_addr, _, admin_addr, token_id, contract_id, client) =
setup_funded_escrow(&env, amounts);

// Approve milestone 0 via normal path so it becomes Released.
client.mark_delivered(&freelancer_addr, &0u32);
client.approve_milestone(&client_addr, &0u32);

client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let freelancer_before = token.balance(&freelancer_addr);

client.admin_override_cancel_release(&admin_addr);

// Only milestones 1 and 2 (8_000 total) transferred.
assert_eq!(token.balance(&freelancer_addr), freelancer_before + 8_000);

// Milestones 1 and 2 must not have the temporary released flag.
for idx in 1..3u32 {
let flag: Option<bool> = env.as_contract(&contract_id, || {
env.storage()
.temporary()
.get(&DataKey::MilestoneReleased(idx))
});
assert_eq!(flag, None);
}
}

/// admin_override_cancel_release must fail with InvalidStatus when no cancel
/// lock is active.
#[test]
fn test_cancel_release_requires_cancel_lock() {
let env = Env::default();
env.mock_all_auths();

let (_, _, _, admin_addr, _, _, client) = setup_funded_escrow(&env, vec![&env, 1_000_i128]);

let result = client.try_admin_override_cancel_release(&admin_addr);
assert_eq!(result, Err(Ok(Error::InvalidStatus)));
}

/// admin_override_cancel_release must fail with Unauthorized for non-admin.
#[test]
fn test_cancel_release_unauthorized_caller_rejected() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, _, _, _, client) = setup_funded_escrow(&env, vec![&env, 1_000_i128]);
client.cancel_escrow(&client_addr);

let attacker = Address::generate(&env);
let result = client.try_admin_override_cancel_release(&attacker);
assert_eq!(result, Err(Ok(Error::Unauthorized)));
}

/// YieldAccrued is reset to zero after a successful release.
#[test]
fn test_cancel_release_resets_yield_accrued() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, admin_addr, _, contract_id, client) =
setup_funded_escrow(&env, vec![&env, 5_000_i128]);
client.cancel_escrow(&client_addr);
client.admin_override_cancel_release(&admin_addr);

let yield_after: i128 = env.as_contract(&contract_id, || {
env.storage()
.persistent()
.get(&DataKey::YieldAccrued)
.unwrap_or(0_i128)
});
assert_eq!(yield_after, 0);
}

// ────────────────────────────────────────────────────────────────────────────
// Issue #386: admin_override_cancel_refund checked arithmetic
// ────────────────────────────────────────────────────────────────────────────

/// Happy path: cancel-locked escrow is fully refunded to the client with
/// correct token amounts and milestone state.
#[test]
fn test_cancel_refund_happy_path_produces_correct_amounts() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, admin_addr, token_id, _, client) =
setup_funded_escrow(&env, vec![&env, 4_000_i128, 6_000_i128]);

client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let client_before = token.balance(&client_addr);
let contract_before = token.balance(&client.address);

client.admin_override_cancel_refund(&admin_addr);

assert_eq!(token.balance(&client_addr), client_before + 10_000);
assert_eq!(token.balance(&client.address), contract_before - 10_000);

let job = client.get_job();
for idx in 0..2u32 {
let ms = job.milestones.get(idx).unwrap();
assert_eq!(ms.status, MilestoneStatus::Refunded);
assert_eq!(ms.released_amount, ms.amount);
}
}

/// Terminal milestones are skipped; only pending ones contribute to the refund.
#[test]
fn test_cancel_refund_skips_terminal_milestones() {
let env = Env::default();
env.mock_all_auths();

let amounts = vec![&env, 1_000_i128, 4_000_i128, 5_000_i128];
let (client_addr, freelancer_addr, _, admin_addr, token_id, _, client) =
setup_funded_escrow(&env, amounts);

// Release milestone 0 normally.
client.mark_delivered(&freelancer_addr, &0u32);
client.approve_milestone(&client_addr, &0u32);

client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let client_before = token.balance(&client_addr);

client.admin_override_cancel_refund(&admin_addr);

// Only milestones 1 and 2 refunded.
assert_eq!(token.balance(&client_addr), client_before + 9_000);
}

/// admin_override_cancel_refund must fail with InvalidStatus when no cancel
/// lock is active (no wrap or panic must occur).
#[test]
fn test_cancel_refund_requires_cancel_lock() {
let env = Env::default();
env.mock_all_auths();

let (_, _, _, admin_addr, _, _, client) = setup_funded_escrow(&env, vec![&env, 1_000_i128]);

let result = client.try_admin_override_cancel_refund(&admin_addr);
assert_eq!(result, Err(Ok(Error::InvalidStatus)));
}

/// admin_override_cancel_refund must fail with Unauthorized for non-admin;
/// no storage must be mutated.
#[test]
fn test_cancel_refund_unauthorized_caller_rejected() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, _, token_id, _, client) =
setup_funded_escrow(&env, vec![&env, 1_000_i128]);
client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let client_balance_before = token.balance(&client_addr);
let contract_balance_before = token.balance(&client.address);

let attacker = Address::generate(&env);
let result = client.try_admin_override_cancel_refund(&attacker);
assert_eq!(result, Err(Ok(Error::Unauthorized)));

assert_eq!(token.balance(&client_addr), client_balance_before);
assert_eq!(token.balance(&client.address), contract_balance_before);
}

/// If every milestone is already terminal the total refund is zero and the
/// function returns Error::InvalidAmount rather than panicking. This validates
/// the checked arithmetic boundary (issue #386).
#[test]
fn test_cancel_refund_all_terminal_returns_invalid_amount() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, freelancer_addr, _, admin_addr, token_id, contract_id, client) =
setup_funded_escrow(&env, vec![&env, 2_000_i128]);

// Fully release the single milestone via normal path.
client.mark_delivered(&freelancer_addr, &0u32);
client.approve_milestone(&client_addr, &0u32);

// Top up the contract so the zero-balance boundary guard in
// `cancel_escrow` still lets a cancel go through even though every
// milestone is now terminal and fully released.
let token_admin = token::StellarAssetClient::new(&env, &token_id);
token_admin.mint(&contract_id, &1_000_i128);

client.cancel_escrow(&client_addr);

let result = client.try_admin_override_cancel_refund(&admin_addr);
assert_eq!(result, Err(Ok(Error::InvalidAmount)));
}

/// YieldAccrued is reset to zero after a successful refund.
#[test]
fn test_cancel_refund_resets_yield_accrued() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, admin_addr, _, contract_id, client) =
setup_funded_escrow(&env, vec![&env, 5_000_i128]);
client.cancel_escrow(&client_addr);
client.admin_override_cancel_refund(&admin_addr);

let yield_after: i128 = env.as_contract(&contract_id, || {
env.storage()
.persistent()
.get(&DataKey::YieldAccrued)
.unwrap_or(0_i128)
});
assert_eq!(yield_after, 0);
}

/// CancelLock is cleared after a successful refund.
#[test]
fn test_cancel_refund_clears_cancel_lock() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, admin_addr, _, contract_id, client) =
setup_funded_escrow(&env, vec![&env, 1_000_i128]);
client.cancel_escrow(&client_addr);
client.admin_override_cancel_refund(&admin_addr);

let locked: bool = env.as_contract(&contract_id, || {
env.storage()
.instance()
.get(&DataKey::CancelLock)
.unwrap_or(false)
});
assert!(!locked);
}

/// Multiple pending milestones accumulate through checked_add correctly;
/// the sum equals the total of all milestone amounts (issue #386).
#[test]
fn test_cancel_refund_multiple_milestones_sum_correctly() {
let env = Env::default();
env.mock_all_auths();

let amounts = vec![&env, 100_i128, 200_i128, 300_i128, 400_i128];
let (client_addr, _, _, admin_addr, token_id, _, client) = setup_funded_escrow(&env, amounts);

client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let client_before = token.balance(&client_addr);

client.admin_override_cancel_refund(&admin_addr);

assert_eq!(token.balance(&client_addr), client_before + 1_000);
}

/// Single-milestone refund with minimum valid amount (1 stroop) succeeds
/// without any arithmetic error; confirms checked path handles small positive
/// values correctly (issue #386).
#[test]
fn test_cancel_refund_minimum_valid_amount() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, _, _, admin_addr, token_id, _, client) =
setup_funded_escrow(&env, vec![&env, 1_i128]);

client.cancel_escrow(&client_addr);

let token = token::Client::new(&env, &token_id);
let client_before = token.balance(&client_addr);

client.admin_override_cancel_refund(&admin_addr);

assert_eq!(token.balance(&client_addr), client_before + 1);
}
Loading
Loading