Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 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
61a1ea3
fix(tax_withholding_deductions): reject Disputed milestones (#298)
larryjay007 Aug 27, 2026
1c9340a
Fix: reduce ledger storage footprint of admin_override_cancel_refund
zinodict121 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
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
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

26 changes: 15 additions & 11 deletions contracts/milestone-escrow/src/admin_override_cancel_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
//! - 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::{token, vec, Address, Env};
use soroban_sdk::{testutils::Address as _, token, vec, Address, Env};

// ────────────────────────────────────────────────────────────────────────────
// Issue #383: admin_override_cancel_release storage footprint
Expand Down Expand Up @@ -118,8 +121,7 @@ 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 (_, _, _, 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)));
Expand All @@ -131,8 +133,7 @@ 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]);
let (client_addr, _, _, _, _, _, client) = setup_funded_escrow(&env, vec![&env, 1_000_i128]);
client.cancel_escrow(&client_addr);

let attacker = Address::generate(&env);
Expand Down Expand Up @@ -225,8 +226,7 @@ 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 (_, _, _, 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)));
Expand Down Expand Up @@ -263,13 +263,19 @@ fn test_cancel_refund_all_terminal_returns_invalid_amount() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, freelancer_addr, _, admin_addr, _, _, client) =
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);
Expand Down Expand Up @@ -324,8 +330,7 @@ fn test_cancel_refund_multiple_milestones_sum_correctly() {
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);
let (client_addr, _, _, admin_addr, token_id, _, client) = setup_funded_escrow(&env, amounts);

client.cancel_escrow(&client_addr);

Expand Down Expand Up @@ -357,4 +362,3 @@ fn test_cancel_refund_minimum_valid_amount() {

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

145 changes: 145 additions & 0 deletions contracts/milestone-escrow/src/admin_override_refund_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
//! Authorization and source-state guards for `admin_override_refund`
//! (issue #329).
//!
//! An unauthorized caller and an illegal (paused) source state must each
//! return their specific typed error, and neither path may mutate storage.

use super::*;
use crate::{Error, MilestoneStatus};
use soroban_sdk::token;
use soroban_sdk::{symbol_short, vec, Address, Env, IntoVal, Val};
use crate::test::setup_funded_escrow;

fn refund_event_count(env: &Env) -> u32 {
let topic_val: Val = symbol_short!("admovrf").into_val(env);
let mut count = 0u32;
for event in env.events().all().iter() {
if let Some(topic) = event.1.get(0) {
if topic.get_payload() == topic_val.get_payload() {
count += 1;
}
}
}
count
}

struct RefundLedgerSnapshot {
paused: bool,
status: MilestoneStatus,
released_amount: i128,
client_balance: i128,
contract_balance: i128,
}

fn snapshot_refund_ledger(
env: &Env,
client: &MilestoneEscrowClient,
token_id: &Address,
client_addr: &Address,
) -> RefundLedgerSnapshot {
let job = client.get_job();
let milestone = job.milestones.get(0).unwrap();
let token = token::Client::new(env, token_id);
let paused = env.as_contract(&client.address, || {
env.storage()
.instance()
.get::<_, bool>(&crate::DataKey::Paused)
.unwrap_or(false)
});
RefundLedgerSnapshot {
paused,
status: milestone.status,
released_amount: milestone.released_amount,
client_balance: token.balance(client_addr),
contract_balance: token.balance(&client.address),
}
}

fn assert_ledger_unchanged(before: &RefundLedgerSnapshot, after: &RefundLedgerSnapshot) {
assert_eq!(after.paused, before.paused);
assert_eq!(after.status, before.status);
assert_eq!(after.released_amount, before.released_amount);
assert_eq!(after.client_balance, before.client_balance);
assert_eq!(after.contract_balance, before.contract_balance);
}

#[test]
fn unauthorized_caller_returns_unauthorized_and_does_not_mutate_storage() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert!(!before.paused);
assert_eq!(before.status, MilestoneStatus::Pending);
assert_eq!(before.released_amount, 0);

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

let after = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert_ledger_unchanged(&before, &after);
assert_eq!(refund_event_count(&env), 0);
}

#[test]
fn illegal_paused_source_state_returns_paused_and_does_not_mutate_storage() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert!(before.paused);
assert_eq!(before.status, MilestoneStatus::Pending);
assert_eq!(before.released_amount, 0);

let result = client.try_admin_override_refund(&admin_addr, &0u32);
assert_eq!(result, Err(Ok(Error::Paused)));

let after = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert_ledger_unchanged(&before, &after);
assert!(after.paused);
assert_eq!(refund_event_count(&env), 0);
}

#[test]
fn client_freelancer_and_arbiter_are_unauthorized_without_storage_mutation() {
let env = Env::default();
env.mock_all_auths();

let (client_addr, freelancer_addr, arbiter_addr, _admin_addr, token_id, _, client) =
setup_funded_escrow(&env, vec![&env, 1_000_i128]);

let before = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
for caller in [client_addr.clone(), freelancer_addr, arbiter_addr] {
let result = client.try_admin_override_refund(&caller, &0u32);
assert_eq!(result, Err(Ok(Error::Unauthorized)));
let after = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert_ledger_unchanged(&before, &after);
}
assert_eq!(refund_event_count(&env), 0);
}

#[test]
fn admin_override_refund_refunds_client() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
client.admin_override_refund(&admin_addr, &0u32);

let after = snapshot_refund_ledger(&env, &client, &token_id, &client_addr);
assert_eq!(after.status, MilestoneStatus::Refunded);
assert_eq!(after.released_amount, 1_000);
assert_eq!(after.client_balance, before.client_balance + 1_000);
assert_eq!(after.contract_balance, before.contract_balance - 1_000);
}
136 changes: 136 additions & 0 deletions contracts/milestone-escrow/src/admin_override_release_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! Authorization and source-state guards for `admin_override_release`
//! (issue #328).
//!
//! An unauthorized caller and an illegal (terminal) source state must each
//! return their specific typed error, and neither path may mutate storage.

use super::*;
use crate::{Error, MilestoneStatus};
use soroban_sdk::token;
use soroban_sdk::{symbol_short, vec, Address, Env, IntoVal, Val};

fn release_event_count(env: &Env) -> u32 {
let topic_val: Val = symbol_short!("admovrls").into_val(env);
let mut count = 0u32;
for event in env.events().all().iter() {
if let Some(topic) = event.1.get(0) {
if topic.get_payload() == topic_val.get_payload() {
count += 1;
}
}
}
count
}

struct ReleaseLedgerSnapshot {
status: MilestoneStatus,
released_amount: i128,
freelancer_balance: i128,
contract_balance: i128,
}

fn snapshot_release_ledger(
env: &Env,
client: &MilestoneEscrowClient,
token_id: &Address,
freelancer_addr: &Address,
) -> ReleaseLedgerSnapshot {
let job = client.get_job();
let milestone = job.milestones.get(0).unwrap();
let token = token::Client::new(env, token_id);
ReleaseLedgerSnapshot {
status: milestone.status,
released_amount: milestone.released_amount,
freelancer_balance: token.balance(freelancer_addr),
contract_balance: token.balance(&client.address),
}
}

fn assert_ledger_unchanged(before: &ReleaseLedgerSnapshot, after: &ReleaseLedgerSnapshot) {
assert_eq!(after.status, before.status);
assert_eq!(after.released_amount, before.released_amount);
assert_eq!(after.freelancer_balance, before.freelancer_balance);
assert_eq!(after.contract_balance, before.contract_balance);
}

#[test]
fn unauthorized_caller_returns_unauthorized_and_does_not_mutate_storage() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_eq!(before.status, MilestoneStatus::Pending);
assert_eq!(before.released_amount, 0);

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

let after = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_ledger_unchanged(&before, &after);
assert_eq!(release_event_count(&env), 0);
}

#[test]
fn illegal_terminal_source_state_returns_invalid_status_and_does_not_mutate_storage() {
let env = Env::default();
env.mock_all_auths();

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

// Fully release the milestone first so the source state is terminal.
client.mark_delivered(&freelancer_addr, &0u32);
client.approve_milestone(&client_addr, &0u32);

let before = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_eq!(before.status, MilestoneStatus::Released);
assert_eq!(before.released_amount, 1_000);

let result = client.try_admin_override_release(&admin_addr, &0u32);
assert_eq!(result, Err(Ok(Error::InvalidStatus)));

let after = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_ledger_unchanged(&before, &after);
assert_eq!(release_event_count(&env), 0);
}

#[test]
fn client_freelancer_and_arbiter_are_unauthorized_without_storage_mutation() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
for caller in [client_addr.clone(), freelancer_addr.clone(), arbiter_addr] {
let result = client.try_admin_override_release(&caller, &0u32);
assert_eq!(result, Err(Ok(Error::Unauthorized)));
let after = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_ledger_unchanged(&before, &after);
}
assert_eq!(release_event_count(&env), 0);
}

#[test]
fn admin_override_release_releases_funds_to_freelancer() {
let env = Env::default();
env.mock_all_auths();

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

let before = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
client.admin_override_release(&admin_addr, &0u32);

let after = snapshot_release_ledger(&env, &client, &token_id, &freelancer_addr);
assert_eq!(after.status, MilestoneStatus::Released);
assert_eq!(after.released_amount, 1_000);
assert_eq!(after.freelancer_balance, before.freelancer_balance + 1_000);
assert_eq!(after.contract_balance, before.contract_balance - 1_000);
assert_eq!(release_event_count(&env), 1);
}
Loading
Loading