From 4c4f5b1d2852cf7c95ca6d8533e47a4fd7be0586 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Wed, 6 Aug 2025 22:25:51 +0100 Subject: [PATCH 1/9] created the hold back contract crate --- apps/contracts/Cargo.lock | 7 +++++++ .../auto-release-escrow-contract/Cargo.toml | 2 +- .../conditional-refund-contract/Cargo.toml | 2 +- .../escrow-arbitration-contract/Cargo.toml | 2 +- .../contracts/hold_back_contract/Cargo.toml | 16 ++++++++++++++++ .../contracts/hold_back_contract/src/lib.rs | 11 +++++++++++ .../milestone-payment-contract/Cargo.toml | 2 +- 7 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 apps/contracts/contracts/hold_back_contract/Cargo.toml create mode 100644 apps/contracts/contracts/hold_back_contract/src/lib.rs diff --git a/apps/contracts/Cargo.lock b/apps/contracts/Cargo.lock index 2d6528e..f04f749 100644 --- a/apps/contracts/Cargo.lock +++ b/apps/contracts/Cargo.lock @@ -712,6 +712,13 @@ dependencies = [ "digest", ] +[[package]] +name = "hold_back_contract" +version = "0.1.0" +dependencies = [ + "soroban-sdk 22.0.8", +] + [[package]] name = "iana-time-zone" version = "0.1.63" diff --git a/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml b/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml index 6f618f4..97e2446 100644 --- a/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml +++ b/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "auto-release-escrow-contract" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] crate-type = ["cdylib"] diff --git a/apps/contracts/contracts/conditional-refund-contract/Cargo.toml b/apps/contracts/contracts/conditional-refund-contract/Cargo.toml index 80adea3..4e5767b 100644 --- a/apps/contracts/contracts/conditional-refund-contract/Cargo.toml +++ b/apps/contracts/contracts/conditional-refund-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "conditional-refund-contract" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] crate-type = ["cdylib"] diff --git a/apps/contracts/contracts/escrow-arbitration-contract/Cargo.toml b/apps/contracts/contracts/escrow-arbitration-contract/Cargo.toml index 093cf1a..a77471a 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/Cargo.toml +++ b/apps/contracts/contracts/escrow-arbitration-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "escrow_arbitration_contract" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] crate-type = ["cdylib"] diff --git a/apps/contracts/contracts/hold_back_contract/Cargo.toml b/apps/contracts/contracts/hold_back_contract/Cargo.toml new file mode 100644 index 0000000..46cd5d8 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hold_back_contract" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] \ No newline at end of file diff --git a/apps/contracts/contracts/hold_back_contract/src/lib.rs b/apps/contracts/contracts/hold_back_contract/src/lib.rs new file mode 100644 index 0000000..14b42a6 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/src/lib.rs @@ -0,0 +1,11 @@ +#![no_std] + + +use soroban_sdk::{Address, Env, String, contract, contractimpl}; + + +#[contract] +pub struct HoldBackContract; + +#[contractimpl] +impl HoldBackContract {} \ No newline at end of file diff --git a/apps/contracts/contracts/milestone-payment-contract/Cargo.toml b/apps/contracts/contracts/milestone-payment-contract/Cargo.toml index dcccfa7..b932c25 100644 --- a/apps/contracts/contracts/milestone-payment-contract/Cargo.toml +++ b/apps/contracts/contracts/milestone-payment-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "milestone-payment" version = "0.1.0" -edition = "2024" +edition = "2021" [lib] crate-type = ["cdylib"] From 7f43d7a50c8af74ea05e877a806466ad100fed21 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 08:01:49 +0100 Subject: [PATCH 2/9] hold back contract fullfilled --- .../contracts/hold_back_contract/src/lib.rs | 715 +++++++++++++++++- 1 file changed, 713 insertions(+), 2 deletions(-) diff --git a/apps/contracts/contracts/hold_back_contract/src/lib.rs b/apps/contracts/contracts/hold_back_contract/src/lib.rs index 14b42a6..af393e3 100644 --- a/apps/contracts/contracts/hold_back_contract/src/lib.rs +++ b/apps/contracts/contracts/hold_back_contract/src/lib.rs @@ -1,11 +1,722 @@ #![no_std] -use soroban_sdk::{Address, Env, String, contract, contractimpl}; +use soroban_sdk::{Address, Env, contract, contractimpl, contracttype, contracterror, log, token}; +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum Error { + NotInitialized = 1, + InvalidAmount = 2, + InvalidHoldbackRate = 3, + InvalidBuyer = 4, + InvalidSeller = 5, + TransactionNotFound = 6, + InvalidStatus = 7, + Unauthorized = 8, + AlreadyInitialized = 9, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +enum TransactionStatus { + Held, + HoldbackPending, + Completed, + Cancelled, + Disputed, +} + + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Transaction { + pub buyer: Address, + pub seller: Address, + pub amount: u128, + pub token: Address, + pub holdback_rate: u32, + pub holdback_amount: u128, + pub final_amount: u128, + pub release_time: u64, + pub status: TransactionStatus, +} + +#[contracttype] +#[derive(Debug, Eq, PartialEq)] +pub enum DataKey { + Transaction(u128), + TransactionCounter, + Token, + Admin, +} + +const DAY_IN_SECONDS: u64 = 86400; + #[contract] pub struct HoldBackContract; #[contractimpl] -impl HoldBackContract {} \ No newline at end of file +impl HoldBackContract { + pub fn initialize(env: Env, admin: Address) -> Result { + if env.storage().persistent().has(&DataKey::Admin) { + return Err(Error::AlreadyInitialized); + } + env.storage().persistent().set(&DataKey::Admin, &admin); + Ok(true) + } + + pub fn create_payment( + env: Env, + buyer: Address, + seller: Address, + amount: u128, + token: Address, + holdback_rate: u32, + holdback_days: u32, + ) -> Result { + buyer.require_auth(); + let admin: Address = env + .storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + + if amount == 0 { + return Err(Error::InvalidAmount); + } + if holdback_rate == 0 || holdback_rate > 100 { + return Err(Error::InvalidHoldbackRate); + } + if buyer == seller || buyer == admin || seller == admin { + return Err(Error::InvalidBuyer); + } + if buyer == token || seller == token { + return Err(Error::InvalidSeller); + } + + let holdback_amount = (amount * holdback_rate as u128) / 100; + let final_amount = amount.checked_sub(holdback_amount).ok_or(Error::InvalidAmount)?; + + let token_client = token::Client::new(&env, &token); + token_client.transfer(&buyer, &env.current_contract_address(), &(amount as i128)); + + if final_amount > 0 { + token_client.transfer(&env.current_contract_address(), &seller, &(final_amount as i128)); + } + + let transaction_id = env + .storage() + .persistent() + .get(&DataKey::TransactionCounter) + .unwrap_or(0u128) + .checked_add(1) + .ok_or(Error::InvalidAmount)?; + env.storage() + .persistent() + .set(&DataKey::TransactionCounter, &transaction_id); + + let transaction = Transaction { + buyer: buyer.clone(), + seller: seller.clone(), + amount, + token, + holdback_rate, + holdback_amount, + final_amount, + release_time: env.ledger().timestamp() + (holdback_days as u64 * DAY_IN_SECONDS), + status: TransactionStatus::Held, + }; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events().publish(("transaction_created",), + (transaction_id, + buyer, + seller, + amount, + holdback_amount, + )); + + log!( + &env, + "Transaction {} created with holdback {}%", + transaction_id, + holdback_rate + ); + Ok(transaction_id) + } + + pub fn approve_release(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { + buyer.require_auth(); + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.buyer != buyer { + return Err(Error::Unauthorized); + } + if transaction.status != TransactionStatus::Held { + return Err(Error::InvalidStatus); + } + + transaction.status = TransactionStatus::HoldbackPending; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + Self::release_holdback_if_due(&env, transaction_id)?; + Ok(()) + } + + pub fn initiate_dispute(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { + buyer.require_auth(); + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.buyer != buyer { + return Err(Error::Unauthorized); + } + if transaction.status != TransactionStatus::Held + && transaction.status != TransactionStatus::HoldbackPending + { + return Err(Error::InvalidStatus); + } + + transaction.status = TransactionStatus::Disputed; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events().publish(( "dispute_initiated",), + (transaction_id, + buyer, + )); + Ok(()) + } + + pub fn resolve_dispute( + env: Env, + transaction_id: u128, + refund: bool, + admin: Address, + ) -> Result<(), Error> { + admin.require_auth(); + let stored_admin: Address = env + .storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + if admin != stored_admin { + return Err(Error::Unauthorized); + } + + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.status != TransactionStatus::Disputed { + return Err(Error::InvalidStatus); + } + + let token_client = token::Client::new(&env, &transaction.token); + if refund { + token_client.transfer( + &env.current_contract_address(), + &transaction.buyer, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Cancelled; + env.events().publish(( "holdback_refunded",), + (transaction_id, + transaction.buyer.clone(), + transaction.holdback_amount, + )); + } else { + token_client.transfer( + &env.current_contract_address(), + &transaction.seller, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Completed; + env.events().publish(("holdback_released",), + (transaction_id, + transaction.seller.clone(), + transaction.holdback_amount, + )); + } + env.storage().persistent().set(&DataKey::Transaction(transaction_id), &transaction); + Ok(()) + } + + pub fn check_and_release(env: Env, transaction_id: u128) -> Result<(), Error> { + let transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.status != TransactionStatus::Held + && transaction.status != TransactionStatus::HoldbackPending + { + return Err(Error::InvalidStatus); + } + + Self::release_holdback_if_due(&env, transaction_id)?; + Ok(()) + } + + fn release_holdback_if_due(env: &Env, transaction_id: u128) -> Result<(), Error> { + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + + if transaction.status == TransactionStatus::HoldbackPending + || (transaction.status == TransactionStatus::Held + && env.ledger().timestamp() >= transaction.release_time) + { + let token_client = token::Client::new(&env, &transaction.token); + token_client.transfer( + &env.current_contract_address(), + &transaction.seller, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Completed; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events().publish(("holdback_released",), + (transaction_id, + transaction.seller, + transaction.holdback_amount, + )); + } + Ok(()) + } + + pub fn get_transaction(env: Env, transaction_id: u128) -> Result { + env.storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound) + } + + pub fn get_admin(env: Env) -> Result { + env.storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized) + } +} + + +#[cfg(test)] +mod test { + use super::*; + + use soroban_sdk::{ + log, + testutils::{Address as _, Ledger}, + token::{self, StellarAssetClient}, + Address, Env, String, + }; + + + fn create_variables() -> (Env, Address, Address) { + let env = Env::default(); + env.mock_all_auths(); + // register the contract + let contract_address = env.register(HoldBackContract, {}); + let mocked_address = Address::generate(&env); + + (env, contract_address, mocked_address) + } + + fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient<'static>) { + let client = env.register_stellar_asset_contract_v2(admin.clone()); + ( + client.address(), + token::StellarAssetClient::new(&env, &client.address()), + ) + } + + fn create_variables_and_initialize_contract() -> (Env, HoldBackContractClient<'static>, Address) { + let (env, contract_address, mocked_address) = create_variables(); + + let contract_instance = HoldBackContractClient::new(&env, &contract_address); + + contract_instance.initialize(&mocked_address); + + (env, contract_instance, mocked_address) + } + + fn generate_addresses(env: &Env) -> (Address, Address, Address, Address) { + (Address::generate(env), Address::generate(env), Address::generate(env), Address::generate(env)) + } + //"initialize the contract" + #[test] + fn test_initialize_contract() { + let (env, contract_address, mocked_address) = create_variables(); + + let contract_instance = HoldBackContractClient::new(&env, &contract_address); + + assert_eq!(contract_instance.initialize(&mocked_address), true, "error"); + + assert_eq!(contract_instance.get_admin(), mocked_address); + } + + + + #[test] + fn test_create_payment() { + let (env, contract_instance, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = contract_instance + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction = contract_instance.get_transaction(&transaction_id); + + assert_eq!(transaction.buyer, buyer); + assert_eq!(transaction.seller, seller); + assert_eq!(transaction.amount, 1000); + assert_eq!(transaction.holdback_rate, 20); + assert_eq!(transaction.holdback_amount, 200); + assert_eq!(transaction.final_amount, 800); + assert_eq!(transaction.status, TransactionStatus::Held); + assert_eq!(transaction.token, token_address); + + } + + #[test] +fn test_approve_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.approve_release(&transaction_id, &buyer); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); + } + + +#[test] +fn test_time_based_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &1); + + env.ledger().set_timestamp(DAY_IN_SECONDS + 1); + + contract.check_and_release(&transaction_id); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); +} + + +#[test] +fn test_dispute_and_refunded() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &admin); + + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Cancelled); +} + +#[test] +#[should_panic] +fn test_invalid_transaction() { + let (_, contract, _) = create_variables_and_initialize_contract(); + + contract.get_transaction(&999); +} + + +#[test] +#[should_panic] +fn test_unauthorized_approve() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.approve_release(&transaction_id, &seller); +} + +#[test] +#[should_panic] +fn test_invalid_amount() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, _) = create_token(&env, &admin); + contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); +} +#[test] +#[should_panic] +fn test_invalid_holdback_rate() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &0, &7); +} + +#[test] +#[should_panic] +fn test_unauthorized_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &seller); +} + +#[test] +#[should_panic] +fn test_unauthorized_resolve_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &buyer); +} + +#[test] +#[should_panic] +fn test_approve_invalid_status() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.approve_release(&transaction_id, &buyer); +} + +#[test] +#[should_panic] +fn test_buyer_is_seller() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, _, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &buyer, &1000, &token_address, &20, &7); +} + +#[test] +#[should_panic] +fn test_buyer_is_admin() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&admin, &seller, &1000, &token_address, &20, &7); +} + +#[test] +#[should_panic] +fn test_seller_is_token() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = contract + .create_payment(&buyer, &token_address, &1000, &token_address, &20, &7); +} +} + +// #[test] +// #[should_panic(expected = "Status: InvalidHoldbackRate")] +// fn test_invalid_holdback_rate() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let seller = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// contract +// .create_payment(buyer, seller, 1000, token, 101, 7) +// .unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: Unauthorized")] +// fn test_unauthorized_dispute() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let seller = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// let transaction_id = contract +// .create_payment(buyer, seller.clone(), 1000, token, 20, 7) +// .unwrap(); + +// contract.initiate_dispute(transaction_id, seller).unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: Unauthorized")] +// fn test_unauthorized_resolve_dispute() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let seller = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// let transaction_id = contract +// .create_payment(buyer.clone(), seller, 1000, token, 20, 7) +// .unwrap(); + +// contract.initiate_dispute(transaction_id, buyer).unwrap(); +// contract.resolve_dispute(transaction_id, true, buyer).unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: InvalidStatus")] +// fn test_approve_invalid_status() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let seller = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// let transaction_id = contract +// .create_payment(buyer.clone(), seller, 1000, token, 20, 7) +// .unwrap(); + +// contract.initiate_dispute(transaction_id, buyer.clone()).unwrap(); +// contract.approve_release(transaction_id, buyer).unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: InvalidBuyer")] +// fn test_buyer_is_seller() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// contract +// .create_payment(buyer.clone(), buyer, 1000, token, 20, 7) +// .unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: InvalidBuyer")] +// fn test_buyer_is_admin() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let seller = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin.clone()).unwrap(); +// contract +// .create_payment(admin, seller, 1000, token, 20, 7) +// .unwrap(); +// } + +// #[test] +// #[should_panic(expected = "Status: InvalidSeller")] +// fn test_seller_is_token() { +// let env = Env::default(); +// env.mock_all_auths(); +// let contract_id = env.register_contract(None, HoldBackContract); +// let contract = HoldBackContract::new(&env, &contract_id); +// let admin = Address::random(&env); +// let buyer = Address::random(&env); +// let token = env.register_stellar_asset_contract(Address::random(&env)); + +// contract.initialize(admin).unwrap(); +// contract +// .create_payment(buyer, token, 1000, token, 20, 7) +// .unwrap(); +// } \ No newline at end of file From f8bfc577587a27b6242a5b4f6eedea7aa0a78a55 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 08:10:54 +0100 Subject: [PATCH 3/9] formatted --- .../contracts/hold_back_contract/Makefile | 19 + .../contracts/hold_back_contract/src/lib.rs | 484 +++++++----------- 2 files changed, 196 insertions(+), 307 deletions(-) create mode 100644 apps/contracts/contracts/hold_back_contract/Makefile diff --git a/apps/contracts/contracts/hold_back_contract/Makefile b/apps/contracts/contracts/hold_back_contract/Makefile new file mode 100644 index 0000000..7788f03 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/Makefile @@ -0,0 +1,19 @@ +-include .env + + +format: + @echo "formatting the project" + @cargo fmt + +build: + @echo "building the project" + @stellar contract build + +test: + @echo "testing the project tests" + @cargo test + + +clippy: + @echo "clip" + @cargo clippy \ No newline at end of file diff --git a/apps/contracts/contracts/hold_back_contract/src/lib.rs b/apps/contracts/contracts/hold_back_contract/src/lib.rs index af393e3..86b85e0 100644 --- a/apps/contracts/contracts/hold_back_contract/src/lib.rs +++ b/apps/contracts/contracts/hold_back_contract/src/lib.rs @@ -1,8 +1,6 @@ #![no_std] - -use soroban_sdk::{Address, Env, contract, contractimpl, contracttype, contracterror, log, token}; - +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, log, token, Address, Env}; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -20,7 +18,7 @@ pub enum Error { #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -enum TransactionStatus { +pub enum TransactionStatus { Held, HoldbackPending, Completed, @@ -28,7 +26,6 @@ enum TransactionStatus { Disputed, } - #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct Transaction { @@ -48,11 +45,11 @@ pub struct Transaction { pub enum DataKey { Transaction(u128), TransactionCounter, - Token, + Token, Admin, } -const DAY_IN_SECONDS: u64 = 86400; +const DAY_IN_SECONDS: u64 = 86400; #[contract] pub struct HoldBackContract; @@ -97,13 +94,19 @@ impl HoldBackContract { } let holdback_amount = (amount * holdback_rate as u128) / 100; - let final_amount = amount.checked_sub(holdback_amount).ok_or(Error::InvalidAmount)?; + let final_amount = amount + .checked_sub(holdback_amount) + .ok_or(Error::InvalidAmount)?; let token_client = token::Client::new(&env, &token); token_client.transfer(&buyer, &env.current_contract_address(), &(amount as i128)); if final_amount > 0 { - token_client.transfer(&env.current_contract_address(), &seller, &(final_amount as i128)); + token_client.transfer( + &env.current_contract_address(), + &seller, + &(final_amount as i128), + ); } let transaction_id = env @@ -132,13 +135,10 @@ impl HoldBackContract { .persistent() .set(&DataKey::Transaction(transaction_id), &transaction); - env.events().publish(("transaction_created",), - (transaction_id, - buyer, - seller, - amount, - holdback_amount, - )); + env.events().publish( + ("transaction_created",), + (transaction_id, buyer, seller, amount, holdback_amount), + ); log!( &env, @@ -193,10 +193,8 @@ impl HoldBackContract { .persistent() .set(&DataKey::Transaction(transaction_id), &transaction); - env.events().publish(( "dispute_initiated",), - (transaction_id, - buyer, - )); + env.events() + .publish(("dispute_initiated",), (transaction_id, buyer)); Ok(()) } @@ -233,11 +231,14 @@ impl HoldBackContract { &(transaction.holdback_amount as i128), ); transaction.status = TransactionStatus::Cancelled; - env.events().publish(( "holdback_refunded",), - (transaction_id, - transaction.buyer.clone(), - transaction.holdback_amount, - )); + env.events().publish( + ("holdback_refunded",), + ( + transaction_id, + transaction.buyer.clone(), + transaction.holdback_amount, + ), + ); } else { token_client.transfer( &env.current_contract_address(), @@ -245,13 +246,18 @@ impl HoldBackContract { &(transaction.holdback_amount as i128), ); transaction.status = TransactionStatus::Completed; - env.events().publish(("holdback_released",), - (transaction_id, - transaction.seller.clone(), - transaction.holdback_amount, - )); + env.events().publish( + ("holdback_released",), + ( + transaction_id, + transaction.seller.clone(), + transaction.holdback_amount, + ), + ); } - env.storage().persistent().set(&DataKey::Transaction(transaction_id), &transaction); + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); Ok(()) } @@ -293,11 +299,14 @@ impl HoldBackContract { .persistent() .set(&DataKey::Transaction(transaction_id), &transaction); - env.events().publish(("holdback_released",), - (transaction_id, - transaction.seller, - transaction.holdback_amount, - )); + env.events().publish( + ("holdback_released",), + ( + transaction_id, + transaction.seller, + transaction.holdback_amount, + ), + ); } Ok(()) } @@ -317,25 +326,22 @@ impl HoldBackContract { } } - #[cfg(test)] mod test { use super::*; use soroban_sdk::{ - log, testutils::{Address as _, Ledger}, token::{self, StellarAssetClient}, - Address, Env, String, + Address, Env, }; - fn create_variables() -> (Env, Address, Address) { let env = Env::default(); env.mock_all_auths(); // register the contract let contract_address = env.register(HoldBackContract, {}); - let mocked_address = Address::generate(&env); + let mocked_address = Address::generate(&env); (env, contract_address, mocked_address) } @@ -348,7 +354,8 @@ mod test { ) } - fn create_variables_and_initialize_contract() -> (Env, HoldBackContractClient<'static>, Address) { + fn create_variables_and_initialize_contract() -> (Env, HoldBackContractClient<'static>, Address) + { let (env, contract_address, mocked_address) = create_variables(); let contract_instance = HoldBackContractClient::new(&env, &contract_address); @@ -359,7 +366,12 @@ mod test { } fn generate_addresses(env: &Env) -> (Address, Address, Address, Address) { - (Address::generate(env), Address::generate(env), Address::generate(env), Address::generate(env)) + ( + Address::generate(env), + Address::generate(env), + Address::generate(env), + Address::generate(env), + ) } //"initialize the contract" #[test] @@ -373,8 +385,6 @@ mod test { assert_eq!(contract_instance.get_admin(), mocked_address); } - - #[test] fn test_create_payment() { let (env, contract_instance, admin) = create_variables_and_initialize_contract(); @@ -384,339 +394,199 @@ mod test { let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract_instance - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - let transaction = contract_instance.get_transaction(&transaction_id); - - assert_eq!(transaction.buyer, buyer); - assert_eq!(transaction.seller, seller); - assert_eq!(transaction.amount, 1000); - assert_eq!(transaction.holdback_rate, 20); - assert_eq!(transaction.holdback_amount, 200); - assert_eq!(transaction.final_amount, 800); - assert_eq!(transaction.status, TransactionStatus::Held); - assert_eq!(transaction.token, token_address); - + let transaction_id = + contract_instance.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction = contract_instance.get_transaction(&transaction_id); + + assert_eq!(transaction.buyer, buyer); + assert_eq!(transaction.seller, seller); + assert_eq!(transaction.amount, 1000); + assert_eq!(transaction.holdback_rate, 20); + assert_eq!(transaction.holdback_amount, 200); + assert_eq!(transaction.final_amount, 800); + assert_eq!(transaction.status, TransactionStatus::Held); + assert_eq!(transaction.token, token_address); } #[test] -fn test_approve_release() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + fn test_approve_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.approve_release(&transaction_id, &buyer); - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Completed); + contract.approve_release(&transaction_id, &buyer); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); } - -#[test] -fn test_time_based_release() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + fn test_time_based_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &1); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &1); - env.ledger().set_timestamp(DAY_IN_SECONDS + 1); - - contract.check_and_release(&transaction_id); - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Completed); -} + env.ledger().set_timestamp(DAY_IN_SECONDS + 1); + contract.check_and_release(&transaction_id); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); + } -#[test] -fn test_dispute_and_refunded() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + fn test_dispute_and_refunded() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.initiate_dispute(&transaction_id, &buyer); - contract.resolve_dispute(&transaction_id, &true, &admin); - - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Cancelled); -} + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &admin); -#[test] -#[should_panic] -fn test_invalid_transaction() { - let (_, contract, _) = create_variables_and_initialize_contract(); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Cancelled); + } - contract.get_transaction(&999); -} + #[test] + #[should_panic] + fn test_invalid_transaction() { + let (_, contract, _) = create_variables_and_initialize_contract(); + contract.get_transaction(&999); + } -#[test] -#[should_panic] -fn test_unauthorized_approve() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_unauthorized_approve() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.approve_release(&transaction_id, &seller); -} + contract.approve_release(&transaction_id, &seller); + } -#[test] -#[should_panic] -fn test_invalid_amount() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_invalid_amount() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, _) = create_token(&env, &admin); - contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); -} -#[test] -#[should_panic] -fn test_invalid_holdback_rate() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + let (token_address, _) = create_token(&env, &admin); + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + } + #[test] + #[should_panic] + fn test_invalid_holdback_rate() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &0, &7); -} + contract.create_payment(&buyer, &seller, &1000, &token_address, &0, &7); + } -#[test] -#[should_panic] -fn test_unauthorized_dispute() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_unauthorized_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.initiate_dispute(&transaction_id, &seller); -} + contract.initiate_dispute(&transaction_id, &seller); + } -#[test] -#[should_panic] -fn test_unauthorized_resolve_dispute() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_unauthorized_resolve_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.initiate_dispute(&transaction_id, &buyer); - contract.resolve_dispute(&transaction_id, &true, &buyer); -} + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &buyer); + } -#[test] -#[should_panic] -fn test_approve_invalid_status() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_approve_invalid_status() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - contract.initiate_dispute(&transaction_id, &buyer); - contract.approve_release(&transaction_id, &buyer); -} + contract.initiate_dispute(&transaction_id, &buyer); + contract.approve_release(&transaction_id, &buyer); + } -#[test] -#[should_panic] -fn test_buyer_is_seller() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_buyer_is_seller() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, _, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &buyer, &1000, &token_address, &20, &7); -} + contract.create_payment(&buyer, &buyer, &1000, &token_address, &20, &7); + } -#[test] -#[should_panic] -fn test_buyer_is_admin() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_buyer_is_admin() { + let (env, contract, admin) = create_variables_and_initialize_contract(); let (buyer, seller, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&admin, &seller, &1000, &token_address, &20, &7); -} + contract.create_payment(&admin, &seller, &1000, &token_address, &20, &7); + } -#[test] -#[should_panic] -fn test_seller_is_token() { - let (env, contract, admin) = create_variables_and_initialize_contract(); + #[test] + #[should_panic] + fn test_seller_is_token() { + let (env, contract, admin) = create_variables_and_initialize_contract(); - let (buyer, seller, _, _) = generate_addresses(&env); + let (buyer, _, _, _) = generate_addresses(&env); - let (token_address, token_client) = create_token(&env, &admin); + let (token_address, token_client) = create_token(&env, &admin); token_client.mint(&buyer, &4000); - let transaction_id = contract - .create_payment(&buyer, &token_address, &1000, &token_address, &20, &7); -} + contract.create_payment(&buyer, &token_address, &1000, &token_address, &20, &7); + } } - -// #[test] -// #[should_panic(expected = "Status: InvalidHoldbackRate")] -// fn test_invalid_holdback_rate() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let seller = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// contract -// .create_payment(buyer, seller, 1000, token, 101, 7) -// .unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: Unauthorized")] -// fn test_unauthorized_dispute() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let seller = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// let transaction_id = contract -// .create_payment(buyer, seller.clone(), 1000, token, 20, 7) -// .unwrap(); - -// contract.initiate_dispute(transaction_id, seller).unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: Unauthorized")] -// fn test_unauthorized_resolve_dispute() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let seller = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// let transaction_id = contract -// .create_payment(buyer.clone(), seller, 1000, token, 20, 7) -// .unwrap(); - -// contract.initiate_dispute(transaction_id, buyer).unwrap(); -// contract.resolve_dispute(transaction_id, true, buyer).unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: InvalidStatus")] -// fn test_approve_invalid_status() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let seller = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// let transaction_id = contract -// .create_payment(buyer.clone(), seller, 1000, token, 20, 7) -// .unwrap(); - -// contract.initiate_dispute(transaction_id, buyer.clone()).unwrap(); -// contract.approve_release(transaction_id, buyer).unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: InvalidBuyer")] -// fn test_buyer_is_seller() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// contract -// .create_payment(buyer.clone(), buyer, 1000, token, 20, 7) -// .unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: InvalidBuyer")] -// fn test_buyer_is_admin() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let seller = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin.clone()).unwrap(); -// contract -// .create_payment(admin, seller, 1000, token, 20, 7) -// .unwrap(); -// } - -// #[test] -// #[should_panic(expected = "Status: InvalidSeller")] -// fn test_seller_is_token() { -// let env = Env::default(); -// env.mock_all_auths(); -// let contract_id = env.register_contract(None, HoldBackContract); -// let contract = HoldBackContract::new(&env, &contract_id); -// let admin = Address::random(&env); -// let buyer = Address::random(&env); -// let token = env.register_stellar_asset_contract(Address::random(&env)); - -// contract.initialize(admin).unwrap(); -// contract -// .create_payment(buyer, token, 1000, token, 20, 7) -// .unwrap(); -// } \ No newline at end of file From 95c5a793d16a4bf9b604ea0d60a64c4ca19a2c18 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 08:29:05 +0100 Subject: [PATCH 4/9] done --- .../hold_back_contract/src/entities.rs | 35 ++ .../hold_back_contract/src/errors.rs | 16 + .../src/hold_back_contract.rs | 282 +++++++++ .../contracts/hold_back_contract/src/lib.rs | 594 +----------------- .../contracts/hold_back_contract/src/tests.rs | 268 ++++++++ 5 files changed, 605 insertions(+), 590 deletions(-) create mode 100644 apps/contracts/contracts/hold_back_contract/src/entities.rs create mode 100644 apps/contracts/contracts/hold_back_contract/src/errors.rs create mode 100644 apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs create mode 100644 apps/contracts/contracts/hold_back_contract/src/tests.rs diff --git a/apps/contracts/contracts/hold_back_contract/src/entities.rs b/apps/contracts/contracts/hold_back_contract/src/entities.rs new file mode 100644 index 0000000..0093498 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/src/entities.rs @@ -0,0 +1,35 @@ + +use soroban_sdk::{contracttype, Address}; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TransactionStatus { + Held, + HoldbackPending, + Completed, + Cancelled, + Disputed, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Transaction { + pub buyer: Address, + pub seller: Address, + pub amount: u128, + pub token: Address, + pub holdback_rate: u32, + pub holdback_amount: u128, + pub final_amount: u128, + pub release_time: u64, + pub status: TransactionStatus, +} + +#[contracttype] +#[derive(Debug, Eq, PartialEq)] +pub enum DataKey { + Transaction(u128), + TransactionCounter, + Token, + Admin, +} \ No newline at end of file diff --git a/apps/contracts/contracts/hold_back_contract/src/errors.rs b/apps/contracts/contracts/hold_back_contract/src/errors.rs new file mode 100644 index 0000000..a7ef003 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/src/errors.rs @@ -0,0 +1,16 @@ +use soroban_sdk::contracterror; + + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub enum Error { + NotInitialized = 1, + InvalidAmount = 2, + InvalidHoldbackRate = 3, + InvalidBuyer = 4, + InvalidSeller = 5, + TransactionNotFound = 6, + InvalidStatus = 7, + Unauthorized = 8, + AlreadyInitialized = 9, +} \ No newline at end of file diff --git a/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs b/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs new file mode 100644 index 0000000..8329d1d --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs @@ -0,0 +1,282 @@ + + +use crate::errors::*; +use crate::entities::*; +use soroban_sdk::{contract, contractimpl, log, token, Address, Env}; + +pub const DAY_IN_SECONDS: u64 = 86400; + +#[contract] +pub struct HoldBackContract; + +#[contractimpl] +impl HoldBackContract { + pub fn initialize(env: Env, admin: Address) -> Result { + if env.storage().persistent().has(&DataKey::Admin) { + return Err(Error::AlreadyInitialized); + } + env.storage().persistent().set(&DataKey::Admin, &admin); + Ok(true) + } + + pub fn create_payment( + env: Env, + buyer: Address, + seller: Address, + amount: u128, + token: Address, + holdback_rate: u32, + holdback_days: u32, + ) -> Result { + buyer.require_auth(); + let admin: Address = env + .storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + + if amount == 0 { + return Err(Error::InvalidAmount); + } + if holdback_rate == 0 || holdback_rate > 100 { + return Err(Error::InvalidHoldbackRate); + } + if buyer == seller || buyer == admin || seller == admin { + return Err(Error::InvalidBuyer); + } + if buyer == token || seller == token { + return Err(Error::InvalidSeller); + } + + let holdback_amount = (amount * holdback_rate as u128) / 100; + let final_amount = amount + .checked_sub(holdback_amount) + .ok_or(Error::InvalidAmount)?; + + let token_client = token::Client::new(&env, &token); + token_client.transfer(&buyer, &env.current_contract_address(), &(amount as i128)); + + if final_amount > 0 { + token_client.transfer( + &env.current_contract_address(), + &seller, + &(final_amount as i128), + ); + } + + let transaction_id = env + .storage() + .persistent() + .get(&DataKey::TransactionCounter) + .unwrap_or(0u128) + .checked_add(1) + .ok_or(Error::InvalidAmount)?; + env.storage() + .persistent() + .set(&DataKey::TransactionCounter, &transaction_id); + + let transaction = Transaction { + buyer: buyer.clone(), + seller: seller.clone(), + amount, + token, + holdback_rate, + holdback_amount, + final_amount, + release_time: env.ledger().timestamp() + (holdback_days as u64 * DAY_IN_SECONDS), + status: TransactionStatus::Held, + }; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events().publish( + ("transaction_created",), + (transaction_id, buyer, seller, amount, holdback_amount), + ); + + log!( + &env, + "Transaction {} created with holdback {}%", + transaction_id, + holdback_rate + ); + Ok(transaction_id) + } + + pub fn approve_release(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { + buyer.require_auth(); + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.buyer != buyer { + return Err(Error::Unauthorized); + } + if transaction.status != TransactionStatus::Held { + return Err(Error::InvalidStatus); + } + + transaction.status = TransactionStatus::HoldbackPending; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + Self::release_holdback_if_due(&env, transaction_id)?; + Ok(()) + } + + pub fn initiate_dispute(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { + buyer.require_auth(); + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.buyer != buyer { + return Err(Error::Unauthorized); + } + if transaction.status != TransactionStatus::Held + && transaction.status != TransactionStatus::HoldbackPending + { + return Err(Error::InvalidStatus); + } + + transaction.status = TransactionStatus::Disputed; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events() + .publish(("dispute_initiated",), (transaction_id, buyer)); + Ok(()) + } + + pub fn resolve_dispute( + env: Env, + transaction_id: u128, + refund: bool, + admin: Address, + ) -> Result<(), Error> { + admin.require_auth(); + let stored_admin: Address = env + .storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized)?; + if admin != stored_admin { + return Err(Error::Unauthorized); + } + + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.status != TransactionStatus::Disputed { + return Err(Error::InvalidStatus); + } + + let token_client = token::Client::new(&env, &transaction.token); + if refund { + token_client.transfer( + &env.current_contract_address(), + &transaction.buyer, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Cancelled; + env.events().publish( + ("holdback_refunded",), + ( + transaction_id, + transaction.buyer.clone(), + transaction.holdback_amount, + ), + ); + } else { + token_client.transfer( + &env.current_contract_address(), + &transaction.seller, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Completed; + env.events().publish( + ("holdback_released",), + ( + transaction_id, + transaction.seller.clone(), + transaction.holdback_amount, + ), + ); + } + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + Ok(()) + } + + pub fn check_and_release(env: Env, transaction_id: u128) -> Result<(), Error> { + let transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + if transaction.status != TransactionStatus::Held + && transaction.status != TransactionStatus::HoldbackPending + { + return Err(Error::InvalidStatus); + } + + Self::release_holdback_if_due(&env, transaction_id)?; + Ok(()) + } + + fn release_holdback_if_due(env: &Env, transaction_id: u128) -> Result<(), Error> { + let mut transaction: Transaction = env + .storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound)?; + + if transaction.status == TransactionStatus::HoldbackPending + || (transaction.status == TransactionStatus::Held + && env.ledger().timestamp() >= transaction.release_time) + { + let token_client = token::Client::new(&env, &transaction.token); + token_client.transfer( + &env.current_contract_address(), + &transaction.seller, + &(transaction.holdback_amount as i128), + ); + transaction.status = TransactionStatus::Completed; + env.storage() + .persistent() + .set(&DataKey::Transaction(transaction_id), &transaction); + + env.events().publish( + ("holdback_released",), + ( + transaction_id, + transaction.seller, + transaction.holdback_amount, + ), + ); + } + Ok(()) + } + + pub fn get_transaction(env: Env, transaction_id: u128) -> Result { + env.storage() + .persistent() + .get(&DataKey::Transaction(transaction_id)) + .ok_or(Error::TransactionNotFound) + } + + pub fn get_admin(env: Env) -> Result { + env.storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(Error::NotInitialized) + } +} diff --git a/apps/contracts/contracts/hold_back_contract/src/lib.rs b/apps/contracts/contracts/hold_back_contract/src/lib.rs index 86b85e0..6e8acf5 100644 --- a/apps/contracts/contracts/hold_back_contract/src/lib.rs +++ b/apps/contracts/contracts/hold_back_contract/src/lib.rs @@ -1,592 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, log, token, Address, Env}; - -#[contracterror] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub enum Error { - NotInitialized = 1, - InvalidAmount = 2, - InvalidHoldbackRate = 3, - InvalidBuyer = 4, - InvalidSeller = 5, - TransactionNotFound = 6, - InvalidStatus = 7, - Unauthorized = 8, - AlreadyInitialized = 9, -} - -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum TransactionStatus { - Held, - HoldbackPending, - Completed, - Cancelled, - Disputed, -} - -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Transaction { - pub buyer: Address, - pub seller: Address, - pub amount: u128, - pub token: Address, - pub holdback_rate: u32, - pub holdback_amount: u128, - pub final_amount: u128, - pub release_time: u64, - pub status: TransactionStatus, -} - -#[contracttype] -#[derive(Debug, Eq, PartialEq)] -pub enum DataKey { - Transaction(u128), - TransactionCounter, - Token, - Admin, -} - -const DAY_IN_SECONDS: u64 = 86400; - -#[contract] -pub struct HoldBackContract; - -#[contractimpl] -impl HoldBackContract { - pub fn initialize(env: Env, admin: Address) -> Result { - if env.storage().persistent().has(&DataKey::Admin) { - return Err(Error::AlreadyInitialized); - } - env.storage().persistent().set(&DataKey::Admin, &admin); - Ok(true) - } - - pub fn create_payment( - env: Env, - buyer: Address, - seller: Address, - amount: u128, - token: Address, - holdback_rate: u32, - holdback_days: u32, - ) -> Result { - buyer.require_auth(); - let admin: Address = env - .storage() - .persistent() - .get(&DataKey::Admin) - .ok_or(Error::NotInitialized)?; - - if amount == 0 { - return Err(Error::InvalidAmount); - } - if holdback_rate == 0 || holdback_rate > 100 { - return Err(Error::InvalidHoldbackRate); - } - if buyer == seller || buyer == admin || seller == admin { - return Err(Error::InvalidBuyer); - } - if buyer == token || seller == token { - return Err(Error::InvalidSeller); - } - - let holdback_amount = (amount * holdback_rate as u128) / 100; - let final_amount = amount - .checked_sub(holdback_amount) - .ok_or(Error::InvalidAmount)?; - - let token_client = token::Client::new(&env, &token); - token_client.transfer(&buyer, &env.current_contract_address(), &(amount as i128)); - - if final_amount > 0 { - token_client.transfer( - &env.current_contract_address(), - &seller, - &(final_amount as i128), - ); - } - - let transaction_id = env - .storage() - .persistent() - .get(&DataKey::TransactionCounter) - .unwrap_or(0u128) - .checked_add(1) - .ok_or(Error::InvalidAmount)?; - env.storage() - .persistent() - .set(&DataKey::TransactionCounter, &transaction_id); - - let transaction = Transaction { - buyer: buyer.clone(), - seller: seller.clone(), - amount, - token, - holdback_rate, - holdback_amount, - final_amount, - release_time: env.ledger().timestamp() + (holdback_days as u64 * DAY_IN_SECONDS), - status: TransactionStatus::Held, - }; - env.storage() - .persistent() - .set(&DataKey::Transaction(transaction_id), &transaction); - - env.events().publish( - ("transaction_created",), - (transaction_id, buyer, seller, amount, holdback_amount), - ); - - log!( - &env, - "Transaction {} created with holdback {}%", - transaction_id, - holdback_rate - ); - Ok(transaction_id) - } - - pub fn approve_release(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { - buyer.require_auth(); - let mut transaction: Transaction = env - .storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound)?; - if transaction.buyer != buyer { - return Err(Error::Unauthorized); - } - if transaction.status != TransactionStatus::Held { - return Err(Error::InvalidStatus); - } - - transaction.status = TransactionStatus::HoldbackPending; - env.storage() - .persistent() - .set(&DataKey::Transaction(transaction_id), &transaction); - - Self::release_holdback_if_due(&env, transaction_id)?; - Ok(()) - } - - pub fn initiate_dispute(env: Env, transaction_id: u128, buyer: Address) -> Result<(), Error> { - buyer.require_auth(); - let mut transaction: Transaction = env - .storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound)?; - if transaction.buyer != buyer { - return Err(Error::Unauthorized); - } - if transaction.status != TransactionStatus::Held - && transaction.status != TransactionStatus::HoldbackPending - { - return Err(Error::InvalidStatus); - } - - transaction.status = TransactionStatus::Disputed; - env.storage() - .persistent() - .set(&DataKey::Transaction(transaction_id), &transaction); - - env.events() - .publish(("dispute_initiated",), (transaction_id, buyer)); - Ok(()) - } - - pub fn resolve_dispute( - env: Env, - transaction_id: u128, - refund: bool, - admin: Address, - ) -> Result<(), Error> { - admin.require_auth(); - let stored_admin: Address = env - .storage() - .persistent() - .get(&DataKey::Admin) - .ok_or(Error::NotInitialized)?; - if admin != stored_admin { - return Err(Error::Unauthorized); - } - - let mut transaction: Transaction = env - .storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound)?; - if transaction.status != TransactionStatus::Disputed { - return Err(Error::InvalidStatus); - } - - let token_client = token::Client::new(&env, &transaction.token); - if refund { - token_client.transfer( - &env.current_contract_address(), - &transaction.buyer, - &(transaction.holdback_amount as i128), - ); - transaction.status = TransactionStatus::Cancelled; - env.events().publish( - ("holdback_refunded",), - ( - transaction_id, - transaction.buyer.clone(), - transaction.holdback_amount, - ), - ); - } else { - token_client.transfer( - &env.current_contract_address(), - &transaction.seller, - &(transaction.holdback_amount as i128), - ); - transaction.status = TransactionStatus::Completed; - env.events().publish( - ("holdback_released",), - ( - transaction_id, - transaction.seller.clone(), - transaction.holdback_amount, - ), - ); - } - env.storage() - .persistent() - .set(&DataKey::Transaction(transaction_id), &transaction); - Ok(()) - } - - pub fn check_and_release(env: Env, transaction_id: u128) -> Result<(), Error> { - let transaction: Transaction = env - .storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound)?; - if transaction.status != TransactionStatus::Held - && transaction.status != TransactionStatus::HoldbackPending - { - return Err(Error::InvalidStatus); - } - - Self::release_holdback_if_due(&env, transaction_id)?; - Ok(()) - } - - fn release_holdback_if_due(env: &Env, transaction_id: u128) -> Result<(), Error> { - let mut transaction: Transaction = env - .storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound)?; - - if transaction.status == TransactionStatus::HoldbackPending - || (transaction.status == TransactionStatus::Held - && env.ledger().timestamp() >= transaction.release_time) - { - let token_client = token::Client::new(&env, &transaction.token); - token_client.transfer( - &env.current_contract_address(), - &transaction.seller, - &(transaction.holdback_amount as i128), - ); - transaction.status = TransactionStatus::Completed; - env.storage() - .persistent() - .set(&DataKey::Transaction(transaction_id), &transaction); - - env.events().publish( - ("holdback_released",), - ( - transaction_id, - transaction.seller, - transaction.holdback_amount, - ), - ); - } - Ok(()) - } - - pub fn get_transaction(env: Env, transaction_id: u128) -> Result { - env.storage() - .persistent() - .get(&DataKey::Transaction(transaction_id)) - .ok_or(Error::TransactionNotFound) - } - - pub fn get_admin(env: Env) -> Result { - env.storage() - .persistent() - .get(&DataKey::Admin) - .ok_or(Error::NotInitialized) - } -} - -#[cfg(test)] -mod test { - use super::*; - - use soroban_sdk::{ - testutils::{Address as _, Ledger}, - token::{self, StellarAssetClient}, - Address, Env, - }; - - fn create_variables() -> (Env, Address, Address) { - let env = Env::default(); - env.mock_all_auths(); - // register the contract - let contract_address = env.register(HoldBackContract, {}); - let mocked_address = Address::generate(&env); - - (env, contract_address, mocked_address) - } - - fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient<'static>) { - let client = env.register_stellar_asset_contract_v2(admin.clone()); - ( - client.address(), - token::StellarAssetClient::new(&env, &client.address()), - ) - } - - fn create_variables_and_initialize_contract() -> (Env, HoldBackContractClient<'static>, Address) - { - let (env, contract_address, mocked_address) = create_variables(); - - let contract_instance = HoldBackContractClient::new(&env, &contract_address); - - contract_instance.initialize(&mocked_address); - - (env, contract_instance, mocked_address) - } - - fn generate_addresses(env: &Env) -> (Address, Address, Address, Address) { - ( - Address::generate(env), - Address::generate(env), - Address::generate(env), - Address::generate(env), - ) - } - //"initialize the contract" - #[test] - fn test_initialize_contract() { - let (env, contract_address, mocked_address) = create_variables(); - - let contract_instance = HoldBackContractClient::new(&env, &contract_address); - - assert_eq!(contract_instance.initialize(&mocked_address), true, "error"); - - assert_eq!(contract_instance.get_admin(), mocked_address); - } - - #[test] - fn test_create_payment() { - let (env, contract_instance, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - - let transaction_id = - contract_instance.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - let transaction = contract_instance.get_transaction(&transaction_id); - - assert_eq!(transaction.buyer, buyer); - assert_eq!(transaction.seller, seller); - assert_eq!(transaction.amount, 1000); - assert_eq!(transaction.holdback_rate, 20); - assert_eq!(transaction.holdback_amount, 200); - assert_eq!(transaction.final_amount, 800); - assert_eq!(transaction.status, TransactionStatus::Held); - assert_eq!(transaction.token, token_address); - } - - #[test] - fn test_approve_release() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.approve_release(&transaction_id, &buyer); - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Completed); - } - - #[test] - fn test_time_based_release() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &1); - - env.ledger().set_timestamp(DAY_IN_SECONDS + 1); - - contract.check_and_release(&transaction_id); - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Completed); - } - - #[test] - fn test_dispute_and_refunded() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.initiate_dispute(&transaction_id, &buyer); - contract.resolve_dispute(&transaction_id, &true, &admin); - - let transaction = contract.get_transaction(&transaction_id); - assert_eq!(transaction.status, TransactionStatus::Cancelled); - } - - #[test] - #[should_panic] - fn test_invalid_transaction() { - let (_, contract, _) = create_variables_and_initialize_contract(); - - contract.get_transaction(&999); - } - - #[test] - #[should_panic] - fn test_unauthorized_approve() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.approve_release(&transaction_id, &seller); - } - - #[test] - #[should_panic] - fn test_invalid_amount() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, _) = create_token(&env, &admin); - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - } - #[test] - #[should_panic] - fn test_invalid_holdback_rate() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - contract.create_payment(&buyer, &seller, &1000, &token_address, &0, &7); - } - - #[test] - #[should_panic] - fn test_unauthorized_dispute() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.initiate_dispute(&transaction_id, &seller); - } - - #[test] - #[should_panic] - fn test_unauthorized_resolve_dispute() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.initiate_dispute(&transaction_id, &buyer); - contract.resolve_dispute(&transaction_id, &true, &buyer); - } - - #[test] - #[should_panic] - fn test_approve_invalid_status() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - let transaction_id = - contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); - - contract.initiate_dispute(&transaction_id, &buyer); - contract.approve_release(&transaction_id, &buyer); - } - - #[test] - #[should_panic] - fn test_buyer_is_seller() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, _, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - contract.create_payment(&buyer, &buyer, &1000, &token_address, &20, &7); - } - - #[test] - #[should_panic] - fn test_buyer_is_admin() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, seller, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - contract.create_payment(&admin, &seller, &1000, &token_address, &20, &7); - } - - #[test] - #[should_panic] - fn test_seller_is_token() { - let (env, contract, admin) = create_variables_and_initialize_contract(); - - let (buyer, _, _, _) = generate_addresses(&env); - - let (token_address, token_client) = create_token(&env, &admin); - token_client.mint(&buyer, &4000); - contract.create_payment(&buyer, &token_address, &1000, &token_address, &20, &7); - } -} +mod errors; +mod entities; +mod hold_back_contract; +mod tests; diff --git a/apps/contracts/contracts/hold_back_contract/src/tests.rs b/apps/contracts/contracts/hold_back_contract/src/tests.rs new file mode 100644 index 0000000..4766110 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/src/tests.rs @@ -0,0 +1,268 @@ + + + +#[cfg(test)] +mod test { + + use crate::{entities::*, errors::*, hold_back_contract::*}; + + use soroban_sdk::{ + testutils::{Address as _, Ledger}, + token::{self, StellarAssetClient}, + Address, Env, + }; + + fn create_variables() -> (Env, Address, Address) { + let env = Env::default(); + env.mock_all_auths(); + // register the contract + let contract_address = env.register(HoldBackContract, {}); + let mocked_address = Address::generate(&env); + + (env, contract_address, mocked_address) + } + + fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient<'static>) { + let client = env.register_stellar_asset_contract_v2(admin.clone()); + ( + client.address(), + token::StellarAssetClient::new(&env, &client.address()), + ) + } + + fn create_variables_and_initialize_contract() -> (Env, HoldBackContractClient<'static>, Address) + { + let (env, contract_address, mocked_address) = create_variables(); + + let contract_instance = HoldBackContractClient::new(&env, &contract_address); + + contract_instance.initialize(&mocked_address); + + (env, contract_instance, mocked_address) + } + + fn generate_addresses(env: &Env) -> (Address, Address, Address, Address) { + ( + Address::generate(env), + Address::generate(env), + Address::generate(env), + Address::generate(env), + ) + } + //"initialize the contract" + #[test] + fn test_initialize_contract() { + let (env, contract_address, mocked_address) = create_variables(); + + let contract_instance = HoldBackContractClient::new(&env, &contract_address); + + assert_eq!(contract_instance.initialize(&mocked_address), true, "error"); + + assert_eq!(contract_instance.get_admin(), mocked_address); + } + + #[test] + fn test_create_payment() { + let (env, contract_instance, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = + contract_instance.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + let transaction = contract_instance.get_transaction(&transaction_id); + + assert_eq!(transaction.buyer, buyer); + assert_eq!(transaction.seller, seller); + assert_eq!(transaction.amount, 1000); + assert_eq!(transaction.holdback_rate, 20); + assert_eq!(transaction.holdback_amount, 200); + assert_eq!(transaction.final_amount, 800); + assert_eq!(transaction.status, TransactionStatus::Held); + assert_eq!(transaction.token, token_address); + } + + #[test] + fn test_approve_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.approve_release(&transaction_id, &buyer); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); + } + + #[test] + fn test_time_based_release() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &1); + + env.ledger().set_timestamp(DAY_IN_SECONDS + 1); + + contract.check_and_release(&transaction_id); + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Completed); + } + + #[test] + fn test_dispute_and_refunded() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &admin); + + let transaction = contract.get_transaction(&transaction_id); + assert_eq!(transaction.status, TransactionStatus::Cancelled); + } + + #[test] + #[should_panic] + fn test_invalid_transaction() { + let (_, contract, _) = create_variables_and_initialize_contract(); + + contract.get_transaction(&999); + } + + #[test] + #[should_panic] + fn test_unauthorized_approve() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.approve_release(&transaction_id, &seller); + } + + #[test] + #[should_panic] + fn test_invalid_amount() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, _) = create_token(&env, &admin); + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + } + #[test] + #[should_panic] + fn test_invalid_holdback_rate() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + contract.create_payment(&buyer, &seller, &1000, &token_address, &0, &7); + } + + #[test] + #[should_panic] + fn test_unauthorized_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &seller); + } + + #[test] + #[should_panic] + fn test_unauthorized_resolve_dispute() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.resolve_dispute(&transaction_id, &true, &buyer); + } + + #[test] + #[should_panic] + fn test_approve_invalid_status() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + let transaction_id = + contract.create_payment(&buyer, &seller, &1000, &token_address, &20, &7); + + contract.initiate_dispute(&transaction_id, &buyer); + contract.approve_release(&transaction_id, &buyer); + } + + #[test] + #[should_panic] + fn test_buyer_is_seller() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, _, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + contract.create_payment(&buyer, &buyer, &1000, &token_address, &20, &7); + } + + #[test] + #[should_panic] + fn test_buyer_is_admin() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, seller, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + contract.create_payment(&admin, &seller, &1000, &token_address, &20, &7); + } + + #[test] + #[should_panic] + fn test_seller_is_token() { + let (env, contract, admin) = create_variables_and_initialize_contract(); + + let (buyer, _, _, _) = generate_addresses(&env); + + let (token_address, token_client) = create_token(&env, &admin); + token_client.mint(&buyer, &4000); + contract.create_payment(&buyer, &token_address, &1000, &token_address, &20, &7); + } +} From 7991f50d5dcc5917baaaca8e6e69451d1076ef1a Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 08:30:48 +0100 Subject: [PATCH 5/9] done formatted --- .../contracts/contracts/hold_back_contract/src/entities.rs | 3 +-- apps/contracts/contracts/hold_back_contract/src/errors.rs | 3 +-- .../contracts/hold_back_contract/src/hold_back_contract.rs | 4 +--- apps/contracts/contracts/hold_back_contract/src/lib.rs | 2 +- apps/contracts/contracts/hold_back_contract/src/tests.rs | 7 ++----- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/contracts/contracts/hold_back_contract/src/entities.rs b/apps/contracts/contracts/hold_back_contract/src/entities.rs index 0093498..422cdb9 100644 --- a/apps/contracts/contracts/hold_back_contract/src/entities.rs +++ b/apps/contracts/contracts/hold_back_contract/src/entities.rs @@ -1,4 +1,3 @@ - use soroban_sdk::{contracttype, Address}; #[contracttype] @@ -32,4 +31,4 @@ pub enum DataKey { TransactionCounter, Token, Admin, -} \ No newline at end of file +} diff --git a/apps/contracts/contracts/hold_back_contract/src/errors.rs b/apps/contracts/contracts/hold_back_contract/src/errors.rs index a7ef003..24f7465 100644 --- a/apps/contracts/contracts/hold_back_contract/src/errors.rs +++ b/apps/contracts/contracts/hold_back_contract/src/errors.rs @@ -1,6 +1,5 @@ use soroban_sdk::contracterror; - #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub enum Error { @@ -13,4 +12,4 @@ pub enum Error { InvalidStatus = 7, Unauthorized = 8, AlreadyInitialized = 9, -} \ No newline at end of file +} diff --git a/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs b/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs index 8329d1d..e6ae1c9 100644 --- a/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs +++ b/apps/contracts/contracts/hold_back_contract/src/hold_back_contract.rs @@ -1,7 +1,5 @@ - - -use crate::errors::*; use crate::entities::*; +use crate::errors::*; use soroban_sdk::{contract, contractimpl, log, token, Address, Env}; pub const DAY_IN_SECONDS: u64 = 86400; diff --git a/apps/contracts/contracts/hold_back_contract/src/lib.rs b/apps/contracts/contracts/hold_back_contract/src/lib.rs index 6e8acf5..2701c26 100644 --- a/apps/contracts/contracts/hold_back_contract/src/lib.rs +++ b/apps/contracts/contracts/hold_back_contract/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -mod errors; mod entities; +mod errors; mod hold_back_contract; mod tests; diff --git a/apps/contracts/contracts/hold_back_contract/src/tests.rs b/apps/contracts/contracts/hold_back_contract/src/tests.rs index 4766110..fb4066e 100644 --- a/apps/contracts/contracts/hold_back_contract/src/tests.rs +++ b/apps/contracts/contracts/hold_back_contract/src/tests.rs @@ -1,10 +1,7 @@ - - - #[cfg(test)] mod test { - - use crate::{entities::*, errors::*, hold_back_contract::*}; + + use crate::{entities::*, hold_back_contract::*}; use soroban_sdk::{ testutils::{Address as _, Ledger}, From c6509e7b87c3e3e05a34573d04e87ad0b78fa2c5 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 09:06:58 +0100 Subject: [PATCH 6/9] all done --- apps/contracts/README.md | 5 + apps/contracts/TEST_DOCUMENTATION.md | 18 +++ .../contracts/hold_back_contract/README.md | 138 ++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 apps/contracts/contracts/hold_back_contract/README.md diff --git a/apps/contracts/README.md b/apps/contracts/README.md index 49e48dc..fa12617 100644 --- a/apps/contracts/README.md +++ b/apps/contracts/README.md @@ -20,6 +20,7 @@ contracts/ ├── mutual-cancellation-contract/ # Cooperative transaction cancellation ├── staggered-payment-contract/ # Time-based payment scheduling └── timelock-contract/ # Time-locked token deposits +└── hold_back_contract/ # Hold a portion of the payment temporarily ``` ## Contract Descriptions @@ -51,6 +52,10 @@ Implements time-based payment scheduling with verification steps, suitable for s ### Timelock Contract Creates time-locked token deposits that cannot be withdrawn until a specified time has elapsed, with optional clawback mechanisms. +### Holdback COntract + This contract ensures that a portion of the payment is held back temporarily after the transaction is completed, serving as a guarantee to incentivize quality and reduce potential disputes. The holdback amount is released only after a predefined period or condition, such as buyer approval or the absence of disputes. + + ## Getting Started ### Prerequisites diff --git a/apps/contracts/TEST_DOCUMENTATION.md b/apps/contracts/TEST_DOCUMENTATION.md index ee7667b..b625751 100644 --- a/apps/contracts/TEST_DOCUMENTATION.md +++ b/apps/contracts/TEST_DOCUMENTATION.md @@ -14,6 +14,7 @@ This document provides an overview of the test coverage for each smart contract 8. [Mutual Cancellation Contract](#mutual-cancellation-contract) 9. [Staggered Payment Contract](#staggered-payment-contract) 10. [Timelock Contract](#timelock-contract) +11. [Holdback Contract](#holdback-contract) ## Conditional Refund Contract @@ -149,6 +150,22 @@ The timelock contract tests verify the functionality of time-locked token deposi - Authorization checks - Event emission +## Holdback Contract + +The holdback contract tests verify the functionality of a holdback guarantee mechanism for secure marketplace transactions. + +### Test Coverage + +- Contract initialization +- Creating payments with holdback +- Buyer-approved holdback release +- Time-based holdback release +- Dispute initiation and resolution (refund and release scenarios) +- Handling invalid inputs (amount, holdback rate) +- Unauthorized access prevention +- Invalid state transitions +- Edge cases (buyer as seller/admin, non-existent transactions) + ## Running the Tests To run all tests for all contracts: @@ -183,3 +200,4 @@ cargo test -p conditional_refund_contract | Mutual Cancellation | 0 | 0 | 0 | None | | Staggered Payment | 6 | 6 | 0 | Medium | | Timelock | 17 | 17 | 0 | High | +| Holdback | 15 | 15 | 0 | High diff --git a/apps/contracts/contracts/hold_back_contract/README.md b/apps/contracts/contracts/hold_back_contract/README.md new file mode 100644 index 0000000..ab53479 --- /dev/null +++ b/apps/contracts/contracts/hold_back_contract/README.md @@ -0,0 +1,138 @@ +HoldBackContract +A Soroban smart contract for a Stellar-based marketplace implementing a holdback guarantee to ensure quality and manage disputes. +🌟 Features +💰 Holdback Payment System + +Configurable Holdback: Set percentage of payment held in escrow +Flexible Release: Time-based or buyer-approved release +Secure Escrow: Funds locked until conditions are met + +🤝 Buyer-Seller Protection + +Clear Terms: Transparent holdback rate and release period +Dispute Resolution: Admin-mediated refunds or releases +Mutual Safeguards: Protects both parties from non-compliance + +🔄 Transaction Processing + +Payment Tracking: Monitor transaction status and holdback +Automatic Release: Time-based release after deadline +Event Emission: Transparent logging of all actions + +🛡️ Risk Management + +Dispute Handling: Buyer-initiated disputes with admin resolution +Cancellation: Refund option for unresolved disputes +Deadline Enforcement: Strict holdback release timing + +🏗️ Architecture +File Structure +src/ +├── lib.rs # Main contract interface +├── error.rs # Error definitions +├── storage.rs # Data storage utilities +└── test.rs # Comprehensive test suite + +🚀 Getting Started +Prerequisites + +Rust 1.70+ +Soroban CLI +Stellar account with testnet tokens + +Building the Contract +# Build the contract +cargo build --target wasm32-unknown-unknown --release + +# Build with Soroban CLI +soroban contract build + +Testing +# Run the test suite +cargo test + +📖 Usage +1. Initialize Contract +contract.initialize(env, admin_address) + +2. Create Payment +let transaction_id = contract.create_payment( + env, + buyer_address, + seller_address, + token_address, + amount, + holdback_rate, + holdback_days, +); + +3. Approve Release (Buyer) +contract.approve_release(env, transaction_id, buyer_address); + +4. Initiate Dispute (Buyer) +contract.initiate_dispute(env, transaction_id, buyer_address); + +5. Resolve Dispute (Admin) +contract.resolve_dispute(env, transaction_id, refund, admin_address); + +6. Check and Release +contract.check_and_release(env, transaction_id); + +🔄 Contract Workflow + +Payment Creation: Buyer initiates payment with holdback terms +Holdback Period: Funds held until release conditions met +Release Options: Buyer approves early release or time-based release +Dispute Process: Buyer initiates dispute; admin resolves with refund or release +Completion: Transaction finalized or cancelled + +📊 Contract States + + + +Status +Description +Available Actions + + + +Held +Payment created, holdback in escrow +Approve, Dispute, Check/Release + + +HoldbackPending +Buyer approved release +Check/Release + + +Disputed +Dispute initiated +Resolve (Admin) + + +Completed +Holdback released +View Only + + +Cancelled +Funds refunded +View Only + + +🛡️ Security Features + +Authorization: require_auth() for all sensitive actions +State Validation: Strict state machine for valid transitions +Fund Protection: Escrow ensures secure holdback management +Event Logging: Transparent events for all major actions + +📚 Test Coverage +Tests cover: + +Contract initialization +Payment creation with holdback +Buyer-approved and time-based releases +Dispute initiation and resolution +Edge cases (invalid inputs, unauthorized actions, non-existent transactions) From a9764fcc4061623befa1b3287e2bfbd4539d5880 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 09:10:40 +0100 Subject: [PATCH 7/9] all formatted --- .../src/escrow_logic.rs | 5 +- .../auto-release-escrow-contract/src/lib.rs | 13 +-- .../auto-release-escrow-contract/src/test.rs | 98 ++++++++++++++----- .../src/auction_logic.rs | 4 +- .../automated-auction-contract/src/lib.rs | 11 +-- .../automated-auction-contract/src/test.rs | 82 +++++++++++++--- .../src/contract.rs | 2 +- .../conditional-refund-contract/src/events.rs | 2 +- .../conditional-refund-contract/src/lib.rs | 2 +- .../src/refund_storage.rs | 2 +- .../conditional-refund-contract/src/test.rs | 2 +- .../src/contract.rs | 2 +- .../src/escrow_storage.rs | 2 +- .../escrow-arbitration-contract/src/events.rs | 2 +- .../escrow-arbitration-contract/src/lib.rs | 2 +- .../escrow-arbitration-contract/src/test.rs | 2 +- .../src/contract.rs | 2 +- .../milestone-payment-contract/src/events.rs | 2 +- .../milestone-payment-contract/src/lib.rs | 2 +- .../src/milestone_storage.rs | 2 +- .../milestone-payment-contract/src/test.rs | 2 +- .../src/deposit_logic.rs | 14 +-- .../partial-payment-contract/src/event.rs | 10 +- .../partial-payment-contract/src/lib.rs | 14 +-- .../partial-payment-contract/src/test.rs | 47 ++++++--- 25 files changed, 218 insertions(+), 110 deletions(-) diff --git a/apps/contracts/contracts/auto-release-escrow-contract/src/escrow_logic.rs b/apps/contracts/contracts/auto-release-escrow-contract/src/escrow_logic.rs index d62951e..cb83b24 100644 --- a/apps/contracts/contracts/auto-release-escrow-contract/src/escrow_logic.rs +++ b/apps/contracts/contracts/auto-release-escrow-contract/src/escrow_logic.rs @@ -28,7 +28,6 @@ pub fn set_admin(env: &Env, admin: Address, new_admin: Address) -> Result<(), Co Ok(()) } - /// Creates a new escrow agreement and immediately locks the buyer's funds. pub fn create_escrow( env: &Env, @@ -90,7 +89,6 @@ pub fn confirm_receipt(env: &Env, buyer: Address, escrow_id: u64) -> Result<(), Ok(()) } - /// Releases funds to the seller if the release time has passed OR the buyer has confirmed. pub fn release_funds(env: &Env, escrow_id: u64) -> Result<(), ContractError> { let mut escrow = storage::get_escrow(env, escrow_id)?; @@ -99,7 +97,8 @@ pub fn release_funds(env: &Env, escrow_id: u64) -> Result<(), ContractError> { return Err(ContractError::EscrowNotActive); } - let can_release = env.ledger().timestamp() >= escrow.release_timestamp || escrow.buyer_confirmed; + let can_release = + env.ledger().timestamp() >= escrow.release_timestamp || escrow.buyer_confirmed; if !can_release { return Err(ContractError::ReleaseTimeNotPassed); } diff --git a/apps/contracts/contracts/auto-release-escrow-contract/src/lib.rs b/apps/contracts/contracts/auto-release-escrow-contract/src/lib.rs index caa6e30..1c59ae4 100644 --- a/apps/contracts/contracts/auto-release-escrow-contract/src/lib.rs +++ b/apps/contracts/contracts/auto-release-escrow-contract/src/lib.rs @@ -1,18 +1,15 @@ #![no_std] mod error; -mod event; mod escrow_logic; +mod event; mod storage; #[cfg(test)] mod test; use soroban_sdk::{contract, contractimpl, Address, Env, String}; -use crate::{ - error::ContractError, - storage::Escrow, -}; +use crate::{error::ContractError, storage::Escrow}; #[contract] pub struct AutoReleaseEscrowContract; @@ -50,11 +47,7 @@ impl AutoReleaseEscrowContract { /// Allows the buyer to confirm they have received the goods/service, /// enabling an early release of funds. - pub fn confirm_receipt( - env: Env, - buyer: Address, - escrow_id: u64, - ) -> Result<(), ContractError> { + pub fn confirm_receipt(env: Env, buyer: Address, escrow_id: u64) -> Result<(), ContractError> { escrow_logic::confirm_receipt(&env, buyer, escrow_id) } diff --git a/apps/contracts/contracts/auto-release-escrow-contract/src/test.rs b/apps/contracts/contracts/auto-release-escrow-contract/src/test.rs index f58e2c0..db852b0 100644 --- a/apps/contracts/contracts/auto-release-escrow-contract/src/test.rs +++ b/apps/contracts/contracts/auto-release-escrow-contract/src/test.rs @@ -12,7 +12,9 @@ fn create_token_contract<'a>( env: &Env, admin: &Address, ) -> (token::Client<'a>, TokenAdminClient<'a>) { - let token_address = env.register_stellar_asset_contract_v2(admin.clone()).address(); + let token_address = env + .register_stellar_asset_contract_v2(admin.clone()) + .address(); ( token::Client::new(env, &token_address), TokenAdminClient::new(env, &token_address), @@ -82,12 +84,18 @@ fn test_set_admin() { // Verify the new admin can perform admin actions, like resolving a dispute let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &100, &test.token.address, &(test.env.ledger().timestamp() + 100) + &test.buyer, + &test.seller, + &100, + &test.token.address, + &(test.env.ledger().timestamp() + 100), ); - test.contract.dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); + test.contract + .dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); // The new admin should now be able to resolve it - test.contract.resolve_dispute_and_refund(&new_admin, &escrow_id); + test.contract + .resolve_dispute_and_refund(&new_admin, &escrow_id); let escrow = test.contract.get_escrow(&escrow_id); assert_eq!(escrow.status, EscrowStatus::Refunded); } @@ -102,7 +110,6 @@ fn test_set_admin_unauthorized() { assert_eq!(result, Err(Ok(ContractError::NotAdmin))); } - #[test] fn test_create_escrow_and_fund_locking() { let test = EscrowTest::setup(); @@ -135,7 +142,11 @@ fn test_release_funds_after_time_elapses() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 10; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); // Advance time past the release timestamp @@ -156,7 +167,11 @@ fn test_confirm_receipt_and_early_release() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; // 1 hour let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); // Buyer confirms receipt @@ -178,19 +193,25 @@ fn test_dispute_and_admin_refund() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); - + // Buyer disputes the escrow let reason = String::from_str(&test.env, "Item not as described"); - test.contract.dispute_escrow(&test.buyer, &escrow_id, &reason); + test.contract + .dispute_escrow(&test.buyer, &escrow_id, &reason); let escrow_after_dispute = test.contract.get_escrow(&escrow_id); assert_eq!(escrow_after_dispute.status, EscrowStatus::Disputed); assert_eq!(escrow_after_dispute.dispute_reason, Some(reason)); // Admin resolves the dispute and refunds the buyer - test.contract.resolve_dispute_and_refund(&test.admin, &escrow_id); + test.contract + .resolve_dispute_and_refund(&test.admin, &escrow_id); let escrow_after_refund = test.contract.get_escrow(&escrow_id); assert_eq!(escrow_after_refund.status, EscrowStatus::Refunded); @@ -205,7 +226,11 @@ fn test_release_fails_before_time() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); let result = test.contract.try_release_funds(&escrow_id); @@ -217,10 +242,15 @@ fn test_release_fails_if_disputed() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); - test.contract.dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); - + test.contract + .dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); + let result = test.contract.try_release_funds(&escrow_id); assert_eq!(result, Err(Ok(ContractError::EscrowNotActive))); } @@ -230,10 +260,16 @@ fn test_dispute_fails_if_not_buyer() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); - let result = test.contract.try_dispute_escrow(&test.seller, &escrow_id, &"reason".into_val(&test.env)); + let result = + test.contract + .try_dispute_escrow(&test.seller, &escrow_id, &"reason".into_val(&test.env)); assert_eq!(result, Err(Ok(ContractError::NotBuyer))); } @@ -242,11 +278,18 @@ fn test_dispute_fails_if_already_disputed() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); - test.contract.dispute_escrow(&test.buyer, &escrow_id, &"reason1".into_val(&test.env)); - - let result = test.contract.try_dispute_escrow(&test.buyer, &escrow_id, &"reason2".into_val(&test.env)); + test.contract + .dispute_escrow(&test.buyer, &escrow_id, &"reason1".into_val(&test.env)); + + let result = + test.contract + .try_dispute_escrow(&test.buyer, &escrow_id, &"reason2".into_val(&test.env)); assert_eq!(result, Err(Ok(ContractError::EscrowAlreadyDisputed))); } @@ -255,11 +298,18 @@ fn test_resolve_dispute_fails_if_not_admin() { let test = EscrowTest::setup(); let release_timestamp = test.env.ledger().timestamp() + 3600; let escrow_id = test.contract.create_escrow( - &test.buyer, &test.seller, &1000, &test.token.address, &release_timestamp + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &release_timestamp, ); - test.contract.dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); + test.contract + .dispute_escrow(&test.buyer, &escrow_id, &"reason".into_val(&test.env)); // Seller (not admin) tries to resolve - let result = test.contract.try_resolve_dispute_and_refund(&test.seller, &escrow_id); + let result = test + .contract + .try_resolve_dispute_and_refund(&test.seller, &escrow_id); assert_eq!(result, Err(Ok(ContractError::NotAdmin))); } diff --git a/apps/contracts/contracts/automated-auction-contract/src/auction_logic.rs b/apps/contracts/contracts/automated-auction-contract/src/auction_logic.rs index 626872a..783b0c0 100644 --- a/apps/contracts/contracts/automated-auction-contract/src/auction_logic.rs +++ b/apps/contracts/contracts/automated-auction-contract/src/auction_logic.rs @@ -66,7 +66,7 @@ pub fn place_bid( if bid_amount < auction.highest_bid + auction.min_bid_increment { return Err(ContractError::BidTooLow); } - + // If there was a previous bidder, refund their bid. if let Some(previous_bidder) = auction.highest_bidder { let token_client = token::Client::new(env, &auction.payment_token); @@ -139,7 +139,7 @@ pub fn cancel_auction(env: &Env, seller: Address, auction_id: u64) -> Result<(), if auction.status == AuctionStatus::Active { return Err(ContractError::AuctionHasBids); } - + if auction.status == AuctionStatus::Closed { return Err(ContractError::AuctionHasEnded); } diff --git a/apps/contracts/contracts/automated-auction-contract/src/lib.rs b/apps/contracts/contracts/automated-auction-contract/src/lib.rs index 8dfdf38..094039a 100644 --- a/apps/contracts/contracts/automated-auction-contract/src/lib.rs +++ b/apps/contracts/contracts/automated-auction-contract/src/lib.rs @@ -9,10 +9,7 @@ mod test; use soroban_sdk::{contract, contractimpl, Address, Env, String}; -use crate::{ - error::ContractError, - storage::Auction, -}; +use crate::{error::ContractError, storage::Auction}; #[contract] pub struct AutomatedAuctionContract; @@ -56,11 +53,7 @@ impl AutomatedAuctionContract { } /// Allows the seller to cancel an auction before any bids have been placed. - pub fn cancel_auction( - env: Env, - seller: Address, - auction_id: u64, - ) -> Result<(), ContractError> { + pub fn cancel_auction(env: Env, seller: Address, auction_id: u64) -> Result<(), ContractError> { auction_logic::cancel_auction(&env, seller, auction_id) } diff --git a/apps/contracts/contracts/automated-auction-contract/src/test.rs b/apps/contracts/contracts/automated-auction-contract/src/test.rs index c4ce592..cc45714 100644 --- a/apps/contracts/contracts/automated-auction-contract/src/test.rs +++ b/apps/contracts/contracts/automated-auction-contract/src/test.rs @@ -15,7 +15,9 @@ fn create_token_contract<'a>( env: &Env, admin: &Address, ) -> (token::Client<'a>, TokenAdminClient<'a>) { - let token_address = env.register_stellar_asset_contract_v2(admin.clone()).address(); + let token_address = env + .register_stellar_asset_contract_v2(admin.clone()) + .address(); ( token::Client::new(env, &token_address), TokenAdminClient::new(env, &token_address), @@ -90,7 +92,12 @@ fn test_create_auction() { fn test_place_bid_success() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &3600, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &3600, + &test.token.address, ); test.contract.place_bid(&test.bidder1, &auction_id, &110); @@ -109,7 +116,12 @@ fn test_place_bid_success() { fn test_outbid_and_refund() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &3600, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &3600, + &test.token.address, ); // Bidder 1 places a bid @@ -134,25 +146,36 @@ fn test_outbid_and_refund() { fn test_place_bid_errors() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &10, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &10, + &test.token.address, ); // Bid too low (not meeting minimum increment) - let result_low = test.contract.try_place_bid(&test.bidder1, &auction_id, &105); + let result_low = test + .contract + .try_place_bid(&test.bidder1, &auction_id, &105); assert_eq!(result_low, Err(Ok(ContractError::BidTooLow))); // Bid exactly at minimum increment (should succeed) test.contract.place_bid(&test.bidder1, &auction_id, &110); // Bid too low (equal to current highest) - let result_equal = test.contract.try_place_bid(&test.bidder2, &auction_id, &110); + let result_equal = test + .contract + .try_place_bid(&test.bidder2, &auction_id, &110); assert_eq!(result_equal, Err(Ok(ContractError::BidTooLow))); // Expire the auction test.env.ledger().with_mut(|l| l.timestamp = 20); - + // Bid after auction ended - let result_ended = test.contract.try_place_bid(&test.bidder2, &auction_id, &120); + let result_ended = test + .contract + .try_place_bid(&test.bidder2, &auction_id, &120); assert_eq!(result_ended, Err(Ok(ContractError::AuctionHasEnded))); } @@ -160,7 +183,12 @@ fn test_place_bid_errors() { fn test_close_auction_with_winner() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &10, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &10, + &test.token.address, ); test.contract.place_bid(&test.bidder1, &auction_id, &150); @@ -182,7 +210,12 @@ fn test_close_auction_with_winner() { fn test_close_auction_no_bids() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &10, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &10, + &test.token.address, ); // Expire the auction @@ -203,7 +236,12 @@ fn test_close_auction_no_bids() { fn test_close_auction_not_ended() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &3600, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &3600, + &test.token.address, ); let result = test.contract.try_close_auction(&auction_id); @@ -214,7 +252,12 @@ fn test_close_auction_not_ended() { fn test_cancel_auction() { let test = AuctionTest::setup(); let auction_id = test.contract.create_auction( - &test.seller, &"Item".into_val(&test.env), &100, &10, &3600, &test.token.address + &test.seller, + &"Item".into_val(&test.env), + &100, + &10, + &3600, + &test.token.address, ); // Seller successfully cancels @@ -224,13 +267,22 @@ fn test_cancel_auction() { // Non-seller tries to cancel let auction_id_2 = test.contract.create_auction( - &test.seller, &"Item 2".into_val(&test.env), &100, &10, &3600, &test.token.address + &test.seller, + &"Item 2".into_val(&test.env), + &100, + &10, + &3600, + &test.token.address, ); - let result_auth = test.contract.try_cancel_auction(&test.bidder1, &auction_id_2); + let result_auth = test + .contract + .try_cancel_auction(&test.bidder1, &auction_id_2); assert_eq!(result_auth, Err(Ok(ContractError::NotAuctionSeller))); // Seller tries to cancel after a bid test.contract.place_bid(&test.bidder1, &auction_id_2, &110); - let result_bids = test.contract.try_cancel_auction(&test.seller, &auction_id_2); + let result_bids = test + .contract + .try_cancel_auction(&test.seller, &auction_id_2); assert_eq!(result_bids, Err(Ok(ContractError::AuctionHasBids))); } diff --git a/apps/contracts/contracts/conditional-refund-contract/src/contract.rs b/apps/contracts/contracts/conditional-refund-contract/src/contract.rs index e6e6332..264de1c 100644 --- a/apps/contracts/contracts/conditional-refund-contract/src/contract.rs +++ b/apps/contracts/contracts/conditional-refund-contract/src/contract.rs @@ -2,7 +2,7 @@ use crate::error::ContractError; use crate::events::*; use crate::refund_storage::*; use crate::storage; -use soroban_sdk::{Address, Env, String, Vec, token}; +use soroban_sdk::{token, Address, Env, String, Vec}; pub fn create_refund_contract( env: &Env, diff --git a/apps/contracts/contracts/conditional-refund-contract/src/events.rs b/apps/contracts/contracts/conditional-refund-contract/src/events.rs index ff03f7d..11fef47 100644 --- a/apps/contracts/contracts/conditional-refund-contract/src/events.rs +++ b/apps/contracts/contracts/conditional-refund-contract/src/events.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, contracttype}; +use soroban_sdk::{contracttype, Address, Env, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/conditional-refund-contract/src/lib.rs b/apps/contracts/contracts/conditional-refund-contract/src/lib.rs index 53c8afd..4464b09 100644 --- a/apps/contracts/contracts/conditional-refund-contract/src/lib.rs +++ b/apps/contracts/contracts/conditional-refund-contract/src/lib.rs @@ -7,7 +7,7 @@ mod refund_storage; mod storage; mod test; -use soroban_sdk::{Address, Env, String, contract, contractimpl}; +use soroban_sdk::{contract, contractimpl, Address, Env, String}; pub use contract::*; pub use error::*; diff --git a/apps/contracts/contracts/conditional-refund-contract/src/refund_storage.rs b/apps/contracts/contracts/conditional-refund-contract/src/refund_storage.rs index 481f278..8c2748e 100644 --- a/apps/contracts/contracts/conditional-refund-contract/src/refund_storage.rs +++ b/apps/contracts/contracts/conditional-refund-contract/src/refund_storage.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, Symbol, Vec, contracttype, symbol_short}; +use soroban_sdk::{contracttype, symbol_short, Address, Env, String, Symbol, Vec}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/conditional-refund-contract/src/test.rs b/apps/contracts/contracts/conditional-refund-contract/src/test.rs index 31f7660..ec4aacb 100644 --- a/apps/contracts/contracts/conditional-refund-contract/src/test.rs +++ b/apps/contracts/contracts/conditional-refund-contract/src/test.rs @@ -4,7 +4,7 @@ extern crate std; use crate::error::ContractError; use crate::refund_storage::ContractStatus; use crate::{ConditionalRefundContract, ConditionalRefundContractClient}; -use soroban_sdk::{Address, Env, String, testutils::Address as _, testutils::Ledger, token}; +use soroban_sdk::{testutils::Address as _, testutils::Ledger, token, Address, Env, String}; use token::Client as TokenClient; use token::StellarAssetClient as TokenAdminClient; diff --git a/apps/contracts/contracts/escrow-arbitration-contract/src/contract.rs b/apps/contracts/contracts/escrow-arbitration-contract/src/contract.rs index 9b9b378..4526a8d 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/src/contract.rs +++ b/apps/contracts/contracts/escrow-arbitration-contract/src/contract.rs @@ -2,7 +2,7 @@ use crate::error::ContractError; use crate::escrow_storage; use crate::escrow_storage::*; use crate::events::*; -use soroban_sdk::{Address, Env, String, Vec, token}; +use soroban_sdk::{token, Address, Env, String, Vec}; pub fn create_escrow( env: &Env, diff --git a/apps/contracts/contracts/escrow-arbitration-contract/src/escrow_storage.rs b/apps/contracts/contracts/escrow-arbitration-contract/src/escrow_storage.rs index 839b1f1..e3bacca 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/src/escrow_storage.rs +++ b/apps/contracts/contracts/escrow-arbitration-contract/src/escrow_storage.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, Symbol, Vec, contracttype, symbol_short}; +use soroban_sdk::{contracttype, symbol_short, Address, Env, String, Symbol, Vec}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/escrow-arbitration-contract/src/events.rs b/apps/contracts/contracts/escrow-arbitration-contract/src/events.rs index 4fcf25e..a44f115 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/src/events.rs +++ b/apps/contracts/contracts/escrow-arbitration-contract/src/events.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, contracttype}; +use soroban_sdk::{contracttype, Address, Env, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/escrow-arbitration-contract/src/lib.rs b/apps/contracts/contracts/escrow-arbitration-contract/src/lib.rs index 054ab32..04f7a61 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/src/lib.rs +++ b/apps/contracts/contracts/escrow-arbitration-contract/src/lib.rs @@ -7,7 +7,7 @@ mod events; mod storage; mod test; -use soroban_sdk::{Address, Env, String, contract, contractimpl}; +use soroban_sdk::{contract, contractimpl, Address, Env, String}; pub use contract::*; pub use error::*; diff --git a/apps/contracts/contracts/escrow-arbitration-contract/src/test.rs b/apps/contracts/contracts/escrow-arbitration-contract/src/test.rs index 974daf9..4e20e9d 100644 --- a/apps/contracts/contracts/escrow-arbitration-contract/src/test.rs +++ b/apps/contracts/contracts/escrow-arbitration-contract/src/test.rs @@ -4,7 +4,7 @@ extern crate std; use crate::error::ContractError; use crate::escrow_storage::EscrowStatus; use crate::{EscrowArbitrationContract, EscrowArbitrationContractClient}; -use soroban_sdk::{Address, Env, String, testutils::Address as _, token}; +use soroban_sdk::{testutils::Address as _, token, Address, Env, String}; use token::Client as TokenClient; use token::StellarAssetClient as TokenAdminClient; diff --git a/apps/contracts/contracts/milestone-payment-contract/src/contract.rs b/apps/contracts/contracts/milestone-payment-contract/src/contract.rs index 4ad6976..5767b9a 100644 --- a/apps/contracts/contracts/milestone-payment-contract/src/contract.rs +++ b/apps/contracts/contracts/milestone-payment-contract/src/contract.rs @@ -3,7 +3,7 @@ use crate::events::*; use crate::milestone_storage; use crate::milestone_storage::*; use crate::storage; -use soroban_sdk::{Address, Env, String, Vec, token}; +use soroban_sdk::{token, Address, Env, String, Vec}; // Validate milestone data fn validate_milestones( diff --git a/apps/contracts/contracts/milestone-payment-contract/src/events.rs b/apps/contracts/contracts/milestone-payment-contract/src/events.rs index 3aca364..f47593d 100644 --- a/apps/contracts/contracts/milestone-payment-contract/src/events.rs +++ b/apps/contracts/contracts/milestone-payment-contract/src/events.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, contracttype}; +use soroban_sdk::{contracttype, Address, Env, String}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/milestone-payment-contract/src/lib.rs b/apps/contracts/contracts/milestone-payment-contract/src/lib.rs index 3a96af3..a47b9ce 100644 --- a/apps/contracts/contracts/milestone-payment-contract/src/lib.rs +++ b/apps/contracts/contracts/milestone-payment-contract/src/lib.rs @@ -7,7 +7,7 @@ mod milestone_storage; mod storage; mod test; -use soroban_sdk::{Address, Env, Vec, contract, contractimpl}; +use soroban_sdk::{contract, contractimpl, Address, Env, Vec}; pub use contract::*; pub use error::*; diff --git a/apps/contracts/contracts/milestone-payment-contract/src/milestone_storage.rs b/apps/contracts/contracts/milestone-payment-contract/src/milestone_storage.rs index 0341b35..c7f0590 100644 --- a/apps/contracts/contracts/milestone-payment-contract/src/milestone_storage.rs +++ b/apps/contracts/contracts/milestone-payment-contract/src/milestone_storage.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, String, Symbol, Vec, contracttype, symbol_short}; +use soroban_sdk::{contracttype, symbol_short, Address, Env, String, Symbol, Vec}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/contracts/milestone-payment-contract/src/test.rs b/apps/contracts/contracts/milestone-payment-contract/src/test.rs index 47fb5f8..0f88043 100644 --- a/apps/contracts/contracts/milestone-payment-contract/src/test.rs +++ b/apps/contracts/contracts/milestone-payment-contract/src/test.rs @@ -4,7 +4,7 @@ extern crate std; use crate::error::ContractError; use crate::milestone_storage::{ContractStatus, MilestoneData, MilestoneStatus}; use crate::{MilestonePaymentContract, MilestonePaymentContractClient}; -use soroban_sdk::{Address, Env, String, testutils::Address as _, token, vec}; +use soroban_sdk::{testutils::Address as _, token, vec, Address, Env, String}; use token::Client as TokenClient; use token::StellarAssetClient as TokenAdminClient; diff --git a/apps/contracts/contracts/partial-payment-contract/src/deposit_logic.rs b/apps/contracts/contracts/partial-payment-contract/src/deposit_logic.rs index 79b0d47..263f85b 100644 --- a/apps/contracts/contracts/partial-payment-contract/src/deposit_logic.rs +++ b/apps/contracts/contracts/partial-payment-contract/src/deposit_logic.rs @@ -113,11 +113,7 @@ pub fn claim_payment(env: &Env, seller: Address, transaction_id: u64) -> Result< } /// Allows the buyer to get a refund if the deadline has passed. -pub fn request_refund( - env: &Env, - buyer: Address, - transaction_id: u64, -) -> Result<(), ContractError> { +pub fn request_refund(env: &Env, buyer: Address, transaction_id: u64) -> Result<(), ContractError> { buyer.require_auth(); let mut transaction = storage::get_transaction(env, transaction_id)?; @@ -128,7 +124,9 @@ pub fn request_refund( if env.ledger().timestamp() <= transaction.deadline { return Err(ContractError::DeadlineNotPassed); } - if transaction.status == TransactionStatus::Funded || transaction.status == TransactionStatus::Completed { + if transaction.status == TransactionStatus::Funded + || transaction.status == TransactionStatus::Completed + { // If fully funded, the deal is locked in. No refunds. return Err(ContractError::TransactionFullyFunded); } @@ -160,7 +158,9 @@ pub fn cancel_transaction( if transaction.buyer != canceller && transaction.seller != canceller { return Err(ContractError::NotParticipant); } - if transaction.status == TransactionStatus::Funded || transaction.status == TransactionStatus::Completed { + if transaction.status == TransactionStatus::Funded + || transaction.status == TransactionStatus::Completed + { return Err(ContractError::TransactionFullyFunded); } diff --git a/apps/contracts/contracts/partial-payment-contract/src/event.rs b/apps/contracts/contracts/partial-payment-contract/src/event.rs index 795378f..833c645 100644 --- a/apps/contracts/contracts/partial-payment-contract/src/event.rs +++ b/apps/contracts/contracts/partial-payment-contract/src/event.rs @@ -1,7 +1,13 @@ -use soroban_sdk::{symbol_short, Address, Env,}; +use soroban_sdk::{symbol_short, Address, Env}; /// Emits an event when a new transaction is started. -pub fn transaction_started(env: &Env, transaction_id: u64, buyer: &Address, seller: &Address, total_amount: i128) { +pub fn transaction_started( + env: &Env, + transaction_id: u64, + buyer: &Address, + seller: &Address, + total_amount: i128, +) { let topics = (symbol_short!("started"), buyer.clone(), seller.clone()); let data = (transaction_id, total_amount); env.events().publish(topics, data); diff --git a/apps/contracts/contracts/partial-payment-contract/src/lib.rs b/apps/contracts/contracts/partial-payment-contract/src/lib.rs index 5a65d85..7c39b17 100644 --- a/apps/contracts/contracts/partial-payment-contract/src/lib.rs +++ b/apps/contracts/contracts/partial-payment-contract/src/lib.rs @@ -9,10 +9,7 @@ mod test; use soroban_sdk::{contract, contractimpl, Address, Env}; -use crate::{ - error::ContractError, - storage::Transaction, -}; +use crate::{error::ContractError, storage::Transaction}; #[contract] pub struct PartialPaymentContract; @@ -28,14 +25,7 @@ impl PartialPaymentContract { payment_token: Address, deadline: u64, // A timestamp by which the full payment must be completed ) -> Result { - deposit_logic::start_transaction( - &env, - buyer, - seller, - total_amount, - payment_token, - deadline, - ) + deposit_logic::start_transaction(&env, buyer, seller, total_amount, payment_token, deadline) } /// Allows a buyer to make a partial deposit towards a transaction. diff --git a/apps/contracts/contracts/partial-payment-contract/src/test.rs b/apps/contracts/contracts/partial-payment-contract/src/test.rs index 766463f..f60b382 100644 --- a/apps/contracts/contracts/partial-payment-contract/src/test.rs +++ b/apps/contracts/contracts/partial-payment-contract/src/test.rs @@ -8,12 +8,13 @@ use soroban_sdk::{ }; use token::StellarAssetClient as TokenAdminClient; - fn create_token_contract<'a>( env: &Env, admin: &Address, ) -> (token::Client<'a>, TokenAdminClient<'a>) { - let token_address = env.register_stellar_asset_contract_v2(admin.clone()).address(); + let token_address = env + .register_stellar_asset_contract_v2(admin.clone()) + .address(); ( token::Client::new(env, &token_address), TokenAdminClient::new(env, &token_address), @@ -87,7 +88,11 @@ fn test_make_partial_and_full_deposit() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 3600; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); // First partial deposit @@ -110,16 +115,20 @@ fn test_claim_payment() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 3600; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); test.contract.make_deposit(&test.buyer, &tx_id, &1000); // Seller claims payment test.contract.claim_payment(&test.seller, &tx_id); - + let tx = test.contract.get_transaction(&tx_id); assert_eq!(tx.status, TransactionStatus::Completed); - + // Check balances assert_eq!(test.token.balance(&test.seller), 1000); assert_eq!(test.token.balance(&test.contract.address), 0); @@ -131,7 +140,11 @@ fn test_claim_payment_errors() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 3600; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); test.contract.make_deposit(&test.buyer, &tx_id, &500); @@ -149,7 +162,11 @@ fn test_request_refund_after_deadline() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 10; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); test.contract.make_deposit(&test.buyer, &tx_id, &300); @@ -172,7 +189,11 @@ fn test_request_refund_errors() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 3600; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); test.contract.make_deposit(&test.buyer, &tx_id, &300); @@ -182,7 +203,7 @@ fn test_request_refund_errors() { // Fully fund the transaction test.contract.make_deposit(&test.buyer, &tx_id, &700); - + // Advance time past the deadline test.env.ledger().with_mut(|l| l.timestamp = deadline + 10); @@ -196,7 +217,11 @@ fn test_cancel_transaction() { let test = DepositTest::setup(); let deadline = test.env.ledger().timestamp() + 3600; let tx_id = test.contract.start_transaction( - &test.buyer, &test.seller, &1000, &test.token.address, &deadline + &test.buyer, + &test.seller, + &1000, + &test.token.address, + &deadline, ); test.contract.make_deposit(&test.buyer, &tx_id, &200); From 84fa6fb42dfdef3c4fecf14ead7d66b8f1edc4e5 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 09:18:35 +0100 Subject: [PATCH 8/9] kk --- apps/contracts/TEST_DOCUMENTATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/contracts/TEST_DOCUMENTATION.md b/apps/contracts/TEST_DOCUMENTATION.md index b625751..ede85f1 100644 --- a/apps/contracts/TEST_DOCUMENTATION.md +++ b/apps/contracts/TEST_DOCUMENTATION.md @@ -173,7 +173,7 @@ To run all tests for all contracts: ```bash cargo test ``` - + To run tests for a specific contract: ```bash From baacf9a4030fe4f2c5901fddf1faa73996da9012 Mon Sep 17 00:00:00 2001 From: blurbeast Date: Thu, 7 Aug 2025 10:10:10 +0100 Subject: [PATCH 9/9] done --- .../contracts/contracts/auto-release-escrow-contract/Cargo.toml | 2 +- .../contracts/automatic-marketplace-fee-deduction/src/test.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml b/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml index 97e2446..6f618f4 100644 --- a/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml +++ b/apps/contracts/contracts/auto-release-escrow-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "auto-release-escrow-contract" version = "0.1.0" -edition = "2021" +edition = "2024" [lib] crate-type = ["cdylib"] diff --git a/apps/contracts/contracts/automatic-marketplace-fee-deduction/src/test.rs b/apps/contracts/contracts/automatic-marketplace-fee-deduction/src/test.rs index 57d7036..192fdfe 100644 --- a/apps/contracts/contracts/automatic-marketplace-fee-deduction/src/test.rs +++ b/apps/contracts/contracts/automatic-marketplace-fee-deduction/src/test.rs @@ -43,7 +43,7 @@ impl TestContext { } } - fn get_client(&self) -> MarketplaceFeeContractClient { + fn get_client(&self) -> MarketplaceFeeContractClient<'static> { MarketplaceFeeContractClient::new(&self.env, &self.contract_id) }