Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/indexer/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
48 changes: 20 additions & 28 deletions contracts/escrow-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#![no_std]

mod refund;

use soroban_sdk::{
contract, contractimpl, contracttype, symbol_short, token, Address, Bytes, Env, IntoVal,
Symbol, Val, Vec,
Expand Down Expand Up @@ -41,7 +43,8 @@ pub enum ContractError {
ConsensusNotMet = 12,
EscrowDisputed = 13,
EscrowNotDisputed = 14,
NoPendingAdmin = 15,
InsufficientBalance = 15,
NoPendingAdmin = 16,
}

impl From<soroban_sdk::Error> for ContractError {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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| {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions contracts/escrow-manager/src/refund.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
Loading