Skip to content
Open
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
80 changes: 80 additions & 0 deletions contracts/escrow/src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,83 @@ fn test_release_eligibility_terminal_refund_blocks_release() {
assert!(!re.eligible);
assert_eq!(re.reason, soroban_sdk::symbol_short!("refunded"));
}

// ── EscrowReceipt getter tests (get_receipt) ─────────────────────────────

/// Success path: receipt returned immediately after deposit (Funded state).
#[test]
fn test_get_receipt_funded_state() {
let t = TestEnv::setup();
let client = EscrowContractClient::new(&t.env, &t.escrow_contract_id);

let eid = deposit_escrow(&t, 1000, 100);
let receipt = client.get_receipt(&eid);

assert_eq!(receipt.escrow_id, eid);
assert_eq!(receipt.buyer, t.buyer);
assert_eq!(receipt.seller, t.seller);
assert_eq!(receipt.order_id, t.order_id());
assert_eq!(receipt.status, EscrowStatus::Funded);
}

/// Success path: receipt reflects Released status after release.
#[test]
fn test_get_receipt_released_state() {
let t = TestEnv::setup();
let client = EscrowContractClient::new(&t.env, &t.escrow_contract_id);

let eid = deposit_escrow(&t, 1000, 100);
client.release(&eid, &t.buyer);

let receipt = client.get_receipt(&eid);
assert_eq!(receipt.escrow_id, eid);
assert_eq!(receipt.status, EscrowStatus::Released);
// Buyer and seller are unchanged after release
assert_eq!(receipt.buyer, t.buyer);
assert_eq!(receipt.seller, t.seller);
assert_eq!(receipt.order_id, t.order_id());
}

/// Success path: receipt reflects Refunded status after refund.
#[test]
fn test_get_receipt_refunded_state() {
let t = TestEnv::setup();
let client = EscrowContractClient::new(&t.env, &t.escrow_contract_id);

let eid = deposit_escrow(&t, 1000, 100);
client.refund(&eid, &t.seller);

let receipt = client.get_receipt(&eid);
assert_eq!(receipt.escrow_id, eid);
assert_eq!(receipt.status, EscrowStatus::Refunded);
assert_eq!(receipt.buyer, t.buyer);
assert_eq!(receipt.seller, t.seller);
assert_eq!(receipt.order_id, t.order_id());
}

/// Success path: receipt reflects Disputed status mid-lifecycle.
#[test]
fn test_get_receipt_disputed_state() {
let t = TestEnv::setup();
let client = EscrowContractClient::new(&t.env, &t.escrow_contract_id);

let eid = deposit_escrow(&t, 1000, 100);
client.dispute(&eid, &t.buyer);

let receipt = client.get_receipt(&eid);
assert_eq!(receipt.escrow_id, eid);
assert_eq!(receipt.status, EscrowStatus::Disputed);
assert_eq!(receipt.buyer, t.buyer);
assert_eq!(receipt.seller, t.seller);
assert_eq!(receipt.order_id, t.order_id());
}

/// Failure path: NotFound error for a non-existent escrow id.
#[test]
fn test_get_receipt_not_found() {
let t = TestEnv::setup();
let client = EscrowContractClient::new(&t.env, &t.escrow_contract_id);

let result = client.try_get_receipt(&999u64);
assert_eq!(result, Err(Ok(EscrowError::NotFound)));
}
44 changes: 44 additions & 0 deletions contracts/escrow/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,25 @@ pub enum EscrowError {
CreationPaused = 26,
}

/// Compact receipt returned to buyers after escrow creation via `get_receipt`.
///
/// Fields are a purposeful subset of `EscrowRecord` — callers that need the
/// full record (amount, token, timeout, …) should use `get_escrow` instead.
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EscrowReceipt {
/// Numeric escrow identifier (matches the value returned by `deposit`).
pub escrow_id: u64,
/// Address of the buyer who funded the escrow.
pub buyer: Address,
/// Address of the seller (merchant) who will receive the released funds.
pub seller: Address,
/// Merchant-facing order reference embedded at deposit time.
pub order_id: BytesN<32>,
/// Current lifecycle status of the escrow.
pub status: EscrowStatus,
}
Comment on lines +220 to +237

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Align EscrowReceipt with the requested wire contract.

The linked requirement calls for EscrowReceipt { escrow_id: BytesN<32>, buyer, merchant, amount, created_at_ledger }, but this adds { escrow_id: u64, buyer, seller, order_id, status } instead. That means the new public getter still cannot serve the buyer/backend receipt shape requested by issue #170, and deposit only persists created_at as a timestamp, so get_receipt has no way to populate created_at_ledger later. Please adjust the stored creation metadata and return the requested fields here.

Also applies to: 942-965

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/escrow/src/lib.rs` around lines 220 - 237, `EscrowReceipt` in the
escrow contract does not match the requested receipt shape, so `get_receipt`
still returns the wrong public contract. Update the receipt type and the related
`deposit`/storage flow so the contract persists enough creation metadata to
return `escrow_id: BytesN<32>`, `buyer`, `merchant`, `amount`, and
`created_at_ledger`. Use the existing `EscrowReceipt` and `get_receipt` symbols
to align the getter output with issue `#170`, and remove or replace fields like
`seller`, `order_id`, and `status` if they are not part of the wire contract.


/// Refund eligibility result returned by `get_refund_eligibility` (issue #173).
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -920,6 +939,31 @@ impl EscrowContract {
.expect("Escrow not found")
}

/// Read-only buyer-facing receipt for an escrow.
///
/// Returns a compact [`EscrowReceipt`] containing the identifiers and
/// current status that a backend service can forward to the buyer after
/// escrow creation or as a status check. The full record (amount, token,
/// timeout, …) is available via [`get_escrow`].
///
/// # Errors
/// Returns [`EscrowError::NotFound`] when no escrow exists for `escrow_id`.
pub fn get_receipt(env: Env, escrow_id: u64) -> Result<EscrowReceipt, EscrowError> {
let key = DataKey::Escrow(escrow_id);
let record: EscrowRecord = env
.storage()
.persistent()
.get(&key)
.ok_or(EscrowError::NotFound)?;
Ok(EscrowReceipt {
escrow_id: record.escrow_id,
buyer: record.buyer,
seller: record.seller,
order_id: record.order_id,
status: record.status,
})
}

/// Propose a new primary admin. Must be called by current primary admin.
pub fn propose_admin(
env: Env,
Expand Down
Loading
Loading