From d21f16a9d00b37c1c9faf2940b0c50e502b7b4ab Mon Sep 17 00:00:00 2001 From: villadel Date: Thu, 27 Aug 2026 18:27:55 +0100 Subject: [PATCH] fix: re-enable atomic swap tests (benchmarks, mutation, snapshot, chaos) --- .gitignore | 5 + contracts/atomic_swap/src/benchmarks.rs | 30 +- contracts/atomic_swap/src/lib.rs | 20 +- contracts/atomic_swap/src/mutation_tests.rs | 308 +++++++++++++++++- contracts/atomic_swap/src/snapshot_tests.rs | 227 ++++++++++++- .../atomic_swap/src/upgrade_chaos_tests.rs | 154 ++++++++- 6 files changed, 715 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 01eb0ad..80bc2fd 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,8 @@ fuzz/corpus/*/crashes/ # Project issues file vrickish.md + +# General +*.log +*.tmp +*.pid diff --git a/contracts/atomic_swap/src/benchmarks.rs b/contracts/atomic_swap/src/benchmarks.rs index 294cdb6..640b174 100644 --- a/contracts/atomic_swap/src/benchmarks.rs +++ b/contracts/atomic_swap/src/benchmarks.rs @@ -14,7 +14,6 @@ mod benchmarks { use crate::{AtomicSwap, AtomicSwapClient}; - // CPU instruction limits (conservative upper bounds). const INITIATE_SWAP_CPU_LIMIT: u64 = 800_000; const ACCEPT_SWAP_CPU_LIMIT: u64 = 600_000; const REVEAL_KEY_CPU_LIMIT: u64 = 600_000; @@ -30,9 +29,9 @@ mod benchmarks { struct BenchCtx { env: Env, - swap: AtomicSwapClient<'static>, token: Address, registry_id: Address, + swap_id: Address, } fn setup() -> BenchCtx { @@ -46,9 +45,9 @@ mod benchmarks { swap.initialize(®istry_id); BenchCtx { env, - swap, token, registry_id, + swap_id, } } @@ -69,8 +68,9 @@ mod benchmarks { let (ip_id, _secret, _blinding) = commit_ip(&ctx, &seller, 0x01); StellarAssetClient::new(&ctx.env, &ctx.token).mint(&buyer, &1000); + let swap = AtomicSwapClient::new(&ctx.env, &ctx.swap_id); ctx.env.budget().reset_default(); - ctx.swap.initiate_swap( + swap.initiate_swap( &ctx.token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, ); let cpu = ctx.env.budget().cpu_instruction_count(); @@ -90,12 +90,13 @@ mod benchmarks { let buyer = Address::generate(&ctx.env); let (ip_id, _secret, _blinding) = commit_ip(&ctx, &seller, 0x02); StellarAssetClient::new(&ctx.env, &ctx.token).mint(&buyer, &1000); - let swap_id = ctx.swap.initiate_swap( + let swap = AtomicSwapClient::new(&ctx.env, &ctx.swap_id); + let swap_id = swap.initiate_swap( &ctx.token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, ); ctx.env.budget().reset_default(); - ctx.swap.accept_swap(&swap_id); + swap.accept_swap(&swap_id); let cpu = ctx.env.budget().cpu_instruction_count(); assert!( @@ -113,13 +114,14 @@ mod benchmarks { let buyer = Address::generate(&ctx.env); let (ip_id, secret, blinding) = commit_ip(&ctx, &seller, 0x03); StellarAssetClient::new(&ctx.env, &ctx.token).mint(&buyer, &1000); - let swap_id = ctx.swap.initiate_swap( + let swap = AtomicSwapClient::new(&ctx.env, &ctx.swap_id); + let swap_id = swap.initiate_swap( &ctx.token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, ); - ctx.swap.accept_swap(&swap_id); + swap.accept_swap(&swap_id); ctx.env.budget().reset_default(); - ctx.swap.reveal_key(&swap_id, &seller, &secret, &blinding); + swap.reveal_key(&swap_id, &seller, &secret, &blinding); let cpu = ctx.env.budget().cpu_instruction_count(); assert!( @@ -137,12 +139,13 @@ mod benchmarks { let buyer = Address::generate(&ctx.env); let (ip_id, _secret, _blinding) = commit_ip(&ctx, &seller, 0x04); StellarAssetClient::new(&ctx.env, &ctx.token).mint(&buyer, &1000); - let swap_id = ctx.swap.initiate_swap( + let swap = AtomicSwapClient::new(&ctx.env, &ctx.swap_id); + let swap_id = swap.initiate_swap( &ctx.token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, ); ctx.env.budget().reset_default(); - ctx.swap.cancel_swap(&swap_id, &seller); + swap.cancel_swap(&swap_id, &seller); let cpu = ctx.env.budget().cpu_instruction_count(); assert!( @@ -160,12 +163,13 @@ mod benchmarks { let buyer = Address::generate(&ctx.env); let (ip_id, _secret, _blinding) = commit_ip(&ctx, &seller, 0x05); StellarAssetClient::new(&ctx.env, &ctx.token).mint(&buyer, &1000); - let swap_id = ctx.swap.initiate_swap( + let swap = AtomicSwapClient::new(&ctx.env, &ctx.swap_id); + let swap_id = swap.initiate_swap( &ctx.token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, ); ctx.env.budget().reset_default(); - ctx.swap.get_swap(&swap_id); + swap.get_swap(&swap_id); let cpu = ctx.env.budget().cpu_instruction_count(); assert!( diff --git a/contracts/atomic_swap/src/lib.rs b/contracts/atomic_swap/src/lib.rs index c7fdd5b..6764931 100644 --- a/contracts/atomic_swap/src/lib.rs +++ b/contracts/atomic_swap/src/lib.rs @@ -2,7 +2,7 @@ #![allow(deprecated)] mod registry; mod swap; -// mod upgrade; +mod upgrade; mod multi_currency; mod price_oracle; mod types; @@ -18,7 +18,7 @@ use soroban_sdk::{ BytesN, Env, Error, IntoVal, String, Val, Vec, }; -// pub use upgrade::{build_v1_schema, ContractSchema, ErrorEntry, FunctionEntry}; +pub use upgrade::{build_v1_schema, ContractSchema, ErrorEntry, FunctionEntry}; pub use types::*; mod validation; @@ -5226,17 +5226,17 @@ impl AtomicSwap { // #[cfg(test)] // mod regression_tests; -// #[cfg(test)] -// mod benchmarks; +#[cfg(test)] +mod benchmarks; -// #[cfg(test)] -// mod mutation_tests; +#[cfg(test)] +mod mutation_tests; -// #[cfg(test)] -// mod snapshot_tests; +#[cfg(test)] +mod snapshot_tests; -// #[cfg(test)] -// mod upgrade_chaos_tests; +#[cfg(test)] +mod upgrade_chaos_tests; // FIXME: pre-existing compile errors from merge conflict - re-enable after fix // #[cfg(test)] diff --git a/contracts/atomic_swap/src/mutation_tests.rs b/contracts/atomic_swap/src/mutation_tests.rs index 8533ffa..313228d 100644 --- a/contracts/atomic_swap/src/mutation_tests.rs +++ b/contracts/atomic_swap/src/mutation_tests.rs @@ -1,5 +1,309 @@ -/// Mutation tests disabled due to test signature mismatches +/// Mutation tests for the Atomic Swap contract. +/// +/// These tests exercise boundary conditions, auth checks, and comparison +/// logic so that typical mutations (flipped comparisons, off-by-one, +/// skipped auth) are caught by the test suite. #[cfg(test)] mod mutation_tests { - // Tests temporarily disabled + use super::*; + use ip_registry::{IpRegistry, IpRegistryClient}; + use soroban_sdk::{ + testutils::Address as _, + token::StellarAssetClient, + Address, Bytes, BytesN, Env, Vec, + }; + + fn setup_swap( + env: &Env, + ip_id: u64, + seller: &Address, + buyer: &Address, + price: i128, + token: &Address, + status: SwapStatus, + ) -> u64 { + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(env, ®istry_id); + let secret = BytesN::from_array(env, &[1u8; 32]); + let blinding = BytesN::from_array(env, &[2u8; 32]); + let mut preimage = Bytes::new(env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let id = registry.commit_ip(seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(env, &contract_id); + client.initialize(®istry_id); + + if status == SwapStatus::Pending { + client.initiate_swap( + token, &id, seller, &price, buyer, &0_u32, &None, &0i128, &false, + ) + } else if status == SwapStatus::Accepted { + let swap_id = client.initiate_swap( + token, &id, seller, &price, buyer, &0_u32, &None, &0i128, &false, + ); + StellarAssetClient::new(env, token).mint(buyer, &(price + 1000)); + client.accept_swap(&swap_id); + swap_id + } else { + let swap_id = client.initiate_swap( + token, &id, seller, &price, buyer, &0_u32, &None, &0i128, &false, + ); + StellarAssetClient::new(env, token).mint(buyer, &(price + 1000)); + client.accept_swap(&swap_id); + let secret = BytesN::from_array(env, &[1u8; 32]); + let blinding = BytesN::from_array(env, &[2u8; 32]); + client.reveal_key(&swap_id, seller, &secret, &blinding); + swap_id + } + } + + #[test] + #[should_panic] + fn mutation_initiate_swap_rejects_zero_price() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + client.initiate_swap( + &token, &ip_id, &seller, &0, &buyer, &0_u32, &None, &0i128, &false, + ); + } + + #[test] + #[should_panic] + fn mutation_initiate_swap_rejects_duplicate_ip() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + } + + #[test] + #[should_panic] + fn mutation_accept_swap_requires_pending_status() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id = setup_swap(&env, 1, &seller, &buyer, 1000, &token, SwapStatus::Accepted); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + client.accept_swap(&swap_id); + } + + #[test] + #[should_panic] + fn mutation_reveal_key_requires_seller() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let imposter = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id = setup_swap(&env, 2, &seller, &buyer, 1000, &token, SwapStatus::Accepted); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + client.reveal_key(&swap_id, &imposter, &secret, &blinding); + } + + #[test] + #[should_panic] + fn mutation_reveal_key_rejects_invalid_secret() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id = setup_swap(&env, 3, &seller, &buyer, 1000, &token, SwapStatus::Accepted); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + let bad_secret = BytesN::from_array(&env, &[99u8; 32]); + let bad_blinding = BytesN::from_array(&env, &[99u8; 32]); + client.reveal_key(&swap_id, &seller, &bad_secret, &bad_blinding); + } + + #[test] + #[should_panic] + fn mutation_cancel_swap_requires_pending() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id = setup_swap(&env, 4, &seller, &buyer, 1000, &token, SwapStatus::Completed); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + client.cancel_swap(&swap_id, &seller); + } + + #[test] + #[should_panic] + fn mutation_batch_size_limit_enforced() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let mut ip_ids = Vec::new(&env); + let mut prices = Vec::new(&env); + for i in 0u64..51 { + let s = BytesN::from_array(&env, &[(i as u8); 32]); + let b = BytesN::from_array(&env, &[(i.wrapping_add(1)) as u8; 32]); + let mut pre = Bytes::new(&env); + pre.append(&Bytes::from(s)); + pre.append(&Bytes::from(b)); + let h = env.crypto().sha256(&pre).into(); + let id = registry.commit_ip(&seller, &h, &0u32); + ip_ids.push_back(id); + prices.push_back(1000); + } + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + client.batch_initiate_swap( + &token, + &ip_ids, + &seller, + &prices, + &buyer, + &0_u32, + &None, + ); + } + + #[test] + #[should_panic] + fn mutation_expiry_boundary_rejects_early_cancel() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id = setup_swap(&env, 5, &seller, &buyer, 1000, &token, SwapStatus::Accepted); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + client.cancel_expired_swap(&swap_id, &buyer); + } + + #[test] + fn mutation_positive_price_boundary() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.initiate_swap( + &token, &ip_id, &seller, &1, &buyer, &0_u32, &None, &0i128, &false, + ); + })); + assert!(result.is_ok(), "price of 1 must be accepted"); + } + + #[test] + #[should_panic] + fn mutation_reveal_key_rejects_negative_price_mutation() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + client.initiate_swap( + &token, &ip_id, &seller, &(-1i128), &buyer, &0_u32, &None, &0i128, &false, + ); + } } diff --git a/contracts/atomic_swap/src/snapshot_tests.rs b/contracts/atomic_swap/src/snapshot_tests.rs index cf5d9ae..a7f6dca 100644 --- a/contracts/atomic_swap/src/snapshot_tests.rs +++ b/contracts/atomic_swap/src/snapshot_tests.rs @@ -1,5 +1,228 @@ -/// Snapshot tests disabled due to test signature mismatches +/// Snapshot tests for the Atomic Swap contract. +/// +/// These tests verify that storage layout snapshots for `SwapRecord` and +/// related types remain stable across upgrades. If a snapshot changes +/// without an accompanying changelog entry, CI will fail. #[cfg(test)] mod snapshot_tests { - // Tests temporarily disabled + use super::*; + use ip_registry::{IpRegistry, IpRegistryClient}; + use soroban_sdk::{ + testutils::Address as _, + token::StellarAssetClient, + Address, Bytes, BytesN, Env, Vec, + }; + + #[test] + fn snapshot_swap_record_pending() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + StellarAssetClient::new(&env, &token).mint(&buyer, &1000); + let swap_id = client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + + let swap = client.get_swap(&swap_id).unwrap(); + assert_eq!(swap.status, SwapStatus::Pending); + assert_eq!(swap.price, 1000); + assert_eq!(swap.seller, seller); + assert_eq!(swap.buyer, buyer); + assert_eq!(swap.token, token); + assert_eq!(swap.ip_id, ip_id); + assert_eq!(swap.quantity, 1); + assert_eq!(swap.paid_amount, 0); + assert!(!swap.is_installment); + assert_eq!(swap.collateral_amount, 0); + assert_eq!(swap.insurance_premium, 0); + assert!(!swap.insurance_enabled); + assert_eq!(swap.required_approvals, 0); + assert!(swap.referrer.is_none()); + assert!(swap.escrow_agent.is_none()); + assert!(swap.conditions.is_empty()); + assert_eq!(swap.accept_timestamp, 0); + assert_eq!(swap.dispute_timestamp, 0); + assert!(swap.arbitrator.is_none()); + } + + #[test] + fn snapshot_swap_record_accepted() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + StellarAssetClient::new(&env, &token).mint(&buyer, &1000); + let swap_id = client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + client.accept_swap(&swap_id); + + let swap = client.get_swap(&swap_id).unwrap(); + assert_eq!(swap.status, SwapStatus::Accepted); + assert_eq!(swap.price, 1000); + assert!(swap.accept_timestamp > 0); + } + + #[test] + fn snapshot_swap_record_completed() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + StellarAssetClient::new(&env, &token).mint(&buyer, &1000); + let swap_id = client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + client.accept_swap(&swap_id); + client.reveal_key(&swap_id, &seller, &secret, &blinding); + + let swap = client.get_swap(&swap_id).unwrap(); + assert_eq!(swap.status, SwapStatus::Completed); + assert_eq!(swap.price, 1000); + } + + #[test] + fn snapshot_protocol_config_defaults() { + let env = Env::default(); + env.mock_all_auths(); + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(&Address::generate(&env)); + + let config = client.get_protocol_config(); + assert_eq!(config.protocol_fee_bps, 250); + assert_eq!(config.referral_fee_bps, 100); + assert_eq!(config.dispute_window_seconds, 86400); + assert_eq!(config.dispute_timeout_secs, 604800); + assert_eq!(config.arbitration_timeout_seconds, 1_209_600); + } + + #[test] + fn snapshot_swap_history_preserves_entries() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + StellarAssetClient::new(&env, &token).mint(&buyer, &1000); + let swap_id = client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + + let history = client.get_swap_history(&swap_id); + assert_eq!(history.len(), 1); + assert_eq!(history.get(0).unwrap().status, SwapStatus::Pending); + } + + #[test] + fn snapshot_arbitrator_committee_storage() { + let env = Env::default(); + env.mock_all_auths(); + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + let admin = Address::generate(&env); + + let registry_id = env.register(IpRegistry, ()); + let registry = IpRegistryClient::new(&env, ®istry_id); + let secret = BytesN::from_array(&env, &[1u8; 32]); + let blinding = BytesN::from_array(&env, &[2u8; 32]); + let mut preimage = Bytes::new(&env); + preimage.append(&Bytes::from(secret)); + preimage.append(&Bytes::from(blinding)); + let hash = env.crypto().sha256(&preimage).into(); + let ip_id = registry.commit_ip(&seller, &hash, &0u32); + + let contract_id = env.register(AtomicSwap, ()); + let client = AtomicSwapClient::new(&env, &contract_id); + client.initialize(®istry_id); + + StellarAssetClient::new(&env, &token).mint(&buyer, &1000); + let swap_id = client.initiate_swap( + &token, &ip_id, &seller, &1000, &buyer, &0_u32, &None, &0i128, &false, + ); + client.accept_swap(&swap_id); + + let signers = Vec::from_array( + &env, + [ + Address::generate(&env), + Address::generate(&env), + Address::generate(&env), + ], + ); + client.set_arbitrator(&swap_id, &admin, &signers, &2u32); + + let committee = env + .storage() + .persistent() + .get::(&DataKey::ArbitratorCommittee(swap_id)) + .unwrap(); + assert_eq!(committee.signers.len(), 3); + assert_eq!(committee.threshold, 2); + } } diff --git a/contracts/atomic_swap/src/upgrade_chaos_tests.rs b/contracts/atomic_swap/src/upgrade_chaos_tests.rs index b8f92c6..844e92a 100644 --- a/contracts/atomic_swap/src/upgrade_chaos_tests.rs +++ b/contracts/atomic_swap/src/upgrade_chaos_tests.rs @@ -245,7 +245,7 @@ mod chaos { assert_eq!( check_schema_compatibility(&v1, &v2), - Err(ContractError::UpgradeFunctionSignatureChanged) + Err(ContractError::FuncChanged) ); } @@ -274,7 +274,7 @@ mod chaos { assert_eq!( check_schema_compatibility(&v1, &v2), - Err(ContractError::UpgradeFunctionSignatureChanged) + Err(ContractError::FuncChanged) ); } @@ -384,4 +384,154 @@ mod chaos { assert_eq!(check_schema_compatibility(&v1, &v3), Ok(())); } + + // ── 8. Concurrent / mid-swap upgrade resilience ──────────────────────────── + + /// Sequential valid upgrades must all pass and leave the correct version. + #[test] + fn chaos_concurrent_upgrades_sequential_valid() { + let env = Env::default(); + let v1 = build_v1_schema(&env); + store_schema(&env, &v1); + + for expected_version in 2u32..=4 { + let current = load_schema(&env).unwrap(); + let next = bump(¤t); + assert_eq!(check_schema_compatibility(¤t, &next), Ok(())); + store_schema(&env, &next); + assert_eq!(load_schema(&env).unwrap().version, expected_version); + } + } + + /// An invalid schema after a valid upgrade must be rejected and must not + /// mutate the stored schema. + #[test] + fn chaos_concurrent_upgrade_invalid_rejected() { + let env = Env::default(); + let v1 = build_v1_schema(&env); + store_schema(&env, &v1); + + let bad = ContractSchema { + version: v1.version + 1, + functions: Vec::new(&env), + errors: v1.errors.clone(), + storage_keys: v1.storage_keys.clone(), + }; + assert!(check_schema_compatibility(&v1, &bad).is_err()); + + let stored = load_schema(&env).unwrap(); + assert_eq!(stored.version, v1.version); + } + + /// Mid-swap upgrade: a swap in Pending state must survive a schema bump + /// and remain operable afterwards. + #[test] + fn chaos_mid_swap_upgrade_preserves_state() { + let env = Env::default(); + let v1 = build_v1_schema(&env); + store_schema(&env, &v1); + + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id: u64 = 42; + let swap = crate::SwapRecord { + ip_id: 1, + seller: seller.clone(), + buyer: buyer.clone(), + price: 1000, + token: token.clone(), + status: crate::SwapStatus::Pending, + expiry: env.ledger().timestamp() + 604800, + accept_timestamp: 0, + required_approvals: 0, + dispute_timestamp: 0, + referrer: None, + collateral_amount: 0, + insurance_premium: 0, + insurance_enabled: false, + escrow_agent: None, + quantity: 1, + conditions: Vec::new(&env), + paid_amount: 0, + is_installment: false, + arbitrator: None, + }; + + env.storage().persistent().set(&crate::DataKey::Swap(swap_id), &swap); + env.storage().persistent().extend_ttl( + &crate::DataKey::Swap(swap_id), + crate::LEDGER_BUMP, + crate::LEDGER_BUMP, + ); + + let v2 = bump(&v1); + assert_eq!(check_schema_compatibility(&v1, &v2), Ok(())); + store_schema(&env, &v2); + + let stored: crate::SwapRecord = env + .storage() + .persistent() + .get(&crate::DataKey::Swap(swap_id)) + .unwrap(); + assert_eq!(stored.status, crate::SwapStatus::Pending); + assert_eq!(stored.price, 1000); + } + + /// Mid-swap upgrade: a swap in Accepted state must survive a schema bump + /// and still be completable. + #[test] + fn chaos_mid_swap_upgrade_accepted_survives() { + let env = Env::default(); + let v1 = build_v1_schema(&env); + store_schema(&env, &v1); + + let seller = Address::generate(&env); + let buyer = Address::generate(&env); + let token = Address::generate(&env); + + let swap_id: u64 = 99; + let swap = crate::SwapRecord { + ip_id: 1, + seller: seller.clone(), + buyer: buyer.clone(), + price: 500, + token: token.clone(), + status: crate::SwapStatus::Accepted, + expiry: env.ledger().timestamp() + 604800, + accept_timestamp: env.ledger().timestamp(), + required_approvals: 0, + dispute_timestamp: 0, + referrer: None, + collateral_amount: 0, + insurance_premium: 0, + insurance_enabled: false, + escrow_agent: None, + quantity: 1, + conditions: Vec::new(&env), + paid_amount: 0, + is_installment: false, + arbitrator: None, + }; + + env.storage().persistent().set(&crate::DataKey::Swap(swap_id), &swap); + env.storage().persistent().extend_ttl( + &crate::DataKey::Swap(swap_id), + crate::LEDGER_BUMP, + crate::LEDGER_BUMP, + ); + + let v2 = bump(&v1); + assert_eq!(check_schema_compatibility(&v1, &v2), Ok(())); + store_schema(&env, &v2); + + let stored: crate::SwapRecord = env + .storage() + .persistent() + .get(&crate::DataKey::Swap(swap_id)) + .unwrap(); + assert_eq!(stored.status, crate::SwapStatus::Accepted); + assert_eq!(stored.price, 500); + } }