diff --git a/backend/src/indexer/handlers.rs b/backend/src/indexer/handlers.rs index 3178757..0a0e488 100644 --- a/backend/src/indexer/handlers.rs +++ b/backend/src/indexer/handlers.rs @@ -205,7 +205,7 @@ impl EventHandler { let id = scval_to_u64(&args[0])?; let decision_raw = scval_to_u64(&args[1])?; - // DisputeDecision: 0=ReleaseToSeller, 1=RefundToLender + // DisputeDecision: 0=ReleaseToSeller, 1=RefundToBuyer let new_status = match decision_raw { 0 => "released", 1 => "refunded", diff --git a/contracts/escrow-manager/src/lib.rs b/contracts/escrow-manager/src/lib.rs index 6a2b612..1769853 100644 --- a/contracts/escrow-manager/src/lib.rs +++ b/contracts/escrow-manager/src/lib.rs @@ -6,6 +6,8 @@ #![no_std] +mod refund; + use soroban_sdk::{ contract, contractimpl, contracttype, symbol_short, token, Address, Bytes, Env, IntoVal, Symbol, Val, Vec, @@ -41,7 +43,8 @@ pub enum ContractError { ConsensusNotMet = 12, EscrowDisputed = 13, EscrowNotDisputed = 14, - NoPendingAdmin = 15, + InsufficientBalance = 15, + NoPendingAdmin = 16, } impl From for ContractError { @@ -107,7 +110,7 @@ pub struct Escrow { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DisputeDecision { ReleaseToSeller = 0, - RefundToLender = 1, + RefundToBuyer = 1, } /// Local mirror of OracleAdapter's ConfirmationData for cross-contract deserialization. @@ -638,10 +641,10 @@ impl EscrowManager { .set(&symbol_short!("test_rate"), &rate); } - /// Refund the escrowed funds to the lender if the escrow has expired. + /// Refund the escrowed funds to the buyer if the escrow has expired. /// /// Anyone can call this after expiry. Unlocks collateral and returns - /// funds to the lender. + /// funds to the buyer. pub fn refund_escrow(env: Env, escrow_id: u64) -> Result<(), ContractError> { let mut escrow: Escrow = env .storage() @@ -661,13 +664,8 @@ impl EscrowManager { return Err(ContractError::EscrowNotExpired); } - // Refund lender - let token_client = token::Client::new(&env, &escrow.asset); - token_client.transfer( - &env.current_contract_address(), - &escrow.lender, - &escrow.amount, - ); + // Refund using the new refund module + refund::process_refund(&env, &mut escrow, escrow_id)?; // Unlock collateral via CollateralRegistry let coll_reg: Address = env @@ -727,18 +725,12 @@ impl EscrowManager { .publish((symbol_short!("esc_rslv"),), (escrow_id, decision)); Ok(()) } - DisputeDecision::RefundToLender => { - // Refund lender - let token_client = token::Client::new(&env, &escrow.asset); - token_client.transfer( - &env.current_contract_address(), - &escrow.lender, - &escrow.amount, - ); + DisputeDecision::RefundToBuyer => { + // Refund using the new refund module + refund::process_refund(&env, &mut escrow, escrow_id)?; Self::unlock_collateral(&env, escrow.collateral_id)?; - escrow.status = EscrowStatus::Refunded; env.storage().persistent().set(&escrow_id, &escrow); env.events() @@ -1178,7 +1170,7 @@ mod test { let escrow_id = create_test_escrow(&t); let token = token::Client::new(&t.env, &t.token_addr); - let lender_balance_before = token.balance(&t.lender); + let buyer_balance_before = token.balance(&t.buyer); // Advance past expiry t.env.ledger().with_mut(|li| { @@ -1191,8 +1183,8 @@ mod test { let escrow = t.escrow_client.get_escrow(&escrow_id).unwrap(); assert_eq!(escrow.status, EscrowStatus::Refunded); - // Verify funds returned to lender - assert_eq!(token.balance(&t.lender), lender_balance_before + 5000); + // Verify funds returned to buyer + assert_eq!(token.balance(&t.buyer), buyer_balance_before + 5000); assert_eq!(token.balance(&t.escrow_id_addr), 0); // Verify collateral unlocked @@ -1325,26 +1317,26 @@ mod test { let escrow_id = create_test_escrow(&t); t.escrow_client - .resolve_dispute(&escrow_id, &DisputeDecision::RefundToLender); + .resolve_dispute(&escrow_id, &DisputeDecision::RefundToBuyer); } #[test] - fn test_resolve_dispute_refund_to_lender_success() { + fn test_resolve_dispute_refund_to_buyer_success() { let t = setup(); let escrow_id = create_test_escrow(&t); let token = token::Client::new(&t.env, &t.token_addr); - let lender_balance_before = token.balance(&t.lender); + let buyer_balance_before = token.balance(&t.buyer); let reason = Bytes::from_slice(&t.env, b"dispute"); t.escrow_client.raise_dispute(&escrow_id, &t.buyer, &reason); t.escrow_client - .resolve_dispute(&escrow_id, &DisputeDecision::RefundToLender); + .resolve_dispute(&escrow_id, &DisputeDecision::RefundToBuyer); let escrow = t.escrow_client.get_escrow(&escrow_id).unwrap(); assert_eq!(escrow.status, EscrowStatus::Refunded); - assert_eq!(token.balance(&t.lender), lender_balance_before + 5000); + assert_eq!(token.balance(&t.buyer), buyer_balance_before + 5000); assert_eq!(token.balance(&t.escrow_id_addr), 0); // Verify collateral unlocked diff --git a/contracts/escrow-manager/src/refund.rs b/contracts/escrow-manager/src/refund.rs new file mode 100644 index 0000000..0007319 --- /dev/null +++ b/contracts/escrow-manager/src/refund.rs @@ -0,0 +1,35 @@ +use crate::{ContractError, Escrow, EscrowStatus}; +use soroban_sdk::{token, Address, Env}; + +/// Process a refund for an escrow +/// +/// This function executes the actual token transfer from the contract +/// back to the buyer's address. It also updates the escrow status +/// and handles any error reporting. +pub fn process_refund( + env: &Env, + escrow: &mut Escrow, + _escrow_id: u64, +) -> Result<(), ContractError> { + // 1. Validate that the escrow has sufficient balance + let token_client = token::Client::new(env, &escrow.asset); + let contract_balance = token_client.balance(&env.current_contract_address()); + + if contract_balance < escrow.amount { + return Err(ContractError::InsufficientBalance); + } + + // 2. Execute the transfer back to the buyer + // Note: The original implementation transferred to the lender, + // but the requirement is to refund the buyer who funded it. + token_client.transfer( + &env.current_contract_address(), + &escrow.buyer, + &escrow.amount, + ); + + // 3. Update the state + escrow.status = EscrowStatus::Refunded; + + Ok(()) +}