From 58853dd040156f038d8f9c0f43d00298e3914e23 Mon Sep 17 00:00:00 2001 From: Lynndabel Date: Wed, 24 Jun 2026 13:53:05 +0100 Subject: [PATCH 1/3] feat(trustflow): add Milestone, EscrowInitialized event and update EscrowRecord --- contracts/trustflow/src/lib.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/contracts/trustflow/src/lib.rs b/contracts/trustflow/src/lib.rs index 148e26a..9821263 100644 --- a/contracts/trustflow/src/lib.rs +++ b/contracts/trustflow/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(test), no_std)] use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, token, vec, Address, Env, String, Vec, + contract, contracterror, contractevent, contractimpl, contracttype, symbol_short, token, Address, Env, String, Vec, }; /// Slash rate in basis points applied to minority voters (10% = 1000 bps) @@ -24,6 +24,21 @@ pub enum TrustFlowError { AlreadyVoted = 7, InsufficientStake = 8, NoVotesCast = 9, + MilestoneAmountMismatch = 10, +} + +// --------------------------------------------------------------------------- +// Events +// --------------------------------------------------------------------------- + +#[contractevent] +#[contracttype] +#[derive(Clone, Debug)] +pub struct EscrowInitialized { + pub escrow_id: u64, + pub depositor: Address, + pub beneficiary: Address, + pub amount: i128, } // --------------------------------------------------------------------------- @@ -76,6 +91,14 @@ pub enum EscrowStatus { Settled, } +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Milestone { + pub label: String, + pub amount: i128, + pub approved: bool, +} + #[contracttype] #[derive(Clone, Debug)] pub struct EscrowRecord { @@ -84,6 +107,7 @@ pub struct EscrowRecord { pub beneficiary: Address, pub amount: i128, pub status: EscrowStatus, + pub milestones: Vec, } #[contracttype] From 65c06df5c78bd5855829f78febbd5b808717b1b3 Mon Sep 17 00:00:00 2001 From: Lynndabel Date: Wed, 24 Jun 2026 13:53:30 +0100 Subject: [PATCH 2/3] feat(trustflow): implement init_escrow and fix Vec ambiguity --- contracts/trustflow/src/lib.rs | 66 ++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/contracts/trustflow/src/lib.rs b/contracts/trustflow/src/lib.rs index 9821263..c2bed2a 100644 --- a/contracts/trustflow/src/lib.rs +++ b/contracts/trustflow/src/lib.rs @@ -246,11 +246,73 @@ impl TrustFlow { beneficiary, amount, status: EscrowStatus::Active, + milestones: Vec::new(&env), }, ); Ok(id) } + /// Initialise an escrow with specific milestones and lock `amount` tokens. + pub fn init_escrow( + env: Env, + depositor: Address, + beneficiary: Address, + milestones: Vec, + ) -> Result { + depositor.require_auth(); + + let mut total_amount: i128 = 0; + for m in milestones.iter() { + if m.amount <= 0 { + return Err(TrustFlowError::InvalidAmount); + } + total_amount += m.amount; + } + + if total_amount <= 0 { + return Err(TrustFlowError::InvalidAmount); + } + + let token: Address = env.storage().instance().get(&DataKey::Token).unwrap(); + token::Client::new(&env, &token).transfer( + &depositor, + &env.current_contract_address(), + &total_amount, + ); + + let counter: u64 = env + .storage() + .instance() + .get(&DataKey::EscrowCounter) + .unwrap_or(0); + let id = counter + 1; + env.storage().instance().set(&DataKey::EscrowCounter, &id); + + env.storage().persistent().set( + &DataKey::Escrow(id), + &EscrowRecord { + id, + depositor: depositor.clone(), + beneficiary: beneficiary.clone(), + amount: total_amount, + status: EscrowStatus::Active, + milestones, + }, + ); + + env.events().publish( + (symbol_short!("escrow"), symbol_short!("init")), + EscrowInitialized { + escrow_id: id, + depositor, + beneficiary, + amount: total_amount, + }, + ); + + Ok(id) + } + // ----------------------------------------------------------------------- // Dispute management // ----------------------------------------------------------------------- @@ -345,7 +407,7 @@ impl TrustFlow { .storage() .persistent() .get(&DataKey::DisputeVoters(escrow_id)) - .unwrap_or_else(|| vec![&env]); + .unwrap_or_else(|| Vec::new(&env)); voters.push_back(juror); env.storage() .persistent() @@ -376,7 +438,7 @@ impl TrustFlow { .storage() .persistent() .get(&DataKey::DisputeVoters(escrow_id)) - .unwrap_or_else(|| vec![&env]); + .unwrap_or_else(|| Vec::new(&env)); if voters.is_empty() { return Err(TrustFlowError::NoVotesCast); } From ded197217c6851db5da8ea44b7fda773e29a7121 Mon Sep 17 00:00:00 2001 From: Lynndabel Date: Wed, 24 Jun 2026 13:53:48 +0100 Subject: [PATCH 3/3] test(trustflow): add test case for init_escrow with milestones --- contracts/trustflow/src/lib.rs | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/contracts/trustflow/src/lib.rs b/contracts/trustflow/src/lib.rs index c2bed2a..1c96d85 100644 --- a/contracts/trustflow/src/lib.rs +++ b/contracts/trustflow/src/lib.rs @@ -1041,4 +1041,41 @@ mod tests { // 100% slash: stake should be zero, not negative assert_eq!(client.get_stake(&malicious), 0); } + + #[test] + fn test_init_escrow_with_milestones() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, token_addr, sac) = setup(&env, DEFAULT_SLASH_BPS); + let depositor = Address::random(&env); + let beneficiary = Address::random(&env); + + mint(&sac, &depositor, 1_000); + + let milestones = Vec::from_array( + &env, + [ + Milestone { + label: String::from_slice(&env, "Design"), + amount: 400, + approved: false, + }, + Milestone { + label: String::from_slice(&env, "Development"), + amount: 600, + approved: false, + }, + ], + ); + + let escrow_id = client.init_escrow(&depositor, &beneficiary, &milestones); + + // Verify balance transfer + assert_eq!(balance(&env, &token_addr, &depositor), 0); + assert_eq!(balance(&env, &token_addr, &client.address), 1_000); + + // Verify escrow ID increment + assert_eq!(escrow_id, 1); + } }