diff --git a/contracts/chainsetttle/src/admin.rs b/contracts/chainsetttle/src/admin.rs deleted file mode 100644 index 2bcd003..0000000 --- a/contracts/chainsetttle/src/admin.rs +++ /dev/null @@ -1,599 +0,0 @@ -use soroban_sdk::{contractimpl, token, Address, BytesN, Env, String, Symbol, Vec}; - -use crate::{ - constants::{AUDIT_LOG_MAX_ENTRIES, MAX_FEE_BPS}, - storage, - AuditEntry, ChainSettleContract, ContractStats, DisputeEntry, FeeConfig, MultiAdminConfig, -}; - -#[contractimpl] -impl ChainSettleContract { - // ---------------------------------------------------------- - // INIT - // ---------------------------------------------------------- - - pub fn init(env: Env, admin: Address) { - admin.require_auth(); - storage::set_admin(&env, &admin); - storage::set_paused(&env, false); - storage::set_min_milestone_percent(&env, 5u32); - storage::set_max_concurrent_disputes(&env, 1u32); - storage::set_admin_action_log(&env, &Vec::new(&env)); - storage::set_contract_stats( - &env, - &ContractStats { - total_shipments: 0, - total_volume: 0, - total_disputes: 0, - completed_shipments: 0, - }, - ); - storage::set_active_disputes(&env, &Vec::::new(&env)); - storage::set_escalation_threshold(&env, 0u32); - storage::set_max_shipment_value(&env, 0i128); - storage::set_circuit_breaker_limit(&env, 0i128); - storage::set_circuit_breaker_window(&env, 0u32); - storage::set_circuit_breaker_window_start(&env, 0u32); - storage::set_circuit_breaker_window_outflow(&env, 0i128); - env.events().publish( - (Symbol::new(&env, "admin_action"), Symbol::new(&env, "init")), - (admin, env.ledger().sequence()), - ); - } - - // ---------------------------------------------------------- - // UPGRADE - // ---------------------------------------------------------- - - pub fn upgrade(env: Env, admin: Address, new_wasm_hash: BytesN<32>) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - env.deployer().update_current_contract_wasm(new_wasm_hash.clone()); - env.events().publish( - (Symbol::new(&env, "contract_upgraded"),), - (new_wasm_hash, env.ledger().sequence()), - ); - } - - pub fn migrate(_env: Env) { - // No-op for current version; implement data migrations here post-upgrade. - } - - // ---------------------------------------------------------- - // PAUSE / UNPAUSE - // ---------------------------------------------------------- - - pub fn pause(env: Env, admin: Address) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - Self::append_admin_action(&env, Symbol::new(&env, "pause"), Symbol::new(&env, "contract_paused")); - storage::set_paused(&env, true); - env.events().publish( - (Symbol::new(&env, "contract_paused"),), - (admin, env.ledger().sequence()), - ); - } - - pub fn unpause(env: Env, admin: Address) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - Self::append_admin_action(&env, Symbol::new(&env, "unpause"), Symbol::new(&env, "contract_unpaused")); - storage::set_paused(&env, false); - env.events().publish( - (Symbol::new(&env, "contract_unpaused"),), - (admin, env.ledger().sequence()), - ); - } - - // ---------------------------------------------------------- - // ESCALATION THRESHOLD - // ---------------------------------------------------------- - - pub fn set_escalation_threshold(env: Env, admin: Address, threshold_ledgers: u32) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::set_escalation_threshold(&env, threshold_ledgers); - env.events().publish( - (Symbol::new(&env, "escalation_threshold_set"),), - threshold_ledgers, - ); - } - - pub fn get_escalation_threshold(env: Env) -> u32 { - storage::get_escalation_threshold(&env) - } - - // ---------------------------------------------------------- - // MAX SHIPMENT VALUE - // ---------------------------------------------------------- - - pub fn set_max_shipment_value(env: Env, admin: Address, max_value: i128) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::set_max_shipment_value(&env, max_value); - env.events().publish( - (Symbol::new(&env, "max_shipment_value_set"),), - max_value, - ); - } - - pub fn get_max_shipment_value(env: Env) -> i128 { - storage::get_max_shipment_value(&env) - } - - // ---------------------------------------------------------- - // CIRCUIT BREAKER - // ---------------------------------------------------------- - - pub fn set_circuit_breaker(env: Env, admin: Address, limit: i128, window_ledgers: u32) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::set_circuit_breaker_limit(&env, limit); - storage::set_circuit_breaker_window(&env, window_ledgers); - storage::set_circuit_breaker_window_start(&env, env.ledger().sequence()); - storage::set_circuit_breaker_window_outflow(&env, 0i128); - env.events().publish( - (Symbol::new(&env, "circuit_breaker_set"),), - (limit, window_ledgers), - ); - } - - // ---------------------------------------------------------- - // MULTI-ADMIN GOVERNANCE - // ---------------------------------------------------------- - - pub fn initialize_multisig_admin(env: Env, admin: Address, admins: Vec
, threshold: u32) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - if admins.len() < 1 || threshold < 1 || threshold > admins.len() as u32 { - panic!("invalid multi-sig parameters"); - } - storage::set_multi_admin_config(&env, &MultiAdminConfig { admins, threshold }); - env.events().publish( - (Symbol::new(&env, "multisig_admin_initialized"),), - threshold, - ); - } - - pub fn propose_admin_action( - env: Env, - admin: Address, - action_id: String, - operation: Symbol, - params: String, - ) { - admin.require_auth(); - let config = storage::get_multi_admin_config(&env) - .unwrap_or_else(|| panic!("multisig admin not configured")); - - let mut is_admin = false; - for i in 0..config.admins.len() { - if config.admins.get(i).unwrap() == admin { - is_admin = true; - break; - } - } - if !is_admin { - panic!("unauthorized"); - } - - let mut approvals = storage::get_admin_approvals(&env, &action_id); - for i in 0..approvals.len() { - if approvals.get(i).unwrap() == admin { - panic!("already approved by this admin"); - } - } - approvals.push_back(admin.clone()); - storage::set_admin_approvals(&env, &action_id, &approvals); - - env.events().publish( - (Symbol::new(&env, "admin_action_proposed"), action_id.clone()), - approvals.len() as u32, - ); - - if approvals.len() as u32 >= config.threshold { - Self::execute_admin_action(&env, &action_id, operation, params); - storage::remove_admin_approvals(&env, &action_id); - } - } - - pub fn get_pending_admin_actions(env: Env, action_id: String) -> Vec
{ - storage::get_admin_approvals(&env, &action_id) - } - - fn execute_admin_action(env: &Env, action_id: &String, operation: Symbol, _params: String) { - env.events().publish( - (Symbol::new(env, "admin_action_executed"), action_id.clone()), - operation, - ); - } - - // ---------------------------------------------------------- - // FEE CONFIG - // ---------------------------------------------------------- - - pub fn set_fee_config(env: Env, admin: Address, fee_bps: u32, treasury: Address) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - if fee_bps > MAX_FEE_BPS { - panic!("fee_bps exceeds maximum of 1000"); - } - Self::append_admin_action(&env, Symbol::new(&env, "set_fee_config"), Symbol::new(&env, "fee_config_updated")); - storage::set_fee_config(&env, &FeeConfig { fee_bps, treasury: treasury.clone() }); - env.events().publish( - (Symbol::new(&env, "admin_action"), Symbol::new(&env, "set_fee_config")), - (admin, fee_bps, treasury, env.ledger().sequence()), - ); - } - - pub fn set_max_concurrent_disputes(env: Env, admin: Address, limit: u32) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::set_max_concurrent_disputes(&env, limit); - Self::append_admin_action( - &env, - Symbol::new(&env, "set_max_concurrent_disputes"), - Symbol::new(&env, "max_concurrent_disputes_updated"), - ); - } - - pub fn set_min_milestone_percent(env: Env, admin: Address, percent: u32) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - if percent == 0 || percent > 100 { - panic!("min_milestone_percent must be between 1 and 100"); - } - storage::set_min_milestone_percent(&env, percent); - Self::append_admin_action( - &env, - Symbol::new(&env, "set_min_milestone_percent"), - Symbol::new(&env, "min_milestone_percent_updated"), - ); - } - - pub fn blacklist_address(env: Env, admin: Address, address: Address, reason_hash: BytesN<32>) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::set_blacklisted(&env, &address, &reason_hash); - Self::append_admin_action( - &env, - Symbol::new(&env, "blacklist_address"), - Symbol::new(&env, "address_blacklisted"), - ); - env.events().publish( - (Symbol::new(&env, "admin_action"), Symbol::new(&env, "blacklist_address")), - (admin, address, reason_hash, env.ledger().sequence()), - ); - } - - pub fn remove_from_blacklist(env: Env, admin: Address, address: Address) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - storage::remove_blacklisted(&env, &address); - Self::append_admin_action( - &env, - Symbol::new(&env, "remove_from_blacklist"), - Symbol::new(&env, "address_unblacklisted"), - ); - } - - pub fn is_blacklisted(env: Env, address: Address) -> bool { - storage::is_blacklisted(&env, &address) - } - - pub fn get_admin_log(env: Env) -> Vec { - storage::get_admin_action_log(&env) - } - - // ---------------------------------------------------------- - // TOKEN WHITELIST - // ---------------------------------------------------------- - - pub fn add_allowed_token(env: Env, token: Address) { - let admin = storage::get_admin(&env).unwrap_or_else(|| panic!("unauthorized")); - admin.require_auth(); - Self::append_admin_action(&env, Symbol::new(&env, "add_allowed_token"), Symbol::new(&env, "allowed_token_added")); - let mut allowed = storage::get_allowed_tokens(&env); - allowed.push_back(token); - storage::set_allowed_tokens(&env, &allowed); - } - - pub fn remove_allowed_token(env: Env, token: Address) { - let admin = storage::get_admin(&env).unwrap_or_else(|| panic!("unauthorized")); - admin.require_auth(); - Self::append_admin_action(&env, Symbol::new(&env, "remove_allowed_token"), Symbol::new(&env, "allowed_token_removed")); - let allowed = storage::get_allowed_tokens(&env); - let mut new_list: Vec
= Vec::new(&env); - for i in 0..allowed.len() { - let t = allowed.get(i).unwrap(); - if t != token { - new_list.push_back(t); - } - } - storage::set_allowed_tokens(&env, &new_list); - } - - // ---------------------------------------------------------- - // TWO-STEP ADMIN TRANSFER - // ---------------------------------------------------------- - - pub fn nominate_admin(env: Env, current_admin: Address, nominee: Address) { - current_admin.require_auth(); - Self::assert_admin(&env, ¤t_admin); - storage::set_pending_admin(&env, &nominee); - env.events().publish( - (Symbol::new(&env, "admin_nominated"),), - nominee, - ); - } - - pub fn accept_admin(env: Env, nominee: Address) { - nominee.require_auth(); - let pending = storage::get_pending_admin(&env) - .unwrap_or_else(|| panic!("no pending nomination")); - if nominee != pending { - panic!("unauthorized"); - } - let old_admin = storage::get_admin(&env).unwrap_or_else(|| panic!("unauthorized")); - storage::set_admin(&env, &nominee); - storage::remove_pending_admin(&env); - env.events().publish( - (Symbol::new(&env, "admin_transferred"),), - (old_admin, nominee), - ); - } - - pub fn revoke_nomination(env: Env, current_admin: Address) { - current_admin.require_auth(); - Self::assert_admin(&env, ¤t_admin); - storage::remove_pending_admin(&env); - env.events().publish( - (Symbol::new(&env, "nomination_revoked"),), - env.ledger().sequence(), - ); - } - - // ---------------------------------------------------------- - // ARBITER FEE TIERS - // ---------------------------------------------------------- - - pub fn set_arbiter_fee_tiers(env: Env, admin: Address, mut tiers: Vec<(i128, u32)>) { - admin.require_auth(); - Self::assert_admin(&env, &admin); - - let mut prev_threshold = -1_i128; - for i in 0..tiers.len() { - let (threshold, bps) = tiers.get(i).unwrap(); - if threshold < 0 { - panic!("threshold must be non-negative"); - } - if threshold <= prev_threshold { - panic!("tiers must be strictly ascending by threshold"); - } - if bps > MAX_FEE_BPS { - panic!("fee_bps exceeds maximum"); - } - prev_threshold = threshold; - } - - Self::append_admin_action(&env, Symbol::new(&env, "set_arbiter_fee_tiers"), Symbol::new(&env, "arbiter_fee_tiers_updated")); - storage::set_arbiter_fee_tiers(&env, &tiers); - env.events().publish( - (Symbol::new(&env, "admin_action"), Symbol::new(&env, "set_arbiter_fee_tiers")), - (admin, env.ledger().sequence()), - ); - } - - pub fn get_arbiter_fee_tiers(env: Env) -> Vec<(i128, u32)> { - storage::get_arbiter_fee_tiers(&env) - } - - // ---------------------------------------------------------- - // EMERGENCY FUND RECOVERY - // ---------------------------------------------------------- - - pub fn emergency_recover(env: Env, admin: Address, shipment_id: String) { - Self::assert_not_paused(&env); - admin.require_auth(); - Self::assert_admin(&env, &admin); - - let mut shipment = Self::get_shipment_internal(&env, &shipment_id); - - if shipment.status != crate::ShipmentStatus::Active { - panic!("shipment is not active"); - } - - let current_ledger = env.ledger().sequence(); - if current_ledger <= shipment.created_at + crate::constants::RECOVERY_THRESHOLD_LEDGERS { - panic!("recovery threshold not reached"); - } - - let recovery_amount = shipment.total_amount - shipment.released_amount; - - if recovery_amount > 0 { - let token_client = token::Client::new(&env, &shipment.token); - token_client.transfer(&env.current_contract_address(), &admin, &recovery_amount); - } - - Self::move_shipment_status_index( - &env, - crate::ShipmentStatus::Active, - crate::ShipmentStatus::Cancelled, - &shipment_id, - ); - shipment.status = crate::ShipmentStatus::Cancelled; - storage::set_shipment(&env, &shipment_id, &shipment); - - let escrowed = storage::get_total_escrowed(&env, &shipment.token); - storage::set_total_escrowed(&env, &shipment.token, (escrowed - recovery_amount).max(0)); - - env.events().publish( - (Symbol::new(&env, "emergency_recovery"), shipment_id.clone()), - (recovery_amount, admin), - ); - } - - // ---------------------------------------------------------- - // INTERNAL HELPERS (shared across modules) - // ---------------------------------------------------------- - - pub(crate) fn assert_not_paused(env: &Env) { - if storage::is_paused(env) { - panic!("contract is paused"); - } - } - - pub(crate) fn assert_admin(env: &Env, caller: &Address) { - let stored = storage::get_admin(env).unwrap_or_else(|| panic!("unauthorized")); - if *caller != stored { - panic!("unauthorized"); - } - } - - pub(crate) fn is_buyer(shipment: &crate::Shipment, addr: &Address) -> bool { - for i in 0..shipment.buyers.len() { - if shipment.buyers.get(i).unwrap() == *addr { - return true; - } - } - false - } - - pub(crate) fn assert_is_buyer(shipment: &crate::Shipment, addr: &Address) { - if !Self::is_buyer(shipment, addr) { - panic!("unauthorized"); - } - } - - pub(crate) fn assert_no_open_disputes(shipment: &crate::Shipment) { - for i in 0..shipment.milestones.len() { - if shipment.milestones.get(i).unwrap().status == crate::MilestoneStatus::Disputed { - panic!("transfer disallowed: open dispute must be resolved first"); - } - } - } - - pub(crate) fn check_circuit_breaker(env: &Env, payment: i128) { - let limit = storage::get_circuit_breaker_limit(env); - if limit == 0 { - return; - } - let window = storage::get_circuit_breaker_window(env); - let window_start = storage::get_circuit_breaker_window_start(env); - let mut outflow = storage::get_circuit_breaker_window_outflow(env); - let current_ledger = env.ledger().sequence(); - - if current_ledger >= window_start + window { - outflow = 0; - storage::set_circuit_breaker_window_start(env, current_ledger); - } - if outflow + payment > limit { - panic!("circuit breaker triggered: outflow limit exceeded"); - } - storage::set_circuit_breaker_window_outflow(env, outflow + payment); - } - - pub(crate) fn get_shipment_internal(env: &Env, shipment_id: &String) -> crate::Shipment { - storage::get_shipment(env, shipment_id) - .unwrap_or_else(|| panic!("shipment not found")) - } - - pub(crate) fn append_audit_entry( - env: &Env, - shipment: &mut crate::Shipment, - action: Symbol, - detail: Symbol, - ) { - let entry = AuditEntry { - action, - caller: storage::get_admin(env).unwrap_or_else(|| panic!("unauthorized")), - ledger: env.ledger().sequence(), - detail, - }; - if shipment.audit_log.len() as usize >= crate::constants::SHIPMENT_AUDIT_LOG_MAX_ENTRIES { - let mut new_log: Vec = Vec::new(env); - for i in 1..shipment.audit_log.len() { - new_log.push_back(shipment.audit_log.get(i).unwrap()); - } - shipment.audit_log = new_log; - } - shipment.audit_log.push_back(entry); - } - - pub(crate) fn append_admin_action(env: &Env, action: Symbol, detail: Symbol) { - let mut log = storage::get_admin_action_log(env); - let entry = AuditEntry { - action, - caller: storage::get_admin(env).unwrap_or_else(|| panic!("unauthorized")), - ledger: env.ledger().sequence(), - detail, - }; - if log.len() as usize >= AUDIT_LOG_MAX_ENTRIES { - let mut next: Vec = Vec::new(env); - for i in 1..log.len() { - next.push_back(log.get(i).unwrap()); - } - log = next; - } - log.push_back(entry); - storage::set_admin_action_log(env, &log); - } - - pub(crate) fn all_milestones_done(shipment: &crate::Shipment) -> bool { - for i in 0..shipment.milestones.len() { - let s = shipment.milestones.get(i).unwrap().status; - if s != crate::MilestoneStatus::Confirmed && s != crate::MilestoneStatus::Resolved { - return false; - } - } - true - } - - pub(crate) fn deduct_fee(env: &Env, gross: i128, token: &Address, fee_out: &mut i128) -> i128 { - if let Some(config) = storage::get_fee_config(env) { - let fee = (gross * config.fee_bps as i128) / 10_000; - if fee > 0 { - let token_client = token::Client::new(env, token); - token_client.transfer(&env.current_contract_address(), &config.treasury, &fee); - *fee_out = fee; - return gross - fee; - } - } - gross - } - - pub(crate) fn add_to_status_index(env: &Env, status: crate::ShipmentStatus, shipment_id: &String) { - let mut list = storage::get_shipments_by_status(env, status.clone()); - list.push_back(shipment_id.clone()); - storage::set_shipments_by_status(env, status, &list); - } - - pub(crate) fn remove_from_status_index( - env: &Env, - status: crate::ShipmentStatus, - shipment_id: &String, - ) { - let list = storage::get_shipments_by_status(env, status.clone()); - let mut new_list: Vec = Vec::new(env); - let mut removed = false; - for i in 0..list.len() { - let id = list.get(i).unwrap(); - if !removed && id == *shipment_id { - removed = true; - } else { - new_list.push_back(id); - } - } - storage::set_shipments_by_status(env, status, &new_list); - } - - pub(crate) fn move_shipment_status_index( - env: &Env, - from: crate::ShipmentStatus, - to: crate::ShipmentStatus, - shipment_id: &String, - ) { - Self::remove_from_status_index(env, from, shipment_id); - Self::add_to_status_index(env, to, shipment_id); - } -} diff --git a/contracts/chainsetttle/src/lib.rs b/contracts/chainsetttle/src/lib.rs index a5d0bb3..605ee8a 100644 --- a/contracts/chainsetttle/src/lib.rs +++ b/contracts/chainsetttle/src/lib.rs @@ -8285,4 +8285,4 @@ mod test_concurrent_disputes; mod test_boundaries; mod test_chaos; mod test_302_303_304_306; -// mod test_features; +mod test_features; diff --git a/contracts/chainsetttle/src/storage.rs b/contracts/chainsetttle/src/storage.rs index 20c5e34..03001cf 100644 --- a/contracts/chainsetttle/src/storage.rs +++ b/contracts/chainsetttle/src/storage.rs @@ -1,3 +1,23 @@ +//! Storage abstraction layer for the ChainSettle contract. +//! +//! This module is the **single source of truth** for all persistent, instance, +//! and temporary storage access. Every read and write to contract state MUST go +//! through the typed accessor functions defined here. +//! +//! Design notes: +//! - All [`DataKey`] variants are versioned with a `V1` prefix so that a future +//! upgrade can introduce `V2_*` keys and run [`migrate_v1_to_v2`] without +//! clobbering live state. +//! - TTL extension is centralised inside each `set_*` helper so callers never +//! need to remember to extend manually. +//! - Extended/feature keys that are not yet covered by `V1DataKey` live in +//! [`crate::DataKeyExt`] (defined in `lib.rs`) and are accessed inline; they +//! will be migrated to this module incrementally. +//! +//! Resolves issue #220 — this file was previously unreachable because `mod storage;` +//! was absent from `lib.rs`. The declaration has been restored and this module is +//! now the canonical storage layer for all production code. + use soroban_sdk::{contracttype, Address, Env, String, Vec}; use crate::{ diff --git a/contracts/chainsetttle/src/test_boundaries.rs b/contracts/chainsetttle/src/test_boundaries.rs index 92f2695..f00818a 100644 --- a/contracts/chainsetttle/src/test_boundaries.rs +++ b/contracts/chainsetttle/src/test_boundaries.rs @@ -22,7 +22,7 @@ use super::*; use soroban_sdk::{ testutils::{Address as _, Ledger as _}, - token, vec, Address, Env, String, + token, vec, Address, Env, String, Symbol, }; // ---- shared env setup ------------------------------------------------------- @@ -60,6 +60,40 @@ fn setup_ctx() -> Ctx { Ctx { env, contract_id, token_id, buyer, supplier, logistics, arbiter } } +/// Default zero-value ShipmentOptions — all features disabled. +fn default_opts(env: &Env) -> ShipmentOptions { + ShipmentOptions { + response_deadline: 0, + penalty_bps: 0, + milestone_mode: MilestoneMode::Parallel, + holdback_ledgers: 0, + dispute_cooldown_ledgers: 0, + late_penalty_bps_per_ledger: 0, + auto_confirm_ledgers: 0, + dispute_bond_amount: 0, + arbiter_fee_bps: 0, + logistics_fee_bps: 0, + supplier_collateral: 0, + expires_at_ledger: None, + metadata_hash: None, + referrer: None, + buyer_cancel_fee_bps: 0, + early_bonus_pool: 0, + review_window_ledgers: None, + milestone_splits: Vec::new(env), + deadlines: Vec::new(env), + dispute_timeout_seconds: 0, + default_resolution: Resolution::Buyer, + backup_arbiter: None, + confirmation_cooldown_ledgers: None, + arbiter_panel: Vec::new(env), + } +} + +fn buyers(env: &Env, buyer: &Address) -> soroban_sdk::Vec
{ + vec![env, buyer.clone()] +} + fn milestones_summing_to(env: &Env, pct: u32) -> soroban_sdk::Vec { vec![ env, @@ -109,8 +143,11 @@ fn create_std(ctx: &Ctx, id: &str, amount: i128) { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, id), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, - &amount, &standard_milestones(&ctx.env), &false, &0, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &amount, + &standard_milestones(&ctx.env), + &default_opts(&ctx.env), ); } @@ -118,8 +155,11 @@ fn create_holdback(ctx: &Ctx, id: &str, amount: i128, holdback: u32) { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, id), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, - &amount, &standard_milestones(&ctx.env), &false, &holdback, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &amount, + &standard_milestones(&ctx.env), + &ShipmentOptions { holdback_ledgers: holdback, ..default_opts(&ctx.env) }, ); } @@ -134,10 +174,11 @@ fn accept_create_shipment_amount_one() { // Minimum valid positive amount client.create_shipment( &String::from_str(&ctx.env, "BNDRY-AMT-1"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &1_i128, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); let s = client.get_shipment(&String::from_str(&ctx.env, "BNDRY-AMT-1")); assert_eq!(s.total_amount, 1); @@ -151,10 +192,11 @@ fn reject_create_shipment_amount_zero() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-AMT-0"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &0_i128, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); } @@ -165,10 +207,11 @@ fn reject_create_shipment_amount_i128_min() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-AMT-MIN"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &i128::MIN, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); } @@ -180,10 +223,11 @@ fn reject_create_shipment_amount_i128_max_insufficient_balance() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-AMT-IMAX"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &i128::MAX, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); } @@ -199,10 +243,11 @@ fn reject_create_shipment_payment_percent_zero_sum() { // All milestones at 0% → sum = 0, not 100 client.create_shipment( &String::from_str(&ctx.env, "BNDRY-PCT-0"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &1_000_000_i128, &milestones_summing_to(&ctx.env, 0), - &false, &0, + &default_opts(&ctx.env), ); } @@ -214,10 +259,11 @@ fn reject_create_shipment_payment_percent_u32_max_overflows_sum() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-PCT-MAX"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &1_000_000_i128, &milestones_summing_to(&ctx.env, u32::MAX), - &false, &0, + &default_opts(&ctx.env), ); } @@ -227,10 +273,11 @@ fn accept_create_shipment_payment_percent_100_single_milestone() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-PCT-100"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &1_000_000_i128, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); let s = client.get_shipment(&String::from_str(&ctx.env, "BNDRY-PCT-100")); assert_eq!(s.milestones.get(0).unwrap().payment_percent, 100); @@ -247,11 +294,11 @@ fn accept_create_shipment_holdback_u32_max() { let client = ChainSettleContractClient::new(&ctx.env, &ctx.contract_id); client.create_shipment( &String::from_str(&ctx.env, "BNDRY-HOLD-MAX"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &1_000_000_i128, &standard_milestones(&ctx.env), - &false, - &u32::MAX, + &ShipmentOptions { holdback_ledgers: u32::MAX, ..default_opts(&ctx.env) }, ); let s = client.get_shipment(&String::from_str(&ctx.env, "BNDRY-HOLD-MAX")); assert_eq!(s.holdback_ledgers, u32::MAX); @@ -538,10 +585,11 @@ fn accept_escrow_balance_never_negative_after_full_release() { let amount: i128 = 1; client.create_shipment( &String::from_str(&ctx.env, "BNDRY-ESCROW-MIN"), - &ctx.buyer, &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, + &buyers(&ctx.env, &ctx.buyer), + &ctx.supplier, &ctx.logistics, &ctx.arbiter, &ctx.token_id, &amount, &milestones_summing_to(&ctx.env, 100), - &false, &0, + &default_opts(&ctx.env), ); let id = String::from_str(&ctx.env, "BNDRY-ESCROW-MIN"); client.submit_proof(&ctx.supplier, &id, &0, &String::from_str(&ctx.env, "h"), &Symbol::new(&ctx.env, "ipfs")); diff --git a/contracts/chainsetttle/src/test_features.rs b/contracts/chainsetttle/src/test_features.rs index b544199..875dfee 100644 --- a/contracts/chainsetttle/src/test_features.rs +++ b/contracts/chainsetttle/src/test_features.rs @@ -91,6 +91,13 @@ fn default_opts(env: &Env) -> ShipmentOptions { buyer_cancel_fee_bps: 0, early_bonus_pool: 0, review_window_ledgers: None, + milestone_splits: Vec::new(env), + deadlines: Vec::new(env), + dispute_timeout_seconds: 0, + default_resolution: Resolution::Buyer, + backup_arbiter: None, + confirmation_cooldown_ledgers: None, + arbiter_panel: Vec::new(env), } }