Skip to content

Commit 4886b5f

Browse files
authored
Merge pull request #118 from tx-cyber/main
feat: trigger escrow freeze on dispute and implement arbitrator resolution controls
2 parents 5ec1d86 + 4fbae54 commit 4886b5f

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

contracts/dispute_resolution_contract/lib.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ impl DisputeResolutionContract {
134134
}
135135
}
136136

137+
let escrow_addr = Self::get_escrow_contract(env.clone());
138+
let escrow_client = escrow_contract::EscrowContractClient::new(&env, &escrow_addr);
139+
escrow_client.freeze_funds(&u64::from(delivery_id));
140+
137141
let dispute_key = DataKey::Dispute(delivery_id);
138142
if env.storage().persistent().has(&dispute_key) {
139143
panic_with_error!(&env, SwiftChainError::DuplicateDelivery);
@@ -284,6 +288,47 @@ impl DisputeResolutionContract {
284288
);
285289
}
286290

291+
pub fn resolve_dispute_pay_driver(env: Env, caller: Address, delivery_id: DeliveryId) {
292+
caller.require_auth();
293+
if !Self::is_admin(env.clone(), caller.clone()) {
294+
panic_with_error!(&env, SwiftChainError::Unauthorized);
295+
}
296+
297+
let dispute_key = DataKey::Dispute(delivery_id);
298+
let mut dispute: DisputeCase = env
299+
.storage()
300+
.persistent()
301+
.get(&dispute_key)
302+
.unwrap_or_else(|| panic_with_error!(&env, SwiftChainError::DeliveryNotFound));
303+
304+
if dispute.status != DisputeStatus::Open {
305+
panic_with_error!(&env, SwiftChainError::InvalidState);
306+
}
307+
308+
dispute.status = DisputeStatus::ResolvedPayout;
309+
env.storage().persistent().set(&dispute_key, &dispute);
310+
env.storage().persistent().extend_ttl(&dispute_key, 518400, 518400);
311+
312+
let escrow_addr = Self::get_escrow_contract(env.clone());
313+
314+
use soroban_sdk::IntoVal;
315+
let _: () = env.invoke_contract(
316+
&escrow_addr,
317+
&Symbol::new(&env, "resolve_dispute"),
318+
soroban_sdk::vec![
319+
&env,
320+
caller.into_val(&env),
321+
u64::from(delivery_id).into_val(&env),
322+
true.into_val(&env),
323+
],
324+
);
325+
326+
env.events().publish(
327+
(Symbol::new(&env, "dispute_resolved_payout"), delivery_id),
328+
(caller, delivery_id),
329+
);
330+
}
331+
287332
pub fn get_dispute(env: Env, delivery_id: DeliveryId) -> DisputeCase {
288333
let dispute_key = DataKey::Dispute(delivery_id);
289334
env.storage()

contracts/dispute_resolution_contract/test.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,15 @@ impl MockEscrowContract {
7777
env.storage().instance().set(&delivery_id, &record);
7878
}
7979
}
80+
81+
pub fn freeze_funds(env: Env, delivery_id: u64) {
82+
if env.storage().instance().has(&delivery_id) {
83+
let mut record: shared_types::EscrowRecord =
84+
env.storage().instance().get(&delivery_id).unwrap();
85+
record.status = shared_types::EscrowStatus::Paused;
86+
env.storage().instance().set(&delivery_id, &record);
87+
}
88+
}
8089
}
8190

8291
fn setup_test() -> (
@@ -250,6 +259,10 @@ fn test_raise_dispute_active_delivery() {
250259
let delivery = MockDeliveryContractClient::new(&env, &delivery_id).get_delivery(&did(1));
251260
assert_eq!(delivery.status, delivery_contract::DeliveryStatus::Disputed);
252261

262+
// Verify escrow status changed to Paused in MockEscrowContract
263+
let escrow = MockEscrowContractClient::new(&env, &escrow_id).get_escrow(&1);
264+
assert_eq!(escrow.status, shared_types::EscrowStatus::Paused);
265+
253266
// Verify local dispute case in DisputeResolutionContract
254267
let case = dispute_client.get_dispute(&did(1));
255268
assert_eq!(case.delivery_id, did(1));
@@ -472,3 +485,49 @@ fn test_integration_resolve_dispute_split_funds() {
472485
assert_eq!(sender_balance, 600); // 60% of 1000 refunded
473486
assert_eq!(driver_balance, 400); // 40% of 1000 paid to driver
474487
}
488+
489+
#[test]
490+
fn test_resolve_dispute_pay_driver_by_admin() {
491+
let (env, admin, sender, recipient, driver, delivery_id, escrow_id, dispute_client) = setup_test();
492+
493+
// Setup mock delivery
494+
let delivery_record = create_mock_delivery_record(&env, did(8), sender.clone(), recipient.clone(), delivery_contract::DeliveryStatus::Active, None);
495+
set_mock_delivery(&env, &delivery_id, did(8), &delivery_record);
496+
497+
// Setup mock escrow as Paused (representing escrow paused after dispute raised)
498+
let token = Address::generate(&env);
499+
let escrow_record = create_mock_escrow_record(sender.clone(), recipient.clone(), driver.clone(), token, shared_types::EscrowStatus::Paused);
500+
set_mock_escrow(&env, &escrow_id, 8, &escrow_record);
501+
502+
// Raise dispute to initialize local dispute case
503+
dispute_client.raise_dispute(&sender, &did(8));
504+
505+
// Resolve dispute
506+
dispute_client.resolve_dispute_pay_driver(&admin, &did(8));
507+
508+
// Verify local dispute status is ResolvedPayout
509+
let case = dispute_client.get_dispute(&did(8));
510+
assert_eq!(case.status, DisputeStatus::ResolvedPayout);
511+
512+
// Verify mock escrow status updated to Released
513+
let escrow = MockEscrowContractClient::new(&env, &escrow_id).get_escrow(&8);
514+
assert_eq!(escrow.status, shared_types::EscrowStatus::Released);
515+
}
516+
517+
#[test]
518+
#[should_panic(expected = "HostError: Error(Contract, #1)")] // SwiftChainError::Unauthorized
519+
fn test_unauthorized_resolve_pay_driver_fails() {
520+
let (env, _admin, sender, recipient, driver, delivery_id, escrow_id, dispute_client) = setup_test();
521+
522+
let delivery_record = create_mock_delivery_record(&env, did(9), sender.clone(), recipient.clone(), delivery_contract::DeliveryStatus::Active, None);
523+
set_mock_delivery(&env, &delivery_id, did(9), &delivery_record);
524+
525+
let token = Address::generate(&env);
526+
let escrow_record = create_mock_escrow_record(sender.clone(), recipient.clone(), driver.clone(), token, shared_types::EscrowStatus::Paused);
527+
set_mock_escrow(&env, &escrow_id, 9, &escrow_record);
528+
529+
dispute_client.raise_dispute(&sender, &did(9));
530+
531+
// Attacker (sender) tries to resolve dispute pay driver
532+
dispute_client.resolve_dispute_pay_driver(&sender, &did(9));
533+
}

contracts/escrow_contract/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,15 @@ impl EscrowContract {
495495
}
496496
load_escrow(&env, delivery_id)
497497
}
498+
499+
pub fn freeze_funds(env: Env, delivery_id: u64) {
500+
let mut record = load_escrow(&env, delivery_id);
501+
if record.status == EscrowStatus::Locked {
502+
record.status = EscrowStatus::Paused;
503+
record.disputed_at = Some(env.ledger().timestamp());
504+
save_escrow(&env, delivery_id, &record);
505+
}
506+
}
498507
}
499508

500509
#[cfg(test)]

0 commit comments

Comments
 (0)