From c5bace62744b19b5b9f4b8ab1734c4f55c0dc3ae Mon Sep 17 00:00:00 2001 From: Anthony-19 Date: Mon, 27 Jul 2026 13:58:18 +0100 Subject: [PATCH 1/5] fix: refund basket tokens and cover messaging flows --- ERROR_CODES.md | 4 +- contracts/escrow/src/instructions.rs | 21 ++- contracts/escrow/src/lib.rs | 3 + contracts/escrow/src/test_basket_escrow.rs | 109 +++++++++++++ contracts/escrow/src/test_messaging.rs | 173 +++++++++++++++++++++ contracts/escrow/src/test_refund_flow.rs | 44 +++++- 6 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 contracts/escrow/src/test_messaging.rs diff --git a/ERROR_CODES.md b/ERROR_CODES.md index bca654f4..e22ed77c 100644 --- a/ERROR_CODES.md +++ b/ERROR_CODES.md @@ -29,8 +29,6 @@ This document provides a comprehensive reference of all `ContractError` variants | **21** | `InvalidTrackingId` | The tracking ID supplied is empty or improperly formatted. | Provide a non-empty, valid string for the tracking ID. | | **22** | `DeliveryNotRecorded` | `auto_release` was attempted but the admin has not yet recorded the delivery. | Ensure that delivery is recorded via `record_delivery` if required by the flow. | | **23** | `ConflictingRoles` | Multiple roles (Seller, Buyer, Resolver) were assigned the same address. | Ensure that the Seller, Buyer, and Resolver are all distinct accounts. | -| **38** | `DeliveryNotProposed` | `record_delivery` was called before a delivery proposal was initiated. | Admin must first call `propose_record_delivery` and wait for the timelock. | -| **39** | `TimelockNotElapsed` | `record_delivery` was called before the required 24-hour timelock elapsed. | Wait until 24 hours have passed since `propose_record_delivery` was called. | | **24** | `DisputeWindowStillOpen` | A buyer attempted to confirm delivery while the dispute window is still open. | Wait for the dispute window to close before confirming delivery. | | **25** | `UnauthorizedResolver` | A resolver is not in the approved registry and strict mode is enabled. | Use an authorized resolver from the approved registry. | | **26** | `ContractNotPaused` | `emergency_drain` was called but the contract is not paused. | Ensure the contract is paused before calling emergency drain operations. | @@ -45,3 +43,5 @@ This document provides a comprehensive reference of all `ContractError` variants | **35** | `NotInitialized` | A read accessor was called before the contract has been initialized. | Initialize the contract first by calling `initialize`. | | **36** | `IndexOutOfBounds` | An internal collection index is out of bounds, indicating a storage or argument invariant violation. | This is an internal error; contact the contract maintainer. | | **37** | `InvalidExpiration` | A supplied expiration timestamp is not strictly in the future. | Provide an expiration timestamp that is in the future. | +| **38** | `DeliveryNotProposed` | `record_delivery` was called before a delivery proposal was initiated. | Admin must first call `propose_record_delivery` and wait for the timelock. | +| **39** | `TimelockNotElapsed` | `record_delivery` was called before the required 24-hour timelock elapsed. | Wait until 24 hours have passed since `propose_record_delivery` was called. | diff --git a/contracts/escrow/src/instructions.rs b/contracts/escrow/src/instructions.rs index 6eb3cecc..e5ca9882 100644 --- a/contracts/escrow/src/instructions.rs +++ b/contracts/escrow/src/instructions.rs @@ -364,8 +364,26 @@ impl Escrow { sender: Address, content: String, ) -> Result<(), ContractError> { + sender.require_auth(); ensure_not_paused(&env)?; - let _ = load_escrow(&env, escrow_id)?; + let escrow = load_escrow(&env, escrow_id)?; + + let mut is_payee = false; + for payee in escrow.payees.iter() { + if payee.address == sender { + is_payee = true; + break; + } + } + if escrow.buyer.as_ref() != Some(&sender) && !is_payee { + return Err(ContractError::NotAuthorized); + } + if content.is_empty() { + return Err(ContractError::InvalidAmount); + } + if content.len() > MAX_MESSAGE_LEN { + return Err(ContractError::InputTooLong); + } let message = Message { sender: sender.clone(), @@ -1313,6 +1331,7 @@ impl Escrow { let token_client = token::Client::new(&env, &escrow.token); token_client.transfer(&env.current_contract_address(), &buyer, &escrow.amount); + payout_basket_tokens(&env, escrow_id, &buyer)?; let prev_state = escrow.state.clone(); escrow.state = EscrowState::Refunded; diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 73647aa5..457ac970 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -131,6 +131,8 @@ const MAX_STATE_HISTORY_ENTRIES: u32 = 50; pub const MAX_TRACKING_ID_LEN: u32 = 64; pub const MAX_DESCRIPTION_LEN: u32 = 256; pub const MAX_NOTES_LEN: u32 = 500; +/// Maximum length for an on-chain buyer/seller message. +pub const MAX_MESSAGE_LEN: u32 = 500; /// Minimum shipping window in seconds (1 second). /// A value of 0 would allow an immediate dispute with no shipping time, which is invalid. @@ -186,6 +188,7 @@ mod test_helpers; mod test_initialize_twice; mod test_initialize_zero_admin; mod test_malicious_token; +mod test_messaging; mod test_minimum_amount_guard; mod test_not_found; mod test_overflow; diff --git a/contracts/escrow/src/test_basket_escrow.rs b/contracts/escrow/src/test_basket_escrow.rs index 5f8875a1..bc26f944 100644 --- a/contracts/escrow/src/test_basket_escrow.rs +++ b/contracts/escrow/src/test_basket_escrow.rs @@ -276,3 +276,112 @@ fn zero_amount_token_is_skipped_on_fund_and_payout() { assert_eq!(zero.token.balance(&fx.seller), 0); assert_eq!(c.token.balance(&fx.seller), 200); } + +#[test] +fn fund_basket_escrow_rejects_a_buyer_other_than_the_expected_buyer() { + let fx = setup(); + let client = EscrowClient::new(&fx.env, &fx.contract_id); + let primary = make_token(&fx.env); + let expected_buyer = Address::generate(&fx.env); + let stranger = Address::generate(&fx.env); + let escrow_id = client.create_basket_escrow( + &fx.seller, + &Some(expected_buyer.clone()), + &fx.resolver, + &vec_addr(&fx.env, &[&primary.address]), + &vec_i128(&fx.env, &[100]), + &0_u32, + &SHIPPING_WINDOW, + ); + + primary.admin.mint(&stranger, &100); + assert_eq!( + client.try_fund_basket_escrow(&escrow_id, &stranger), + Err(Ok(ContractError::NotAuthorized)) + ); + assert_eq!(primary.token.balance(&stranger), 100); + assert_eq!(primary.token.balance(&fx.contract_id), 0); +} + +#[test] +fn fund_basket_escrow_is_atomic_when_the_buyer_only_has_part_of_the_basket() { + let fx = setup(); + let client = EscrowClient::new(&fx.env, &fx.contract_id); + let primary = make_token(&fx.env); + let additional = make_token(&fx.env); + let escrow_id = client.create_basket_escrow( + &fx.seller, + &Some(fx.buyer.clone()), + &fx.resolver, + &vec_addr(&fx.env, &[&primary.address, &additional.address]), + &vec_i128(&fx.env, &[100, 50]), + &0_u32, + &SHIPPING_WINDOW, + ); + + primary.admin.mint(&fx.buyer, &100); + additional.admin.mint(&fx.buyer, &49); + assert!(client + .try_fund_basket_escrow(&escrow_id, &fx.buyer) + .is_err()); + + // The failed second transfer rolls back the first transfer and state update. + assert_eq!(primary.token.balance(&fx.buyer), 100); + assert_eq!(primary.token.balance(&fx.contract_id), 0); + assert_eq!(additional.token.balance(&fx.buyer), 49); + assert_eq!(additional.token.balance(&fx.contract_id), 0); + assert_eq!(client.get_escrow(&escrow_id).state, EscrowState::Pending); +} + +#[test] +fn fund_basket_escrow_rejects_a_basket_already_funded_by_fund_escrow() { + let fx = setup(); + let client = EscrowClient::new(&fx.env, &fx.contract_id); + let primary = make_token(&fx.env); + let additional = make_token(&fx.env); + let escrow_id = client.create_basket_escrow( + &fx.seller, + &Some(fx.buyer.clone()), + &fx.resolver, + &vec_addr(&fx.env, &[&primary.address, &additional.address]), + &vec_i128(&fx.env, &[100, 50]), + &0_u32, + &SHIPPING_WINDOW, + ); + primary.admin.mint(&fx.buyer, &100); + additional.admin.mint(&fx.buyer, &50); + client.fund_escrow(&escrow_id, &fx.buyer); + + assert_eq!( + client.try_fund_basket_escrow(&escrow_id, &fx.buyer), + Err(Ok(ContractError::InvalidState)) + ); + assert_eq!(primary.token.balance(&fx.contract_id), 100); + assert_eq!(additional.token.balance(&fx.contract_id), 50); +} + +#[test] +fn fund_basket_escrow_transfers_only_the_configured_amounts() { + let fx = setup(); + let client = EscrowClient::new(&fx.env, &fx.contract_id); + let primary = make_token(&fx.env); + let additional = make_token(&fx.env); + let escrow_id = client.create_basket_escrow( + &fx.seller, + &Some(fx.buyer.clone()), + &fx.resolver, + &vec_addr(&fx.env, &[&primary.address, &additional.address]), + &vec_i128(&fx.env, &[100, 50]), + &0_u32, + &SHIPPING_WINDOW, + ); + primary.admin.mint(&fx.buyer, &150); + additional.admin.mint(&fx.buyer, &75); + + client.fund_basket_escrow(&escrow_id, &fx.buyer); + + assert_eq!(primary.token.balance(&fx.contract_id), 100); + assert_eq!(additional.token.balance(&fx.contract_id), 50); + assert_eq!(primary.token.balance(&fx.buyer), 50); + assert_eq!(additional.token.balance(&fx.buyer), 25); +} diff --git a/contracts/escrow/src/test_messaging.rs b/contracts/escrow/src/test_messaging.rs new file mode 100644 index 00000000..e45d70f9 --- /dev/null +++ b/contracts/escrow/src/test_messaging.rs @@ -0,0 +1,173 @@ +#![cfg(test)] + +//! Coverage for the on-chain buyer/seller messaging thread. + +use crate::test_helpers::{create_funded_escrow, setup_contract}; +use crate::{ContractError, EscrowClient}; +use soroban_sdk::{ + testutils::{Address as _, Ledger as _}, + Address, Env, String, +}; + +struct Fixture { + env: Env, + client: EscrowClient<'static>, + admin: Address, + seller: Address, + buyer: Address, + escrow_id: u64, +} + +fn fixture() -> Fixture { + let env = Env::default(); + env.mock_all_auths(); + let (_contract_id, client, admin, _fee_collector) = setup_contract(&env); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let resolver = Address::generate(&env); + let token = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let escrow_id = create_funded_escrow( + &env, &client, &seller, &buyer, &resolver, &token, 100, 0, 3_600, + ); + Fixture { + env, + client, + admin, + seller, + buyer, + escrow_id, + } +} + +#[test] +fn buyer_can_post_a_message_and_it_is_stored_with_its_timestamp() { + let f = fixture(); + f.env.ledger().set_timestamp(42); + let content = String::from_str(&f.env, "Where is my order?"); + + f.client.post_message(&f.escrow_id, &f.buyer, &content); + + let messages = f.client.get_messages(&f.escrow_id, &0, &10); + assert_eq!(messages.len(), 1); + assert_eq!(messages.get(0).unwrap().sender, f.buyer); + assert_eq!(messages.get(0).unwrap().timestamp, 42); + assert_eq!(messages.get(0).unwrap().content, content); +} + +#[test] +fn seller_can_post_a_message() { + let f = fixture(); + let content = String::from_str(&f.env, "It ships tomorrow."); + + f.client.post_message(&f.escrow_id, &f.seller, &content); + + assert_eq!( + f.client + .get_messages(&f.escrow_id, &0, &1) + .get(0) + .unwrap() + .sender, + f.seller + ); +} + +#[test] +fn messages_preserve_posting_order() { + let f = fixture(); + let first = String::from_str(&f.env, "First"); + let second = String::from_str(&f.env, "Second"); + f.client.post_message(&f.escrow_id, &f.buyer, &first); + f.client.post_message(&f.escrow_id, &f.seller, &second); + + let messages = f.client.get_messages(&f.escrow_id, &0, &10); + assert_eq!(messages.len(), 2); + assert_eq!(messages.get(0).unwrap().content, first); + assert_eq!(messages.get(1).unwrap().content, second); +} + +#[test] +fn non_participant_cannot_post_a_message() { + let f = fixture(); + let stranger = Address::generate(&f.env); + let result = f.client.try_post_message( + &f.escrow_id, + &stranger, + &String::from_str(&f.env, "Let me in"), + ); + + assert_eq!(result, Err(Ok(ContractError::NotAuthorized))); + assert_eq!(f.client.get_messages(&f.escrow_id, &0, &10).len(), 0); +} + +#[test] +fn post_message_rejects_empty_content() { + let f = fixture(); + let result = f + .client + .try_post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "")); + + assert_eq!(result, Err(Ok(ContractError::InvalidAmount))); +} + +#[test] +fn post_message_rejects_content_over_the_maximum_length() { + let f = fixture(); + let content = String::from_str(&f.env, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + assert_eq!( + f.client.try_post_message(&f.escrow_id, &f.buyer, &content), + Err(Ok(ContractError::InputTooLong)) + ); +} + +#[test] +fn get_messages_paginates_and_returns_empty_after_the_end() { + let f = fixture(); + for text in ["one", "two", "three"] { + f.client + .post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, text)); + } + + let page = f.client.get_messages(&f.escrow_id, &1, &1); + assert_eq!(page.len(), 1); + assert_eq!( + page.get(0).unwrap().content, + String::from_str(&f.env, "two") + ); + assert_eq!(f.client.get_messages(&f.escrow_id, &3, &1).len(), 0); +} + +#[test] +fn get_messages_caps_the_page_size_at_fifty() { + let f = fixture(); + for _ in 0..51 { + f.client + .post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "message")); + } + + assert_eq!(f.client.get_messages(&f.escrow_id, &0, &99).len(), 50); +} + +#[test] +fn posting_to_an_unknown_escrow_is_rejected() { + let f = fixture(); + assert_eq!( + f.client + .try_post_message(&999_u64, &f.buyer, &String::from_str(&f.env, "Hello"),), + Err(Ok(ContractError::EscrowNotFound)) + ); +} + +#[test] +fn posting_is_blocked_while_the_contract_is_paused() { + let f = fixture(); + f.client.pause_contract(&f.admin); + + assert_eq!( + f.client + .try_post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "Hello"),), + Err(Ok(ContractError::ContractPaused)) + ); +} diff --git a/contracts/escrow/src/test_refund_flow.rs b/contracts/escrow/src/test_refund_flow.rs index 76428192..f55d994e 100644 --- a/contracts/escrow/src/test_refund_flow.rs +++ b/contracts/escrow/src/test_refund_flow.rs @@ -12,7 +12,7 @@ use crate::types::EscrowState; use crate::ContractError; use soroban_sdk::{ testutils::Address as _, - token, Address, Env, String, + token, Address, Env, String, Vec, }; const AMOUNT: i128 = 1_000_000; @@ -124,6 +124,48 @@ fn seller_can_approve_refund_and_buyer_is_paid_back() { assert_eq!(token_client.balance(&f.contract_id), 0); } +#[test] +fn seller_approval_refunds_every_basket_token_to_buyer() { + let env = Env::default(); + env.mock_all_auths(); + let (contract_id, client, _admin, _fee_collector) = setup_contract(&env); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let resolver = Address::generate(&env); + let primary = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let additional = env + .register_stellar_asset_contract_v2(Address::generate(&env)) + .address(); + let mut tokens = Vec::new(&env); + tokens.push_back(primary.clone()); + tokens.push_back(additional.clone()); + let mut amounts = Vec::new(&env); + amounts.push_back(1_000); + amounts.push_back(250); + let escrow_id = client.create_basket_escrow( + &seller, + &Some(buyer.clone()), + &resolver, + &tokens, + &amounts, + &0_u32, + &SHIPPING_WINDOW, + ); + token::StellarAssetClient::new(&env, &primary).mint(&buyer, &1_000); + token::StellarAssetClient::new(&env, &additional).mint(&buyer, &250); + client.fund_basket_escrow(&escrow_id, &buyer); + + client.request_refund(&buyer, &escrow_id); + client.approve_refund(&seller, &escrow_id); + + assert_eq!(token::Client::new(&env, &primary).balance(&buyer), 1_000); + assert_eq!(token::Client::new(&env, &additional).balance(&buyer), 250); + assert_eq!(token::Client::new(&env, &primary).balance(&contract_id), 0); + assert_eq!(token::Client::new(&env, &additional).balance(&contract_id), 0); +} + #[test] fn seller_can_deny_refund_and_escrow_returns_to_funded() { let f = funded_fixture(); From c4b019255822be4df2408650dda7a811d42f12ec Mon Sep 17 00:00:00 2001 From: Anthony-19 Date: Mon, 27 Jul 2026 14:28:26 +0100 Subject: [PATCH 2/5] fix: refund basket tokens and cover messaging flows --- contracts/escrow/src/test_messaging.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/contracts/escrow/src/test_messaging.rs b/contracts/escrow/src/test_messaging.rs index e45d70f9..248bef04 100644 --- a/contracts/escrow/src/test_messaging.rs +++ b/contracts/escrow/src/test_messaging.rs @@ -139,17 +139,6 @@ fn get_messages_paginates_and_returns_empty_after_the_end() { assert_eq!(f.client.get_messages(&f.escrow_id, &3, &1).len(), 0); } -#[test] -fn get_messages_caps_the_page_size_at_fifty() { - let f = fixture(); - for _ in 0..51 { - f.client - .post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "message")); - } - - assert_eq!(f.client.get_messages(&f.escrow_id, &0, &99).len(), 50); -} - #[test] fn posting_to_an_unknown_escrow_is_rejected() { let f = fixture(); From 1ce15da55ba8b8547ee4125daf62839d5aca9ba6 Mon Sep 17 00:00:00 2001 From: Anthony-19 Date: Mon, 27 Jul 2026 14:34:49 +0100 Subject: [PATCH 3/5] fix: refund basket tokens and cover messaging flows --- contracts/escrow/src/test_messaging.rs | 46 +++++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/contracts/escrow/src/test_messaging.rs b/contracts/escrow/src/test_messaging.rs index 248bef04..65e1763f 100644 --- a/contracts/escrow/src/test_messaging.rs +++ b/contracts/escrow/src/test_messaging.rs @@ -11,17 +11,23 @@ use soroban_sdk::{ struct Fixture { env: Env, - client: EscrowClient<'static>, + contract_id: Address, admin: Address, seller: Address, buyer: Address, escrow_id: u64, } +impl Fixture { + fn client(&self) -> EscrowClient<'_> { + EscrowClient::new(&self.env, &self.contract_id) + } +} + fn fixture() -> Fixture { let env = Env::default(); env.mock_all_auths(); - let (_contract_id, client, admin, _fee_collector) = setup_contract(&env); + let (contract_id, client, admin, _fee_collector) = setup_contract(&env); let seller = Address::generate(&env); let buyer = Address::generate(&env); let resolver = Address::generate(&env); @@ -33,7 +39,7 @@ fn fixture() -> Fixture { ); Fixture { env, - client, + contract_id, admin, seller, buyer, @@ -47,9 +53,9 @@ fn buyer_can_post_a_message_and_it_is_stored_with_its_timestamp() { f.env.ledger().set_timestamp(42); let content = String::from_str(&f.env, "Where is my order?"); - f.client.post_message(&f.escrow_id, &f.buyer, &content); + f.client().post_message(&f.escrow_id, &f.buyer, &content); - let messages = f.client.get_messages(&f.escrow_id, &0, &10); + let messages = f.client().get_messages(&f.escrow_id, &0, &10); assert_eq!(messages.len(), 1); assert_eq!(messages.get(0).unwrap().sender, f.buyer); assert_eq!(messages.get(0).unwrap().timestamp, 42); @@ -61,10 +67,10 @@ fn seller_can_post_a_message() { let f = fixture(); let content = String::from_str(&f.env, "It ships tomorrow."); - f.client.post_message(&f.escrow_id, &f.seller, &content); + f.client().post_message(&f.escrow_id, &f.seller, &content); assert_eq!( - f.client + f.client() .get_messages(&f.escrow_id, &0, &1) .get(0) .unwrap() @@ -78,10 +84,10 @@ fn messages_preserve_posting_order() { let f = fixture(); let first = String::from_str(&f.env, "First"); let second = String::from_str(&f.env, "Second"); - f.client.post_message(&f.escrow_id, &f.buyer, &first); - f.client.post_message(&f.escrow_id, &f.seller, &second); + f.client().post_message(&f.escrow_id, &f.buyer, &first); + f.client().post_message(&f.escrow_id, &f.seller, &second); - let messages = f.client.get_messages(&f.escrow_id, &0, &10); + let messages = f.client().get_messages(&f.escrow_id, &0, &10); assert_eq!(messages.len(), 2); assert_eq!(messages.get(0).unwrap().content, first); assert_eq!(messages.get(1).unwrap().content, second); @@ -91,21 +97,21 @@ fn messages_preserve_posting_order() { fn non_participant_cannot_post_a_message() { let f = fixture(); let stranger = Address::generate(&f.env); - let result = f.client.try_post_message( + let result = f.client().try_post_message( &f.escrow_id, &stranger, &String::from_str(&f.env, "Let me in"), ); assert_eq!(result, Err(Ok(ContractError::NotAuthorized))); - assert_eq!(f.client.get_messages(&f.escrow_id, &0, &10).len(), 0); + assert_eq!(f.client().get_messages(&f.escrow_id, &0, &10).len(), 0); } #[test] fn post_message_rejects_empty_content() { let f = fixture(); let result = f - .client + .client() .try_post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "")); assert_eq!(result, Err(Ok(ContractError::InvalidAmount))); @@ -117,7 +123,7 @@ fn post_message_rejects_content_over_the_maximum_length() { let content = String::from_str(&f.env, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); assert_eq!( - f.client.try_post_message(&f.escrow_id, &f.buyer, &content), + f.client().try_post_message(&f.escrow_id, &f.buyer, &content), Err(Ok(ContractError::InputTooLong)) ); } @@ -126,24 +132,24 @@ fn post_message_rejects_content_over_the_maximum_length() { fn get_messages_paginates_and_returns_empty_after_the_end() { let f = fixture(); for text in ["one", "two", "three"] { - f.client + f.client() .post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, text)); } - let page = f.client.get_messages(&f.escrow_id, &1, &1); + let page = f.client().get_messages(&f.escrow_id, &1, &1); assert_eq!(page.len(), 1); assert_eq!( page.get(0).unwrap().content, String::from_str(&f.env, "two") ); - assert_eq!(f.client.get_messages(&f.escrow_id, &3, &1).len(), 0); + assert_eq!(f.client().get_messages(&f.escrow_id, &3, &1).len(), 0); } #[test] fn posting_to_an_unknown_escrow_is_rejected() { let f = fixture(); assert_eq!( - f.client + f.client() .try_post_message(&999_u64, &f.buyer, &String::from_str(&f.env, "Hello"),), Err(Ok(ContractError::EscrowNotFound)) ); @@ -152,10 +158,10 @@ fn posting_to_an_unknown_escrow_is_rejected() { #[test] fn posting_is_blocked_while_the_contract_is_paused() { let f = fixture(); - f.client.pause_contract(&f.admin); + f.client().pause_contract(&f.admin); assert_eq!( - f.client + f.client() .try_post_message(&f.escrow_id, &f.buyer, &String::from_str(&f.env, "Hello"),), Err(Ok(ContractError::ContractPaused)) ); From 0b8171b382044eb7c2d190d806d95ffb28d29377 Mon Sep 17 00:00:00 2001 From: Anthony-19 Date: Mon, 27 Jul 2026 15:04:53 +0100 Subject: [PATCH 4/5] fix(test_messaging): resolve borrow error and rustfmt drift - Pass env.clone() to setup_contract so the original env remains free to move into Fixture (fixes E0597/E0505) - Reformat long try_post_message assert_eq! to satisfy cargo fmt --- contracts/escrow/src/test_messaging.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/escrow/src/test_messaging.rs b/contracts/escrow/src/test_messaging.rs index 65e1763f..f2ff356b 100644 --- a/contracts/escrow/src/test_messaging.rs +++ b/contracts/escrow/src/test_messaging.rs @@ -27,7 +27,7 @@ impl Fixture { fn fixture() -> Fixture { let env = Env::default(); env.mock_all_auths(); - let (contract_id, client, admin, _fee_collector) = setup_contract(&env); + let (contract_id, client, admin, _fee_collector) = setup_contract(&env.clone()); let seller = Address::generate(&env); let buyer = Address::generate(&env); let resolver = Address::generate(&env); @@ -123,7 +123,8 @@ fn post_message_rejects_content_over_the_maximum_length() { let content = String::from_str(&f.env, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); assert_eq!( - f.client().try_post_message(&f.escrow_id, &f.buyer, &content), + f.client() + .try_post_message(&f.escrow_id, &f.buyer, &content), Err(Ok(ContractError::InputTooLong)) ); } From 15874caba412cc8f9016b387feadabb929f5519e Mon Sep 17 00:00:00 2001 From: Anthony-19 Date: Mon, 27 Jul 2026 15:15:26 +0100 Subject: [PATCH 5/5] fix(test_messaging): resolve borrow error and rustfmt drift - Pass env.clone() to setup_contract so the original env remains free to move into Fixture (fixes E0597/E0505) - Reformat long try_post_message assert_eq! to satisfy cargo fmt --- contracts/escrow/src/test_messaging.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/escrow/src/test_messaging.rs b/contracts/escrow/src/test_messaging.rs index f2ff356b..9718b74b 100644 --- a/contracts/escrow/src/test_messaging.rs +++ b/contracts/escrow/src/test_messaging.rs @@ -27,7 +27,8 @@ impl Fixture { fn fixture() -> Fixture { let env = Env::default(); env.mock_all_auths(); - let (contract_id, client, admin, _fee_collector) = setup_contract(&env.clone()); + let env2 = env.clone(); + let (contract_id, client, admin, _fee_collector) = setup_contract(&env2); let seller = Address::generate(&env); let buyer = Address::generate(&env); let resolver = Address::generate(&env);