From 80d0adefcefb7737c6981ca5d7e7efe93382e84b Mon Sep 17 00:00:00 2001 From: KingFRANKHOOD Date: Tue, 27 Jan 2026 12:49:59 +0100 Subject: [PATCH 1/2] feat: implement dispute resolution system --- apps/onchain/src/lib.rs | 146 +++++- apps/onchain/src/test.rs | 131 ++++- ...admin_resolves_dispute_to_depositor.1.json | 474 ++++++++++++++++++ ...admin_resolves_dispute_to_recipient.1.json | 474 ++++++++++++++++++ .../test/test_cancel_escrow.1.json | 12 + .../test/test_complete_escrow.1.json | 12 + .../test/test_create_and_get_escrow.1.json | 12 + .../test/test_dispute_blocks_release.1.json | 310 ++++++++++++ .../test/test_double_release.1.json | 12 + .../test/test_duplicate_escrow_id.1.json | 12 + .../test/test_release_milestone.1.json | 12 + 11 files changed, 1605 insertions(+), 2 deletions(-) create mode 100644 apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_depositor.1.json create mode 100644 apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_recipient.1.json create mode 100644 apps/onchain/test_snapshots/test/test_dispute_blocks_release.1.json diff --git a/apps/onchain/src/lib.rs b/apps/onchain/src/lib.rs index bcd0057..6d23a13 100644 --- a/apps/onchain/src/lib.rs +++ b/apps/onchain/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] use soroban_sdk::{ - Address, Env, Symbol, Vec, contract, contracterror, contractimpl, contracttype, symbol_short, + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, Symbol, Vec, }; // Milestone status tracking @@ -28,6 +28,17 @@ pub enum EscrowStatus { Active, Completed, Cancelled, + Disputed, + Resolved, +} + +// Dispute resolution outcome +#[contracttype] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Resolution { + None, + Depositor, + Recipient, } // Main escrow structure @@ -40,6 +51,7 @@ pub struct Escrow { pub total_released: i128, pub milestones: Vec, pub status: EscrowStatus, + pub resolution: Resolution, } // Contract error types @@ -56,6 +68,11 @@ pub enum Error { InsufficientBalance = 8, EscrowNotActive = 9, VectorTooLarge = 10, + AdminNotInitialized = 11, + AlreadyInitialized = 12, + InvalidEscrowStatus = 13, + AlreadyInDispute = 14, + InvalidWinner = 15, } #[contract] @@ -63,6 +80,17 @@ pub struct VaultixEscrow; #[contractimpl] impl VaultixEscrow { + /// Initializes the contract with an admin address responsible for dispute resolution. + pub fn init(env: Env, admin: Address) -> Result<(), Error> { + if env.storage().persistent().has(&admin_storage_key()) { + return Err(Error::AlreadyInitialized); + } + + admin.require_auth(); + env.storage().persistent().set(&admin_storage_key(), &admin); + Ok(()) + } + /// Creates a new escrow with milestone-based payment releases. /// /// # Arguments @@ -110,6 +138,7 @@ impl VaultixEscrow { total_released: 0, milestones: initialized_milestones, status: EscrowStatus::Active, + resolution: Resolution::None, }; // Save to persistent storage @@ -180,6 +209,102 @@ impl VaultixEscrow { Ok(()) } + /// Raises a dispute on an active escrow. Either party (depositor or recipient) may invoke this. + pub fn raise_dispute(env: Env, escrow_id: u64, caller: Address) -> Result<(), Error> { + let storage_key = get_storage_key(escrow_id); + + let mut escrow: Escrow = env + .storage() + .persistent() + .get(&storage_key) + .ok_or(Error::EscrowNotFound)?; + + if caller != escrow.depositor && caller != escrow.recipient { + return Err(Error::UnauthorizedAccess); + } + caller.require_auth(); + + if escrow.status == EscrowStatus::Disputed { + return Err(Error::AlreadyInDispute); + } + if escrow.status != EscrowStatus::Active { + return Err(Error::InvalidEscrowStatus); + } + + // Mark pending milestones as disputed to freeze further releases. + let mut updated_milestones = Vec::new(&env); + for milestone in escrow.milestones.iter() { + let mut m = milestone.clone(); + if m.status == MilestoneStatus::Pending { + m.status = MilestoneStatus::Disputed; + } + updated_milestones.push_back(m); + } + + escrow.milestones = updated_milestones; + escrow.status = EscrowStatus::Disputed; + escrow.resolution = Resolution::None; + env.storage().persistent().set(&storage_key, &escrow); + + Ok(()) + } + + /// Resolves an active dispute by directing funds to the chosen party. Only the admin may call this. + pub fn resolve_dispute(env: Env, escrow_id: u64, winner: Address) -> Result<(), Error> { + let admin = get_admin(&env)?; + admin.require_auth(); + + let storage_key = get_storage_key(escrow_id); + + let mut escrow: Escrow = env + .storage() + .persistent() + .get(&storage_key) + .ok_or(Error::EscrowNotFound)?; + + if escrow.status != EscrowStatus::Disputed { + return Err(Error::InvalidEscrowStatus); + } + + // Winner must be one of the parties + if winner != escrow.depositor && winner != escrow.recipient { + return Err(Error::InvalidWinner); + } + + // Release or refund remaining funds based on winner + if winner == escrow.recipient { + // Force release of all pending/disputed milestones + let mut updated_milestones = Vec::new(&env); + for milestone in escrow.milestones.iter() { + let mut m = milestone.clone(); + if m.status != MilestoneStatus::Released { + m.status = MilestoneStatus::Released; + } + updated_milestones.push_back(m); + } + escrow.milestones = updated_milestones; + escrow.total_released = escrow.total_amount; + escrow.resolution = Resolution::Recipient; + } else { + // Refund remaining funds to depositor; keep already released milestones as-is + let mut updated_milestones = Vec::new(&env); + for milestone in escrow.milestones.iter() { + let mut m = milestone.clone(); + if m.status == MilestoneStatus::Pending || m.status == MilestoneStatus::Disputed { + m.status = MilestoneStatus::Disputed; + } + updated_milestones.push_back(m); + } + escrow.milestones = updated_milestones; + escrow.resolution = Resolution::Depositor; + } + + escrow.status = EscrowStatus::Resolved; + env.storage().persistent().set(&storage_key, &escrow); + + Ok(()) + } + /// Retrieves escrow details. /// /// # Arguments @@ -219,6 +344,10 @@ impl VaultixEscrow { // Verify authorization escrow.depositor.require_auth(); + if escrow.status != EscrowStatus::Active { + return Err(Error::InvalidEscrowStatus); + } + // Verify no milestones have been released if escrow.total_released > 0 { return Err(Error::MilestoneAlreadyReleased); @@ -252,6 +381,10 @@ impl VaultixEscrow { // Verify authorization escrow.depositor.require_auth(); + if escrow.status != EscrowStatus::Active { + return Err(Error::InvalidEscrowStatus); + } + // Verify all milestones are released if !verify_all_released(&escrow.milestones) { return Err(Error::EscrowNotActive); @@ -270,6 +403,17 @@ fn get_storage_key(escrow_id: u64) -> (Symbol, u64) { (symbol_short!("escrow"), escrow_id) } +fn admin_storage_key() -> Symbol { + symbol_short!("admin") +} + +fn get_admin(env: &Env) -> Result { + env.storage() + .persistent() + .get(&admin_storage_key()) + .ok_or(Error::AdminNotInitialized) +} + // Validates milestone vector and returns total amount fn validate_milestones(milestones: &Vec) -> Result { // Check vector size to prevent gas issues diff --git a/apps/onchain/src/test.rs b/apps/onchain/src/test.rs index c140a50..eccc155 100644 --- a/apps/onchain/src/test.rs +++ b/apps/onchain/src/test.rs @@ -1,5 +1,5 @@ use super::*; -use soroban_sdk::{Address, Env, testutils::Address as _, vec}; +use soroban_sdk::{testutils::Address as _, vec, Address, Env}; #[test] fn test_create_and_get_escrow() { @@ -89,6 +89,40 @@ fn test_release_milestone() { ); } +#[test] +#[should_panic(expected = "Error(Contract, #9)")] +fn test_dispute_blocks_release() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(VaultixEscrow, ()); + let client = VaultixEscrowClient::new(&env, &contract_id); + + let depositor = Address::generate(&env); + let recipient = Address::generate(&env); + let escrow_id = 9u64; + + let milestones = vec![ + &env, + Milestone { + amount: 500, + status: MilestoneStatus::Pending, + description: symbol_short!("Task"), + }, + ]; + + client.create_escrow(&escrow_id, &depositor, &recipient, &milestones); + + // Either party can raise dispute; use depositor as caller. + client.raise_dispute(&escrow_id, &depositor); + + let escrow = client.get_escrow(&escrow_id); + assert_eq!(escrow.status, EscrowStatus::Disputed); + + // This should panic with Error #9 (EscrowNotActive) + client.release_milestone(&escrow_id, &0); +} + #[test] fn test_complete_escrow() { let env = Env::default(); @@ -159,6 +193,101 @@ fn test_cancel_escrow() { assert_eq!(escrow.status, EscrowStatus::Cancelled); } +#[test] +fn test_admin_resolves_dispute_to_recipient() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(VaultixEscrow, ()); + let client = VaultixEscrowClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let depositor = Address::generate(&env); + let recipient = Address::generate(&env); + let escrow_id = 10u64; + + client.init(&admin); + + let milestones = vec![ + &env, + Milestone { + amount: 4000, + status: MilestoneStatus::Pending, + description: symbol_short!("Phase1"), + }, + Milestone { + amount: 6000, + status: MilestoneStatus::Pending, + description: symbol_short!("Phase2"), + }, + ]; + + client.create_escrow(&escrow_id, &depositor, &recipient, &milestones); + + // Raise dispute mid-project + client.raise_dispute(&escrow_id, &recipient); + + // Admin resolves in favor of recipient (force payout) + client.resolve_dispute(&escrow_id, &recipient); + + let escrow = client.get_escrow(&escrow_id); + assert_eq!(escrow.status, EscrowStatus::Resolved); + assert_eq!(escrow.resolution, Resolution::Recipient); + assert_eq!(escrow.total_released, escrow.total_amount); + assert!(escrow + .milestones + .iter() + .all(|m| m.status == MilestoneStatus::Released)); +} + +#[test] +fn test_admin_resolves_dispute_to_depositor() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(VaultixEscrow, ()); + let client = VaultixEscrowClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let depositor = Address::generate(&env); + let recipient = Address::generate(&env); + let escrow_id = 11u64; + + client.init(&admin); + + let milestones = vec![ + &env, + Milestone { + amount: 2000, + status: MilestoneStatus::Pending, + description: symbol_short!("Alpha"), + }, + Milestone { + amount: 3000, + status: MilestoneStatus::Pending, + description: symbol_short!("Beta"), + }, + ]; + + client.create_escrow(&escrow_id, &depositor, &recipient, &milestones); + + // Raise dispute as depositor + client.raise_dispute(&escrow_id, &depositor); + + // Admin rules in favor of depositor (refund remaining funds) + client.resolve_dispute(&escrow_id, &depositor); + + let escrow = client.get_escrow(&escrow_id); + assert_eq!(escrow.status, EscrowStatus::Resolved); + assert_eq!(escrow.resolution, Resolution::Depositor); + // No additional releases occurred + assert_eq!(escrow.total_released, 0); + assert!(escrow + .milestones + .iter() + .all(|m| m.status == MilestoneStatus::Disputed)); +} + #[test] #[should_panic(expected = "Error(Contract, #2)")] fn test_duplicate_escrow_id() { diff --git a/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_depositor.1.json b/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_depositor.1.json new file mode 100644 index 0000000..bcdeb9e --- /dev/null +++ b/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_depositor.1.json @@ -0,0 +1,474 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_escrow", + "args": [ + { + "u64": "11" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "2000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Alpha" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "3000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Beta" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "raise_dispute", + "args": [ + { + "u64": "11" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "resolve_dispute", + "args": [ + { + "u64": "11" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "symbol": "admin" + }, + "durability": "persistent", + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "escrow" + }, + { + "u64": "11" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "depositor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "milestones" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "2000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Alpha" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "3000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Beta" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "recipient" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "Depositor" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Resolved" + } + ] + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": "5000" + } + }, + { + "key": { + "symbol": "total_released" + }, + "val": { + "i128": "0" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_recipient.1.json b/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_recipient.1.json new file mode 100644 index 0000000..c6f54cd --- /dev/null +++ b/apps/onchain/test_snapshots/test/test_admin_resolves_dispute_to_recipient.1.json @@ -0,0 +1,474 @@ +{ + "generators": { + "address": 4, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "init", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_escrow", + "args": [ + { + "u64": "10" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "4000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Phase1" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "6000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Phase2" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "raise_dispute", + "args": [ + { + "u64": "10" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "resolve_dispute", + "args": [ + { + "u64": "10" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "symbol": "admin" + }, + "durability": "persistent", + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "escrow" + }, + { + "u64": "10" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "depositor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "milestones" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "4000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Phase1" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Released" + } + ] + } + } + ] + }, + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "6000" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Phase2" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Released" + } + ] + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "recipient" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "Recipient" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Resolved" + } + ] + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "total_released" + }, + "val": { + "i128": "10000" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/apps/onchain/test_snapshots/test/test_cancel_escrow.1.json b/apps/onchain/test_snapshots/test/test_cancel_escrow.1.json index 5eb106b..ab02b4e 100644 --- a/apps/onchain/test_snapshots/test/test_cancel_escrow.1.json +++ b/apps/onchain/test_snapshots/test/test_cancel_escrow.1.json @@ -175,6 +175,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" diff --git a/apps/onchain/test_snapshots/test/test_complete_escrow.1.json b/apps/onchain/test_snapshots/test/test_complete_escrow.1.json index d0e2931..8d5deec 100644 --- a/apps/onchain/test_snapshots/test/test_complete_escrow.1.json +++ b/apps/onchain/test_snapshots/test/test_complete_escrow.1.json @@ -283,6 +283,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" diff --git a/apps/onchain/test_snapshots/test/test_create_and_get_escrow.1.json b/apps/onchain/test_snapshots/test/test_create_and_get_escrow.1.json index 76342bd..395c12c 100644 --- a/apps/onchain/test_snapshots/test/test_create_and_get_escrow.1.json +++ b/apps/onchain/test_snapshots/test/test_create_and_get_escrow.1.json @@ -284,6 +284,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" diff --git a/apps/onchain/test_snapshots/test/test_dispute_blocks_release.1.json b/apps/onchain/test_snapshots/test/test_dispute_blocks_release.1.json new file mode 100644 index 0000000..469b1d1 --- /dev/null +++ b/apps/onchain/test_snapshots/test/test_dispute_blocks_release.1.json @@ -0,0 +1,310 @@ +{ + "generators": { + "address": 3, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "create_escrow", + "args": [ + { + "u64": "9" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "500" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Task" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Pending" + } + ] + } + } + ] + } + ] + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "raise_dispute", + "args": [ + { + "u64": "9" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "vec": [ + { + "symbol": "escrow" + }, + { + "u64": "9" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "depositor" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "milestones" + }, + "val": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "500" + } + }, + { + "key": { + "symbol": "description" + }, + "val": { + "symbol": "Task" + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + } + ] + } + ] + } + }, + { + "key": { + "symbol": "recipient" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, + { + "key": { + "symbol": "status" + }, + "val": { + "vec": [ + { + "symbol": "Disputed" + } + ] + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": "500" + } + }, + { + "key": { + "symbol": "total_released" + }, + "val": { + "i128": "0" + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/apps/onchain/test_snapshots/test/test_double_release.1.json b/apps/onchain/test_snapshots/test/test_double_release.1.json index 2597829..744503b 100644 --- a/apps/onchain/test_snapshots/test/test_double_release.1.json +++ b/apps/onchain/test_snapshots/test/test_double_release.1.json @@ -178,6 +178,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" diff --git a/apps/onchain/test_snapshots/test/test_duplicate_escrow_id.1.json b/apps/onchain/test_snapshots/test/test_duplicate_escrow_id.1.json index 81bc9b4..ab23f39 100644 --- a/apps/onchain/test_snapshots/test/test_duplicate_escrow_id.1.json +++ b/apps/onchain/test_snapshots/test/test_duplicate_escrow_id.1.json @@ -156,6 +156,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" diff --git a/apps/onchain/test_snapshots/test/test_release_milestone.1.json b/apps/onchain/test_snapshots/test/test_release_milestone.1.json index ffab168..857b243 100644 --- a/apps/onchain/test_snapshots/test/test_release_milestone.1.json +++ b/apps/onchain/test_snapshots/test/test_release_milestone.1.json @@ -242,6 +242,18 @@ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" } }, + { + "key": { + "symbol": "resolution" + }, + "val": { + "vec": [ + { + "symbol": "None" + } + ] + } + }, { "key": { "symbol": "status" From 3ebc2c7b20a2b7cab7452921ad6549bd9dd4469f Mon Sep 17 00:00:00 2001 From: KingFRANKHOOD Date: Fri, 30 Jan 2026 18:56:25 +0100 Subject: [PATCH 2/2] fix: resolve formatting errors --- apps/onchain/src/lib.rs | 4 ++-- apps/onchain/src/test.rs | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/apps/onchain/src/lib.rs b/apps/onchain/src/lib.rs index 6d28db5..7f92fee 100644 --- a/apps/onchain/src/lib.rs +++ b/apps/onchain/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, symbol_short, token, Address, Env, Symbol, - Vec, + Address, Env, Symbol, Vec, contract, contracterror, contractimpl, contracttype, symbol_short, + token, }; #[contracttype] diff --git a/apps/onchain/src/test.rs b/apps/onchain/src/test.rs index 4760f1f..5a19185 100644 --- a/apps/onchain/src/test.rs +++ b/apps/onchain/src/test.rs @@ -1,5 +1,5 @@ use super::*; -use soroban_sdk::{testutils::Address as _, token, vec, Address, Env}; +use soroban_sdk::{Address, Env, testutils::Address as _, token, vec}; /// Helper function to create and initialize a test token /// Returns admin client for minting and the token address @@ -465,10 +465,12 @@ fn test_admin_resolves_dispute_to_recipient() { assert_eq!(escrow.status, EscrowStatus::Resolved); assert_eq!(escrow.resolution, Resolution::Recipient); assert_eq!(escrow.total_released, escrow.total_amount); - assert!(escrow - .milestones - .iter() - .all(|m| m.status == MilestoneStatus::Released)); + assert!( + escrow + .milestones + .iter() + .all(|m| m.status == MilestoneStatus::Released) + ); assert_eq!(token_client.balance(&recipient), 10000); assert_eq!(token_client.balance(&contract_id), 0); @@ -527,10 +529,12 @@ fn test_admin_resolves_dispute_to_depositor() { assert_eq!(escrow.status, EscrowStatus::Resolved); assert_eq!(escrow.resolution, Resolution::Depositor); assert_eq!(escrow.total_released, 0); - assert!(escrow - .milestones - .iter() - .all(|m| m.status == MilestoneStatus::Disputed)); + assert!( + escrow + .milestones + .iter() + .all(|m| m.status == MilestoneStatus::Disputed) + ); assert_eq!(token_client.balance(&depositor), 5000); assert_eq!(token_client.balance(&contract_id), 0);