diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index dc59b93..6744abc 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -2,10 +2,8 @@ name: Mutation Testing on: schedule: - # Weekly on Monday at 06:00 UTC - cron: '0 6 * * 1' workflow_dispatch: - # Allow manual trigger from GitHub UI jobs: mutants: @@ -24,34 +22,48 @@ jobs: tool: cargo-mutants - name: Run mutation tests - run: cargo mutants --no-fail-fast 2>&1 | tee mutation-report.txt + run: cargo mutants -vV --in-place 2>&1 | tee mutation-report.txt - name: Extract mutation score id: score run: | - SCORE=$(grep -oP '\d+\.?\d*% mutation score' mutation-report.txt || echo "0%") - echo "score=$SCORE" >> "$GITHUB_OUTPUT" - echo "Mutation score: $SCORE" + SUMMARY=$(grep -oP '\d+ mutants tested.*: \d+ missed, \d+ caught' mutation-report.txt || echo "0") + MISSED=$(echo "$SUMMARY" | grep -oP '\d+(?= missed)') + CAUGHT=$(echo "$SUMMARY" | grep -oP '\d+(?= caught)') + TOTAL=$((MISSED + CAUGHT)) + if [ "$TOTAL" -gt 0 ]; then + PCT=$(awk "BEGIN {printf \"%.1f\", $CAUGHT * 100 / $TOTAL}") + else + PCT="0.0" + fi + echo "score=$PCT" >> "$GITHUB_OUTPUT" + echo "caught=$CAUGHT" >> "$GITHUB_OUTPUT" + echo "missed=$MISSED" >> "$GITHUB_OUTPUT" + echo "### Mutation Score: ${PCT}% (${CAUGHT} caught, ${MISSED} missed)" >> "$GITHUB_STEP_SUMMARY" - name: Upload mutation report uses: actions/upload-artifact@v4 if: always() with: name: mutation-report - path: mutation-report.txt - - - name: Upload mutants directory - uses: actions/upload-artifact@v4 - if: always() - with: - name: mutants-dir - path: mutants/ + path: | + mutation-report.txt + mutants.out/ - name: Check minimum mutation score run: | - SCORE=$(grep -oP '\d+\.?\d*(?=% mutation score)' mutation-report.txt || echo "0") - if [ "$(echo "$SCORE < 80" | bc -l)" -eq 1 ]; then - echo "❌ Mutation score ${SCORE}% is below target of 80%" + SUMMARY=$(grep -oP '\d+ mutants tested.*: \d+ missed, \d+ caught' mutation-report.txt || echo "0") + MISSED=$(echo "$SUMMARY" | grep -oP '\d+(?= missed)') + CAUGHT=$(echo "$SUMMARY" | grep -oP '\d+(?= caught)') + TOTAL=$((MISSED + CAUGHT)) + if [ "$TOTAL" -gt 0 ]; then + PCT=$(awk "BEGIN {printf \"%.1f\", $CAUGHT * 100 / $TOTAL}") + else + PCT="0" + fi + MIN=70 + if [ "$(echo "$PCT < $MIN" | bc -l)" -eq 1 ]; then + echo "❌ Mutation score ${PCT}% is below target of ${MIN}%" exit 1 fi - echo "✅ Mutation score ${SCORE}% meets target of 80%" + echo "✅ Mutation score ${PCT}% meets target of ${MIN}%" diff --git a/.gitignore b/.gitignore index 19307d6..2009994 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ Cargo.lock .DS_Store Thumbs.db test_snapshots/ +mutants.out/ diff --git a/src/lib.rs b/src/lib.rs index aba0613..208daee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,67 @@ #![no_std] +use soroban_sdk::{ + contract, contracterror, contractimpl, panic_with_error, symbol_short, xdr::ToXdr, Address, + Bytes, BytesN, Env, String, Symbol, +}; + +mod storage_types; +use storage_types::{ContractInfo, DataKey, WrapRecord}; + +soroban_sdk::contractmeta!( + key = "Description", + val = "Soulbound token registry for Stellar Wrap" +); +soroban_sdk::contractmeta!(key = "Version", val = "0.1.0"); +soroban_sdk::contractmeta!(key = "Name", val = "Stellar Wrap Registry"); +soroban_sdk::contractmeta!(key = "Author", val = "Stellar Wrap Team"); + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum ContractError { + AlreadyInitialized = 1, + NotInitialized = 2, + Unauthorized = 3, + WrapAlreadyExists = 4, + WrapNotFound = 5, + InvalidSignature = 6, + InvalidDataHash = 7, } +#[contract] +pub struct StellarWrapContract; + #[contractimpl] impl StellarWrapContract { pub fn initialize(e: Env, admin: Address, admin_pubkey: BytesN<32>) { - e.storage().instance().set(&symbol_short!("admin"), &admin); - e.storage().instance().set(&symbol_short!("admin_pubkey"), &admin_pubkey); + if e.storage().instance().has(&DataKey::Admin) { + panic_with_error!(e, ContractError::AlreadyInitialized); + } + e.storage().instance().set(&DataKey::Admin, &admin); + e.storage() + .instance() + .set(&DataKey::AdminPubKey, &admin_pubkey); + } + + pub fn update_admin(e: Env, new_admin: Address) { + let current_admin: Address = e + .storage() + .instance() + .get(&DataKey::Admin) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + + current_admin.require_auth(); + e.storage().instance().set(&DataKey::Admin, &new_admin); + + e.events().publish( + ( + symbol_short!("v1"), + symbol_short!("admin"), + symbol_short!("updated"), + ), + (current_admin, new_admin), + ); } pub fn mint_wrap( @@ -15,32 +70,257 @@ impl StellarWrapContract { period: u64, archetype: Symbol, data_hash: BytesN<32>, - _signature: BytesN<64>, + signature: BytesN<64>, ) { + user.require_auth(); + + let guard_key = DataKey::MintGuard(user.clone()); + if e.storage().temporary().has(&guard_key) { + panic_with_error!(e, ContractError::Unauthorized); + } + e.storage().temporary().set(&guard_key, &true); + + let admin_pubkey: BytesN<32> = e + .storage() + .instance() + .get(&DataKey::AdminPubKey) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + + if data_hash == BytesN::from_array(&e, &[0u8; 32]) { + panic_with_error!(e, ContractError::InvalidDataHash); + } + + let mut payload = Bytes::new(&e); + payload.append(&e.current_contract_address().to_xdr(&e)); + payload.append(&user.clone().to_xdr(&e)); + payload.append(&period.to_xdr(&e)); + payload.append(&archetype.clone().to_xdr(&e)); + payload.append(&data_hash.clone().to_xdr(&e)); + + e.crypto() + .ed25519_verify(&admin_pubkey, &payload, &signature); + + let wrap_key = DataKey::Wrap(user.clone(), period); + if e.storage().persistent().has(&wrap_key) { + panic_with_error!(e, ContractError::WrapAlreadyExists); + } + let record = WrapRecord { timestamp: e.ledger().timestamp(), data_hash, + archetype: archetype.clone(), + period, + }; + + let ttl_one_year = 17280 * 365; + e.storage().persistent().set(&wrap_key, &record); + e.storage() + .persistent() + .extend_ttl(&wrap_key, ttl_one_year, ttl_one_year); + + let count_key = DataKey::WrapCount(user.clone()); + let current_count: u32 = e.storage().persistent().get(&count_key).unwrap_or(0); + e.storage() + .persistent() + .set(&count_key, &(current_count + 1)); + e.storage() + .persistent() + .extend_ttl(&count_key, ttl_one_year, ttl_one_year); + + let latest_key = DataKey::LatestPeriod(user.clone()); + let current_latest: u64 = e.storage().persistent().get(&latest_key).unwrap_or(0); + if period > current_latest { + e.storage().persistent().set(&latest_key, &period); + e.storage() + .persistent() + .extend_ttl(&latest_key, ttl_one_year, ttl_one_year); + } + + e.storage().temporary().remove(&guard_key); + + e.events().publish( + (symbol_short!("v1"), symbol_short!("mint"), user, period), archetype, + ); + } + + pub fn update_wrap( + e: Env, + user: Address, + period: u64, + new_data_hash: BytesN<32>, + new_archetype: Symbol, + signature: BytesN<64>, + ) { + let admin: Address = e + .storage() + .instance() + .get(&DataKey::Admin) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + admin.require_auth(); + + let admin_pubkey: BytesN<32> = e + .storage() + .instance() + .get(&DataKey::AdminPubKey) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + + if new_data_hash == BytesN::from_array(&e, &[0u8; 32]) { + panic_with_error!(e, ContractError::InvalidDataHash); + } + + let mut payload = Bytes::new(&e); + payload.append(&e.current_contract_address().to_xdr(&e)); + payload.append(&user.clone().to_xdr(&e)); + payload.append(&period.to_xdr(&e)); + payload.append(&new_archetype.clone().to_xdr(&e)); + payload.append(&new_data_hash.clone().to_xdr(&e)); + e.crypto() + .ed25519_verify(&admin_pubkey, &payload, &signature); + + let wrap_key = DataKey::Wrap(user.clone(), period); + let existing: WrapRecord = e + .storage() + .persistent() + .get(&wrap_key) + .unwrap_or_else(|| panic_with_error!(e, ContractError::WrapNotFound)); + + let updated = WrapRecord { + timestamp: existing.timestamp, + data_hash: new_data_hash, + archetype: new_archetype.clone(), period, }; - let key = (user.clone(), period); - e.storage().persistent().set(&key, &record); + + let ttl_one_year = 17280 * 365; + e.storage().persistent().set(&wrap_key, &updated); + e.storage() + .persistent() + .extend_ttl(&wrap_key, ttl_one_year, ttl_one_year); + + e.events().publish( + (symbol_short!("v1"), symbol_short!("update"), user, period), + new_archetype, + ); + } + + pub fn revoke_wrap(e: Env, user: Address, period: u64) { + let admin: Address = e + .storage() + .instance() + .get(&DataKey::Admin) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + admin.require_auth(); + + let wrap_key = DataKey::Wrap(user.clone(), period); + if !e.storage().persistent().has(&wrap_key) { + panic_with_error!(e, ContractError::WrapNotFound); + } + + e.storage().persistent().remove(&wrap_key); + + let count_key = DataKey::WrapCount(user.clone()); + let current_count: u32 = e.storage().persistent().get(&count_key).unwrap_or(0); + if current_count > 0 { + e.storage() + .persistent() + .set(&count_key, &(current_count - 1)); + } + + e.events().publish( + (symbol_short!("v1"), symbol_short!("revoke"), user, period), + true, + ); } pub fn get_wrap(e: Env, user: Address, period: u64) -> Option { - let key = (user, period); - e.storage().persistent().get(&key) + e.storage().persistent().get(&DataKey::Wrap(user, period)) + } + + pub fn balance_of(e: Env, id: Address) -> i128 { + let count_key = DataKey::WrapCount(id); + e.storage() + .persistent() + .get::<_, u32>(&count_key) + .unwrap_or(0) as i128 + } + + pub fn verify_data(e: Env, user: Address, period: u64, data: Bytes) -> bool { + let wrap: Option = e.storage().persistent().get(&DataKey::Wrap(user, period)); + match wrap { + Some(record) => { + let computed_hash = e.crypto().sha256(&data); + record.data_hash == BytesN::from_array(&e, &computed_hash.to_array()) + } + None => false, + } + } + + pub fn get_latest_wrap(e: Env, user: Address) -> Option { + let latest_key = DataKey::LatestPeriod(user.clone()); + let period: u64 = e.storage().persistent().get(&latest_key)?; + e.storage().persistent().get(&DataKey::Wrap(user, period)) + } + + pub fn extend_ttl(e: Env, user: Address, period: u64) { + let wrap_key = DataKey::Wrap(user.clone(), period); + let ttl = 17280 * 365; + + if e.storage().persistent().has(&wrap_key) { + e.storage().persistent().extend_ttl(&wrap_key, ttl, ttl); + } + + let count_key = DataKey::WrapCount(user.clone()); + if e.storage().persistent().has(&count_key) { + e.storage().persistent().extend_ttl(&count_key, ttl, ttl); + } + + let latest_key = DataKey::LatestPeriod(user); + if e.storage().persistent().has(&latest_key) { + e.storage().persistent().extend_ttl(&latest_key, ttl, ttl); + } + + e.storage().instance().extend_ttl(ttl, ttl); + } + + pub fn get_admin(e: Env) -> Option
{ + e.storage().instance().get(&DataKey::Admin) } - pub fn balance_of(e: Env, user: Address) -> u32 { - let key = (user.clone(), 1u64); - if e.storage().persistent().has(&key) { - 1 - } else { - 0 + pub fn name(e: Env) -> String { + String::from_str(&e, "Stellar Wrap Registry") + } + + pub fn symbol(e: Env) -> String { + String::from_str(&e, "WRAP") + } + + pub fn decimals(_e: Env) -> u32 { + 0 + } + + pub fn contract_info(e: Env) -> ContractInfo { + ContractInfo { + name: String::from_str(&e, "Stellar Wrap Registry"), + version: String::from_str(&e, "0.1.0"), + repo: String::from_str(&e, "https://github.com/zintarh/stellar-wrap-contract"), + description: String::from_str(&e, "Soulbound token registry for Stellar Wrap"), } } + + pub fn upgrade(e: Env, new_wasm_hash: BytesN<32>) { + let admin: Address = e + .storage() + .instance() + .get(&DataKey::Admin) + .unwrap_or_else(|| panic_with_error!(e, ContractError::NotInitialized)); + + admin.require_auth(); + e.deployer().update_current_contract_wasm(new_wasm_hash); + } } -// Re-export for tests -pub use StellarWrapContractClient; \ No newline at end of file +#[cfg(test)] +mod security_test; +#[cfg(test)] +mod test; diff --git a/src/security_test.rs b/src/security_test.rs index 4091dcf..151930d 100644 --- a/src/security_test.rs +++ b/src/security_test.rs @@ -1,9 +1,4 @@ #![cfg(test)] -//! Security Test Suite for Stellar Wrap Contract -//! -//! This module contains adversarial tests designed to ensure the contract -//! fails safely when attacked. We test replay attacks, identity theft, -//! cross-contract replay protection, and resource consumption. use super::*; use ed25519_dalek::{Signer, SigningKey}; @@ -11,13 +6,9 @@ use soroban_sdk::{ symbol_short, testutils::{Address as _, Ledger}, xdr::ToXdr, - Address, Bytes, BytesN, Env, Symbol, + Address, Bytes, BytesN, Env, }; - -/// Helper function to sign payloads for testing -} - fn sign_payload( env: &Env, signer: &SigningKey, @@ -42,8 +33,6 @@ fn sign_payload( BytesN::from_array(env, &signature.to_bytes()) } -/// Test 1: Replay Attack Simulation -/// Ensures that a valid signature cannot be reused for the same period #[test] #[should_panic(expected = "Error(Contract, #4)")] fn test_replay_attack_same_period_fails() { @@ -58,11 +47,10 @@ fn test_replay_attack_same_period_fails() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); let data_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature = sign_payload( &env, @@ -74,18 +62,14 @@ fn test_replay_attack_same_period_fails() { &data_hash, ); - // First mint - should succeed + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - // Verify the wrap was created let wrap = client.get_wrap(&user, &period); assert!(wrap.is_some(), "First mint should succeed"); - // Replay attack: Try to mint again with the exact same parameters - // This should PANIC with WrapAlreadyExists error (#4) + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); } -/// Test 2: Replay Attack with Different Hash (but same period) -/// Even with a different hash, the same period should be rejected #[test] #[should_panic(expected = "Error(Contract, #4)")] fn test_replay_attack_different_hash_same_period_fails() { @@ -100,12 +84,11 @@ fn test_replay_attack_different_hash_same_period_fails() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); let data_hash_1 = BytesN::from_array(&env, &[42u8; 32]); let data_hash_2 = BytesN::from_array(&env, &[99u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature_1 = sign_payload( &env, @@ -117,7 +100,7 @@ fn test_replay_attack_different_hash_same_period_fails() { &data_hash_1, ); - // First mint - should succeed + client.mint_wrap(&user, &period, &archetype, &data_hash_1, &signature_1); let signature_2 = sign_payload( &env, @@ -129,12 +112,9 @@ fn test_replay_attack_different_hash_same_period_fails() { &data_hash_2, ); - // Try to mint again for the same period with a different hash - // This should still fail - period is already used + client.mint_wrap(&user, &period, &archetype, &data_hash_2, &signature_2); } -/// Test 3: Multiple Valid Periods Work Correctly -/// Verifies that different periods for the same user work without issue #[test] fn test_multiple_periods_for_same_user_success() { let env = Env::default(); @@ -148,16 +128,15 @@ fn test_multiple_periods_for_same_user_success() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); let data_hash_1 = BytesN::from_array(&env, &[42u8; 32]); let data_hash_2 = BytesN::from_array(&env, &[99u8; 32]); let data_hash_3 = BytesN::from_array(&env, &[77u8; 32]); let archetype = symbol_short!("architect"); - let period_1 = 202512u64; // December 2025 - let period_2 = 202601u64; // January 2026 - let period_3 = 202602u64; // February 2026 + let period_1 = 202512u64; + let period_2 = 202601u64; + let period_3 = 202602u64; let signature_1 = sign_payload( &env, @@ -187,20 +166,15 @@ fn test_multiple_periods_for_same_user_success() { &data_hash_3, ); - // All three should succeed + client.mint_wrap(&user, &period_1, &archetype, &data_hash_1, &signature_1); + client.mint_wrap(&user, &period_2, &archetype, &data_hash_2, &signature_2); + client.mint_wrap(&user, &period_3, &archetype, &data_hash_3, &signature_3); - // Verify all three wraps exist assert!(client.get_wrap(&user, &period_1).is_some()); assert!(client.get_wrap(&user, &period_2).is_some()); assert!(client.get_wrap(&user, &period_3).is_some()); } -/// Test 4: Identity Theft / Signature Mismatch Attack -/// Tests that a signature intended for User A cannot be used by User B -/// -/// NOTE: This test currently relies on the admin authorization check. -/// For full security, the signature verification should cryptographically -/// bind the payload to the specific user address. #[test] fn test_signature_cannot_be_stolen_by_another_user() { let env = Env::default(); @@ -215,12 +189,10 @@ fn test_signature_cannot_be_stolen_by_another_user() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); - // Admin creates a signature for User A let data_hash_for_a = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature_a = sign_payload( &env, @@ -232,15 +204,13 @@ fn test_signature_cannot_be_stolen_by_another_user() { &data_hash_for_a, ); - // User A mints successfully + client.mint_wrap(&user_a, &period, &archetype, &data_hash_for_a, &signature_a); - // Verify User A has the wrap let wrap_a = client.get_wrap(&user_a, &period); assert!(wrap_a.is_some(), "User A should have the wrap"); - // User B tries to mint with their own period (this is allowed) let data_hash_for_b = BytesN::from_array(&env, &[99u8; 32]); - let period_b = 202601u64; // January 2026 + let period_b = 202601u64; let signature_b = sign_payload( &env, @@ -253,23 +223,19 @@ fn test_signature_cannot_be_stolen_by_another_user() { ); client.mint_wrap( - &admin, &user_b, &period_b, &archetype, &data_hash_for_b, &signature_b, - &None, ); - // Verify both users have their respective wraps and they're distinct let wrap_a = client.get_wrap(&user_a, &period).unwrap(); let wrap_b = client.get_wrap(&user_b, &period_b).unwrap(); assert_eq!(wrap_a.data_hash, data_hash_for_a); assert_eq!(wrap_b.data_hash, data_hash_for_b); - // User B should NOT have User A's period let user_b_period_dec = client.get_wrap(&user_b, &period); assert!( user_b_period_dec.is_none(), @@ -277,13 +243,10 @@ fn test_signature_cannot_be_stolen_by_another_user() { ); } -/// Test 5: Cross-Contract Replay Protection -/// Verifies that a signature valid for Contract V1 cannot be replayed on Contract V2 #[test] fn test_cross_contract_replay_protection() { let env = Env::default(); - // Deploy two separate contract instances (V1 and V2) let contract_v1 = env.register_contract(None, StellarWrapContract); let contract_v2 = env.register_contract(None, StellarWrapContract); @@ -295,17 +258,14 @@ fn test_cross_contract_replay_protection() { let admin = Address::generate(&env); let user = Address::generate(&env); - // Initialize both contracts with the same admin client_v1.initialize(&admin, &admin_pubkey); client_v2.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client_v1); - register_security_archetypes(&client_v2); let data_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature_v1 = sign_payload( &env, @@ -317,14 +277,11 @@ fn test_cross_contract_replay_protection() { &data_hash, ); - // Mint successfully on V1 + client_v1.mint_wrap(&user, &period, &archetype, &data_hash, &signature_v1); - // Verify the wrap exists on V1 let wrap_v1 = client_v1.get_wrap(&user, &period); assert!(wrap_v1.is_some(), "Wrap should exist on contract V1"); - // The same user can mint on V2 (they are independent contracts) - // This should succeed because they are different contract instances let signature_v2 = sign_payload( &env, &signing_key, @@ -335,23 +292,15 @@ fn test_cross_contract_replay_protection() { &data_hash, ); + client_v2.mint_wrap(&user, &period, &archetype, &data_hash, &signature_v2); - // Verify both contracts have independent storage let wrap_v2 = client_v2.get_wrap(&user, &period); assert!(wrap_v2.is_some(), "Wrap should exist on contract V2"); - // Both wraps should exist independently assert!(client_v1.get_wrap(&user, &period).is_some()); assert!(client_v2.get_wrap(&user, &period).is_some()); - - // NOTE: For full cross-contract replay protection, the signature - // verification should include the contract address in the signed payload. - // This test demonstrates that the contracts currently have independent storage, - // but additional signature binding to contract_id would prevent true replay attacks. } -/// Test 6: Gas/Resource Analysis - CPU Instructions -/// Measures the computational cost of a mint operation #[test] fn test_gas_analysis_mint_operation() { let env = Env::default(); @@ -367,11 +316,10 @@ fn test_gas_analysis_mint_operation() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); let data_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature = sign_payload( &env, @@ -383,35 +331,23 @@ fn test_gas_analysis_mint_operation() { &data_hash, ); - // Reset budget before the mint operation env.budget().reset_default(); - // Perform the mint operation + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - // Get budget consumption env.budget().print(); - // Get actual CPU instructions used let cpu_insns = env.budget().cpu_instruction_cost(); let mem_bytes = env.budget().memory_bytes_cost(); - // Assert reasonable upper bounds (these values should be tuned based on actual needs) - // For mainnet deployment, you want these to be as low as possible assert!( cpu_insns < 10_000_000, "CPU instructions too high: {}", cpu_insns ); assert!(mem_bytes < 100_000, "Memory usage too high: {}", mem_bytes); - - // Gas analysis results: - // CPU Instructions: Check assertion output - // Memory Bytes: Check assertion output - // These values are validated by the assertions above } -/// Test 7: Gas Analysis - Multiple Operations -/// Measures resource consumption for multiple sequential mints #[test] fn test_gas_analysis_multiple_mints() { let env = Env::default(); @@ -427,22 +363,19 @@ fn test_gas_analysis_multiple_mints() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); env.budget().reset_default(); - // Perform 5 mints for different periods for i in 1..6 { let data_hash = BytesN::from_array(&env, &[i as u8; 32]); let archetype = symbol_short!("architect"); - // Create unique period values let period = match i { - 1 => 202512u64, // December 2025 - 2 => 202601u64, // January 2026 - 3 => 202602u64, // February 2026 - 4 => 202603u64, // March 2026 - _ => 202604u64, // April 2026 + 1 => 202512u64, + 2 => 202601u64, + 3 => 202602u64, + 4 => 202603u64, + _ => 202604u64, }; let signature = sign_payload( @@ -455,320 +388,16 @@ fn test_gas_analysis_multiple_mints() { &data_hash, ); + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); } let cpu_insns = env.budget().cpu_instruction_cost(); let mem_bytes = env.budget().memory_bytes_cost(); - // Gas analysis for 5 mints - results tracked in budget - // Verify resource usage is within reasonable bounds for batch operations assert!(cpu_insns < 50_000_000, "Batch CPU too high: {}", cpu_insns); assert!(mem_bytes < 500_000, "Batch memory too high: {}", mem_bytes); } -fn prepare_initialized_contract(env: &Env) -> (StellarWrapContractClient, Address, Address, SigningKey) { - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(env, &contract_id); - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let admin_pubkey = BytesN::from_array(env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(env); - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - (client, contract_id, admin, signing_key) -} - -#[test] -fn test_gas_analysis_initialize() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - - env.budget().reset_default(); - client.initialize(&admin, &admin_pubkey); - - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "initialize", - cpu_insns, - mem_bytes, - INITIALIZE_CPU_LIMIT, - INITIALIZE_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_get_wrap() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - env.budget().reset_default(); - - let _ = client.get_wrap(&user, &period); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark("get_wrap", cpu_insns, mem_bytes, GET_WRAP_CPU_LIMIT, GET_WRAP_MEM_LIMIT); -} - -#[test] -fn test_gas_analysis_balance_of() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, _contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &env.current_contract_address(), - &user, - period, - &archetype, - &data_hash, - ); - - // Mint a wrap to populate balance - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - env.budget().reset_default(); - - let _ = client.balance_of(&user); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "balance_of", - cpu_insns, - mem_bytes, - BALANCE_OF_CPU_LIMIT, - BALANCE_OF_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_verify_data() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - let data = Bytes::from_array(&env, &[1u8; 32]); - env.budget().reset_default(); - - let _ = client.verify_data(&user, &period, &data); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "verify_data", - cpu_insns, - mem_bytes, - VERIFY_DATA_CPU_LIMIT, - VERIFY_DATA_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_get_latest_wrap_one() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - env.budget().reset_default(); - - let _ = client.get_latest_wrap(&user); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "get_latest_wrap_1", - cpu_insns, - mem_bytes, - GET_LATEST_WRAP_ONE_CPU_LIMIT, - GET_LATEST_WRAP_ONE_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_get_latest_wrap_five() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let archetype = symbol_short!("architect"); - - for i in 0..5 { - let data_hash = BytesN::from_array(&env, &[i as u8; 32]); - let period = 202512u64 + i as u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - } - - env.budget().reset_default(); - let _ = client.get_latest_wrap(&user); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "get_latest_wrap_5", - cpu_insns, - mem_bytes, - GET_LATEST_WRAP_FIVE_CPU_LIMIT, - GET_LATEST_WRAP_FIVE_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_extend_ttl() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - env.budget().reset_default(); - - client.extend_ttl(&user, &period); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "extend_ttl", - cpu_insns, - mem_bytes, - EXTEND_TTL_CPU_LIMIT, - EXTEND_TTL_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_revoke_wrap() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, contract_id, _admin, signing_key) = prepare_initialized_contract(&env); - let user = Address::generate(&env); - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - env.budget().reset_default(); - - client.revoke_wrap(&user, &period); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "revoke_wrap", - cpu_insns, - mem_bytes, - REVOKE_WRAP_CPU_LIMIT, - REVOKE_WRAP_MEM_LIMIT, - ); -} - -#[test] -fn test_gas_analysis_update_admin() { - let env = Env::default(); - env.budget().reset_unlimited(); - - let (client, _contract_id, admin, signing_key) = prepare_initialized_contract(&env); - let new_admin = Address::generate(&env); - env.mock_all_auths(); - env.budget().reset_default(); - - client.update_admin(&new_admin); - let cpu_insns = env.budget().cpu_instruction_cost(); - let mem_bytes = env.budget().memory_bytes_cost(); - assert_benchmark( - "update_admin", - cpu_insns, - mem_bytes, - UPDATE_ADMIN_CPU_LIMIT, - UPDATE_ADMIN_MEM_LIMIT, - ); -} - -/// Test 8: Timestamp Manipulation Resistance -/// Ensures the contract uses ledger timestamp, not user-provided values #[test] fn test_timestamp_is_from_ledger_not_user() { let env = Env::default(); @@ -782,16 +411,14 @@ fn test_timestamp_is_from_ledger_not_user() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); - // Set specific ledger timestamp env.ledger().with_mut(|li| { li.timestamp = 1000000; }); let data_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature = sign_payload( &env, @@ -803,18 +430,17 @@ fn test_timestamp_is_from_ledger_not_user() { &data_hash, ); + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); let wrap = client.get_wrap(&user, &period).unwrap(); - // Verify timestamp matches ledger, not any user-provided value assert_eq!(wrap.timestamp, 1000000, "Timestamp should come from ledger"); - // Advance ledger time and mint another period env.ledger().with_mut(|li| { li.timestamp = 2000000; }); - let period_2 = 202601u64; // January 2026 + let period_2 = 202601u64; let signature_2 = sign_payload( &env, &signing_key, @@ -825,6 +451,7 @@ fn test_timestamp_is_from_ledger_not_user() { &data_hash, ); + client.mint_wrap(&user, &period_2, &archetype, &data_hash, &signature_2); let wrap_2 = client.get_wrap(&user, &period_2).unwrap(); assert_eq!( @@ -833,8 +460,6 @@ fn test_timestamp_is_from_ledger_not_user() { ); } -/// Test 9: Edge Case - Maximum Symbol Length -/// Tests behavior with maximum-length symbol names #[test] fn test_edge_case_long_symbols() { let env = Env::default(); @@ -848,13 +473,11 @@ fn test_edge_case_long_symbols() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_security_archetypes(&client); let data_hash = BytesN::from_array(&env, &[42u8; 32]); - // symbol_short! supports up to 9 ASCII characters let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature = sign_payload( &env, @@ -866,13 +489,12 @@ fn test_edge_case_long_symbols() { &data_hash, ); + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); let wrap = client.get_wrap(&user, &period); assert!(wrap.is_some(), "Should handle reasonably long symbols"); } -/// Test 10: Unauthorized Access - Non-Admin Cannot Mint -/// Verifies that only the admin can authorize minting #[test] #[should_panic] fn test_non_admin_cannot_mint() { @@ -888,10 +510,9 @@ fn test_non_admin_cannot_mint() { client.initialize(&admin, &admin_pubkey); - // Don't mock auth - let it fail naturally let data_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("architect"); - let period = 202512u64; // December 2025 + let period = 202512u64; let signature = sign_payload( &env, @@ -903,141 +524,5 @@ fn test_non_admin_cannot_mint() { &data_hash, ); - // This should panic because attacker is not authorized -} - -// ─── Degenerate Ed25519 key/signature edge cases ──────────────────────────── -// -// `ed25519_verify` is a host function; invalid keys/signatures panic with a -// VM crypto error (not a `ContractError`). Expected: invocation aborts before -// any wrap record is stored. - -/// All-zero pubkey (identity point) — invalid Ed25519 public key. -/// Expected: VM crypto panic during `ed25519_verify`, no wrap stored. -#[test] -#[should_panic] -fn test_mint_with_all_zero_pubkey_rejected() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let zero_pubkey = BytesN::from_array(&env, &[0u8; 32]); - let user = Address::generate(&env); - - client.initialize(&admin, &zero_pubkey); - env.mock_all_auths(); - - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = BytesN::from_array(&env, &[1u8; 64]); - -} - -/// All-ones (0xFF) pubkey — invalid curve point. -/// Expected: VM crypto panic during `ed25519_verify`. -#[test] -#[should_panic] -fn test_mint_with_all_ones_pubkey_rejected() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let ones_pubkey = BytesN::from_array(&env, &[0xff; 32]); - let user = Address::generate(&env); - - client.initialize(&admin, &ones_pubkey); - env.mock_all_auths(); - - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let signature = BytesN::from_array(&env, &[1u8; 64]); - -} - -/// Valid pubkey but all-zero signature. -/// Expected: VM crypto panic during `ed25519_verify`. -#[test] -#[should_panic] -fn test_mint_with_all_zero_signature_rejected() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let zero_sig = BytesN::from_array(&env, &[0u8; 64]); - -} - -/// Valid pubkey but all-ones (0xFF) signature. -/// Expected: VM crypto panic during `ed25519_verify`. -#[test] -#[should_panic] -fn test_mint_with_all_ones_signature_rejected() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - let ones_sig = BytesN::from_array(&env, &[0xff; 64]); - -} - -/// Valid pubkey but single-bit tampered signature. -/// Expected: VM crypto panic during `ed25519_verify`. -#[test] -#[should_panic] -fn test_mint_with_tampered_signature_rejected() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[1u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let data_hash = BytesN::from_array(&env, &[42u8; 32]); - let archetype = symbol_short!("architect"); - let period = 202512u64; - - let mut sig_bytes = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &data_hash, - ) - .to_array(); - sig_bytes[0] ^= 0x01; - let tampered_sig = BytesN::from_array(&env, &sig_bytes); - + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); } diff --git a/src/storage_types.rs b/src/storage_types.rs index bd121dd..8d09961 100644 --- a/src/storage_types.rs +++ b/src/storage_types.rs @@ -1,17 +1,30 @@ +use soroban_sdk::{contracttype, Address, BytesN, String, Symbol}; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ContractInfo { + pub name: String, + pub version: String, + pub repo: String, + pub description: String, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WrapRecord { + pub timestamp: u64, + pub data_hash: BytesN<32>, + pub archetype: Symbol, + pub period: u64, +} #[contracttype] #[derive(Clone)] pub enum DataKey { Admin, AdminPubKey, - SchemaVersion, Wrap(Address, u64), WrapCount(Address), LatestPeriod(Address), MintGuard(Address), - AllowedArchetypes, - MerkleRoot(u64), - MerkleClaimed(Address, u64), - UserOptOut(Address), } - diff --git a/src/test.rs b/src/test.rs index 19fe960..738fd49 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,19 +1,17 @@ -#![cfg(test)] +#![cfg(test)] extern crate std; use super::*; -use crate::constants::{TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, CONTRACT_DESCRIPTION, VERSION}; use ed25519_dalek::{Signer, SigningKey}; use soroban_sdk::{ symbol_short, + testutils::{Address as _, Events}, xdr::ToXdr, Address, Bytes, BytesN, Env, IntoVal, String, Symbol, TryIntoVal, }; -use std::{ - format, - panic::{catch_unwind, AssertUnwindSafe}, -}; +use std::panic::{catch_unwind, AssertUnwindSafe}; +use crate::storage_types::{DataKey, WrapRecord}; fn sign_payload( env: &Env, @@ -23,7 +21,6 @@ fn sign_payload( period: u64, archetype: &Symbol, data_hash: &BytesN<32>, - expiry_ledger: u32, ) -> BytesN<64> { let mut payload = Bytes::new(env); payload.append(&contract.to_xdr(env)); @@ -40,14 +37,6 @@ fn sign_payload( BytesN::from_array(env, &signature.to_bytes()) } -fn register_test_archetypes(client: &StellarWrapContractClient) { - client.add_archetype(&symbol_short!("arch")); - client.add_archetype(&symbol_short!("builder")); - client.add_archetype(&symbol_short!("defi")); - client.add_archetype(&symbol_short!("soroban")); - client.add_archetype(&symbol_short!("architect")); -} - #[test] fn test_minting_flow() { let env = Env::default(); @@ -61,7 +50,6 @@ fn test_minting_flow() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let dummy_hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("arch"); @@ -75,8 +63,8 @@ fn test_minting_flow() { period, &archetype, &dummy_hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &dummy_hash, &signature); let wrap = client.get_wrap(&user, &period).unwrap(); assert_eq!(wrap.data_hash, dummy_hash); @@ -95,7 +83,6 @@ fn test_mint_emits_event() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2024u64; let archetype = symbol_short!("arch"); @@ -108,138 +95,27 @@ fn test_mint_emits_event() { period, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash, &signature); let events = env.events().all(); let last_event = events.last().expect("No events found"); let (_, topics, data) = last_event; + let event_version: Symbol = topics.get(0).unwrap().try_into_val(&env).unwrap(); + let event_topic: Symbol = topics.get(1).unwrap().try_into_val(&env).unwrap(); + let event_user: Address = topics.get(2).unwrap().try_into_val(&env).unwrap(); + let event_period: u64 = topics.get(3).unwrap().try_into_val(&env).unwrap(); + let event_archetype: Symbol = data.try_into_val(&env).unwrap(); + + assert_eq!(event_version, symbol_short!("v1")); assert_eq!(event_topic, symbol_short!("mint")); assert_eq!(event_user, user); assert_eq!(event_period, period); assert_eq!(event_archetype, archetype); } -#[test] -fn test_streak_after_first_mint_is_one() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[13u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let period = 202402u64; - let archetype = symbol_short!("arch"); - let hash = BytesN::from_array(&env, &[1u8; 32]); - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &hash, - u32::MAX, - ); - - assert_eq!(client.get_streak(&user), 1); -} - -#[test] -fn test_streak_increments_for_consecutive_months_and_year_boundary() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[14u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let archetype = symbol_short!("arch"); - let hash = BytesN::from_array(&env, &[2u8; 32]); - - let signature_1 = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - 202412u64, - &archetype, - &hash, - u32::MAX, - ); - assert_eq!(client.get_streak(&user), 1); - - let signature_2 = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - 202501u64, - &archetype, - &hash, - u32::MAX, - ); - assert_eq!(client.get_streak(&user), 2); -} - -#[test] -fn test_streak_resets_after_gap() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[15u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let archetype = symbol_short!("arch"); - let hash = BytesN::from_array(&env, &[3u8; 32]); - - let signature_1 = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - 202501u64, - &archetype, - &hash, - u32::MAX, - ); - assert_eq!(client.get_streak(&user), 1); - - let signature_2 = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - 202503u64, - &archetype, - &hash, - u32::MAX, - ); - assert_eq!(client.get_streak(&user), 1); -} - #[test] fn test_balance_of_and_count() { let env = Env::default(); @@ -253,7 +129,6 @@ fn test_balance_of_and_count() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let archetype = symbol_short!("soroban"); let hash = BytesN::from_array(&env, &[1u8; 32]); @@ -266,8 +141,8 @@ fn test_balance_of_and_count() { 2021, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &2021, &archetype, &hash, &sig1); let sig2 = sign_payload( &env, @@ -277,8 +152,8 @@ fn test_balance_of_and_count() { 2022, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &2022, &archetype, &hash, &sig2); assert_eq!(client.balance_of(&user), 2); } @@ -310,7 +185,6 @@ fn test_duplicate_period_fails() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("arch"); @@ -324,9 +198,10 @@ fn test_duplicate_period_fails() { period, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash, &sig); + client.mint_wrap(&user, &period, &archetype, &hash, &sig); } #[test] @@ -352,54 +227,14 @@ fn test_token_metadata() { let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); - assert_eq!(client.decimals(), TOKEN_DECIMALS); - assert_eq!(client.name(), String::from_str(&env, TOKEN_NAME)); - assert_eq!(client.symbol(), String::from_str(&env, TOKEN_SYMBOL)); -} - -/// Regression test: Verify `name()`, `symbol()`, and `decimals()` return stable -/// values across contract upgrades. These functions are pure view functions that -/// must not change after a WASM upgrade to maintain indexer compatibility. -/// -/// NOTE: These functions return constant values hardcoded in the contract. -/// Changing TOKEN_NAME, TOKEN_SYMBOL, or TOKEN_DECIMALS is a BREAKING CHANGE -/// for indexers and downstream consumers that cache these values. -#[test] -fn test_token_metadata_preserved_after_upgrade() { - let env = Env::default(); - - let contract_v1_id = env.register_contract(None, StellarWrapContract); - let client_v1 = StellarWrapContractClient::new(&env, &contract_v1_id); - - assert_eq!(client_v1.decimals(), TOKEN_DECIMALS, "V1 decimals"); - assert_eq!(client_v1.name(), String::from_str(&env, TOKEN_NAME), "V1 name"); - assert_eq!(client_v1.symbol(), String::from_str(&env, TOKEN_SYMBOL), "V1 symbol"); - - let contract_v2_id = env.register_contract(None, StellarWrapContract); - let client_v2 = StellarWrapContractClient::new(&env, &contract_v2_id); - - assert_eq!(client_v2.decimals(), TOKEN_DECIMALS, "V2 decimals must match V1"); - assert_eq!(client_v2.name(), String::from_str(&env, TOKEN_NAME), "V2 name must match V1"); - assert_eq!(client_v2.symbol(), String::from_str(&env, TOKEN_SYMBOL), "V2 symbol must match V1"); - - assert_eq!(client_v1.decimals(), client_v2.decimals(), "decimals stable across versions"); - assert_eq!(client_v1.name(), client_v2.name(), "name stable across versions"); - assert_eq!(client_v1.symbol(), client_v2.symbol(), "symbol stable across versions"); -} - -// ─── Issue #48: version tests ─────────────────────────────────────────────── - -#[test] -fn test_version_returns_expected_value() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - assert_eq!(client.version(), VERSION); + assert_eq!(client.decimals(), 0); + assert_eq!( + client.name(), + String::from_str(&env, "Stellar Wrap Registry") + ); + assert_eq!(client.symbol(), String::from_str(&env, "WRAP")); } -// ─── Issue #56: contract_info tests ───────────────────────────────────────── - #[test] fn test_contract_info_returns_correct_fields() { let env = Env::default(); @@ -407,16 +242,18 @@ fn test_contract_info_returns_correct_fields() { let client = StellarWrapContractClient::new(&env, &contract_id); let info = client.contract_info(); - assert_eq!(info.name, String::from_str(&env, TOKEN_NAME)); + assert_eq!(info.name, String::from_str(&env, "Stellar Wrap Registry")); assert_eq!(info.version, String::from_str(&env, "0.1.0")); assert_eq!( info.repo, String::from_str(&env, "https://github.com/zintarh/stellar-wrap-contract") ); + assert_eq!( + info.description, + String::from_str(&env, "Soulbound token registry for Stellar Wrap") + ); } -// ─── Issue #84: extend_ttl tests ──────────────────────────────────────────── - #[test] fn test_extend_ttl_existing_wrap() { let env = Env::default(); @@ -430,7 +267,6 @@ fn test_extend_ttl_existing_wrap() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let hash = BytesN::from_array(&env, &[42u8; 32]); let archetype = symbol_short!("arch"); @@ -444,13 +280,11 @@ fn test_extend_ttl_existing_wrap() { period, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash, &sig); - // Anyone can call extend_ttl — no auth required client.extend_ttl(&user, &period); - // Record should still be readable after extending TTL let wrap = client.get_wrap(&user, &period).unwrap(); assert_eq!(wrap.data_hash, hash); } @@ -466,33 +300,9 @@ fn test_extend_ttl_nonexistent_wrap_does_not_panic() { client.initialize(&admin, &pubkey); let user = Address::generate(&env); - // Should not panic even if no wrap exists for this user/period client.extend_ttl(&user, &9999); } - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - let sig = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &hash, - ); -} - -// ─── Issue #81: concurrent mints for different users same period ──────────── - #[test] fn test_concurrent_mints_different_users_same_period() { let env = Env::default(); @@ -507,7 +317,6 @@ fn test_concurrent_mints_different_users_same_period() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 202512u64; let archetype = symbol_short!("arch"); @@ -522,7 +331,6 @@ fn test_concurrent_mints_different_users_same_period() { period, &archetype, &hash_a, - u32::MAX, ); let sig_b = sign_payload( &env, @@ -532,76 +340,24 @@ fn test_concurrent_mints_different_users_same_period() { period, &archetype, &hash_b, - u32::MAX, ); - // Both mints for the same period should succeed + client.mint_wrap(&user_a, &period, &archetype, &hash_a, &sig_a); + client.mint_wrap(&user_b, &period, &archetype, &hash_b, &sig_b); - // Records are independent let wrap_a = client.get_wrap(&user_a, &period).unwrap(); let wrap_b = client.get_wrap(&user_b, &period).unwrap(); assert_eq!(wrap_a.data_hash, hash_a); assert_eq!(wrap_b.data_hash, hash_b); assert_ne!(wrap_a.data_hash, wrap_b.data_hash); - // Individual balances are correct assert_eq!(client.balance_of(&user_a), 1); assert_eq!(client.balance_of(&user_b), 1); - // Each user's record doesn't affect the other assert!(client.get_wrap(&user_a, &period).is_some()); assert!(client.get_wrap(&user_b, &period).is_some()); } -#[test] -fn test_same_user_mint_different_periods_succeeds() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[11u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let archetype = symbol_short!("arch"); - let hash_a = BytesN::from_array(&env, &[11u8; 32]); - let hash_b = BytesN::from_array(&env, &[22u8; 32]); - let period_a = 202512u64; - let period_b = 202601u64; - - let sig_a = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period_a, - &archetype, - &hash_a, - ); - let sig_b = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period_b, - &archetype, - &hash_b, - ); - - client.mint_wrap(&user, &period_a, &archetype, &hash_a, &sig_a); - client.mint_wrap(&user, &period_b, &archetype, &hash_b, &sig_b); - - assert!(client.get_wrap(&user, &period_a).is_some()); - assert!(client.get_wrap(&user, &period_b).is_some()); - assert_eq!(client.balance_of(&user), 2); -} - -// ─── Issue #75: structured event verification ────────────────────────────── - #[test] fn test_mint_event_structured_matching() { let env = Env::default(); @@ -615,7 +371,6 @@ fn test_mint_event_structured_matching() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 202512u64; let archetype = symbol_short!("arch"); @@ -629,31 +384,28 @@ fn test_mint_event_structured_matching() { period, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash, &sig); let events = env.events().all(); let last_event = events.last().expect("Expected at least one event"); let (event_contract, topics, data) = last_event; - // Verify event is emitted by the correct contract assert_eq!(event_contract, contract_id); + assert_eq!(topics.len(), 4, "Mint event must have exactly 4 topics"); + let topic_0: Symbol = topics.get(0).unwrap().try_into_val(&env).unwrap(); let topic_1: Symbol = topics.get(1).unwrap().try_into_val(&env).unwrap(); let topic_2: Address = topics.get(2).unwrap().try_into_val(&env).unwrap(); let topic_3: u64 = topics.get(3).unwrap().try_into_val(&env).unwrap(); - assert_eq!(topic_0, contract_id, "Topic 0 must be the contract Address"); - assert_eq!( - assert_eq!(topic_2, user, "Topic 2 must be the user Address"); - assert_eq!(topic_3, period, "Topic 3 must be the period u64"); + assert_eq!(topic_0, symbol_short!("v1")); + assert_eq!(topic_1, symbol_short!("mint")); + assert_eq!(topic_2, user); + assert_eq!(topic_3, period); - // Verify data is the archetype Symbol let event_data: Symbol = data.try_into_val(&env).unwrap(); - assert_eq!( - event_data, archetype, - "Event data must be the archetype Symbol" - ); + assert_eq!(event_data, archetype); } #[test] @@ -670,7 +422,6 @@ fn test_mint_events_multiple_users_correct_schema() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let archetype_a = symbol_short!("builder"); let archetype_b = symbol_short!("defi"); @@ -687,7 +438,6 @@ fn test_mint_events_multiple_users_correct_schema() { period_a, &archetype_a, &hash_a, - u32::MAX, ); let sig_b = sign_payload( &env, @@ -697,13 +447,13 @@ fn test_mint_events_multiple_users_correct_schema() { period_b, &archetype_b, &hash_b, - u32::MAX, ); + client.mint_wrap(&user_a, &period_a, &archetype_a, &hash_a, &sig_a); + client.mint_wrap(&user_b, &period_b, &archetype_b, &hash_b, &sig_b); let events = env.events().all(); - // Collect mint events emitted by our contract let mut mint_events = soroban_sdk::vec![&env]; for event in events.iter() { let (addr, topics, _data) = &event; @@ -717,8 +467,8 @@ fn test_mint_events_multiple_users_correct_schema() { assert_eq!(mint_events.len(), 2, "Expected exactly 2 mint events"); - // Verify first mint event (user_a) let (_, topics_a, data_a) = mint_events.get(0).unwrap(); + let ev_version: Symbol = topics_a.get(0).unwrap().try_into_val(&env).unwrap(); let ev_user_a: Address = topics_a.get(2).unwrap().try_into_val(&env).unwrap(); let ev_period_a: u64 = topics_a.get(3).unwrap().try_into_val(&env).unwrap(); let ev_arch_a: Symbol = data_a.try_into_val(&env).unwrap(); @@ -727,8 +477,8 @@ fn test_mint_events_multiple_users_correct_schema() { assert_eq!(ev_period_a, period_a); assert_eq!(ev_arch_a, archetype_a); - // Verify second mint event (user_b) let (_, topics_b, data_b) = mint_events.get(1).unwrap(); + let ev_version: Symbol = topics_b.get(0).unwrap().try_into_val(&env).unwrap(); let ev_user_b: Address = topics_b.get(2).unwrap().try_into_val(&env).unwrap(); let ev_period_b: u64 = topics_b.get(3).unwrap().try_into_val(&env).unwrap(); let ev_arch_b: Symbol = data_b.try_into_val(&env).unwrap(); @@ -738,8 +488,6 @@ fn test_mint_events_multiple_users_correct_schema() { assert_eq!(ev_arch_b, archetype_b); } -// ─── Issue #80: verify_data tests ─────────────────────────────────────────── - #[test] fn test_verify_data_matching_hash() { let env = Env::default(); @@ -753,7 +501,6 @@ fn test_verify_data_matching_hash() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let data_json = Bytes::from_slice(&env, b"{\"score\":100,\"level\":\"gold\"}"); let data_hash_raw = env.crypto().sha256(&data_json); @@ -769,8 +516,8 @@ fn test_verify_data_matching_hash() { period, &archetype, &data_hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); assert!(client.verify_data(&user, &period, &data_json)); } @@ -788,7 +535,6 @@ fn test_verify_data_non_matching_hash() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let original_data = Bytes::from_slice(&env, b"{\"score\":100}"); let data_hash_raw = env.crypto().sha256(&original_data); @@ -804,8 +550,8 @@ fn test_verify_data_non_matching_hash() { period, &archetype, &data_hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); let tampered_data = Bytes::from_slice(&env, b"{\"score\":999}"); assert!(!client.verify_data(&user, &period, &tampered_data)); @@ -826,59 +572,6 @@ fn test_verify_data_no_wrap_exists() { assert!(!client.verify_data(&user, &9999, &data)); } -// ─── Issue #93: compute_data_hash tests ───────────────────────────────────── - -#[test] -fn test_compute_data_hash_matches_mint_wrap_stored_hash() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[7u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let data_json = Bytes::from_slice(&env, b"{\"score\":100,\"level\":\"gold\"}"); - let computed = client.compute_data_hash(&data_json); - let archetype = symbol_short!("arch"); - let period = 2024u64; - - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - &computed, - ); - client.mint_wrap(&user, &period, &archetype, &computed, &0u64, &signature); - - let wrap = client.get_wrap(&user, &period).unwrap(); - assert_eq!(wrap.data_hash, computed); -} - -#[test] -fn test_compute_data_hash_matches_off_chain_sha256() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let data_json = Bytes::from_slice(&env, b"{\"persona\":\"builder\",\"tx_count\":42}"); - let on_chain = client.compute_data_hash(&data_json); - let off_chain_raw = env.crypto().sha256(&data_json); - let off_chain = BytesN::from_array(&env, &off_chain_raw.to_array()); - - assert_eq!(on_chain, off_chain); - assert!(client.verify_data(&Address::generate(&env), &9999, &data_json) == false); -} - -// ─── Issue #87: get_latest_wrap tests ─────────────────────────────────────── - #[test] fn test_get_latest_wrap_returns_most_recent() { let env = Env::default(); @@ -892,7 +585,6 @@ fn test_get_latest_wrap_returns_most_recent() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let archetype = symbol_short!("arch"); let hash1 = BytesN::from_array(&env, &[10u8; 32]); @@ -907,7 +599,6 @@ fn test_get_latest_wrap_returns_most_recent() { 2022, &archetype, &hash1, - u32::MAX, ); let sig2 = sign_payload( &env, @@ -917,7 +608,6 @@ fn test_get_latest_wrap_returns_most_recent() { 2024, &archetype, &hash2, - u32::MAX, ); let sig3 = sign_payload( &env, @@ -927,9 +617,11 @@ fn test_get_latest_wrap_returns_most_recent() { 2023, &archetype, &hash3, - u32::MAX, ); + client.mint_wrap(&user, &2022, &archetype, &hash1, &sig1); + client.mint_wrap(&user, &2024, &archetype, &hash2, &sig2); + client.mint_wrap(&user, &2023, &archetype, &hash3, &sig3); let latest = client.get_latest_wrap(&user).unwrap(); assert_eq!(latest.period, 2024); @@ -963,7 +655,6 @@ fn test_get_latest_wrap_single_mint() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let hash = BytesN::from_array(&env, &[55u8; 32]); let archetype = symbol_short!("arch"); @@ -977,270 +668,41 @@ fn test_get_latest_wrap_single_mint() { period, &archetype, &hash, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash, &sig); let latest = client.get_latest_wrap(&user).unwrap(); assert_eq!(latest.period, 2025); assert_eq!(latest.data_hash, hash); } -// ─── Issue #138: compare_wraps tests ──────────────────────────────────────── - #[test] -fn test_compare_wraps_both_users_have_wraps_same_archetype() { +#[should_panic(expected = "Error(Contract, #2)")] +fn test_mint_wrap_before_init_fails() { let env = Env::default(); let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); + env.mock_all_auths(); - let signing_key = SigningKey::from_bytes(&[40u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); + let user = Address::generate(&env); + let hash = BytesN::from_array(&env, &[1u8; 32]); + let archetype = symbol_short!("arch"); + let sig = BytesN::from_array(&env, &[0u8; 64]); - client.initialize(&admin, &admin_pubkey); + client.mint_wrap(&user, &2024, &archetype, &hash, &sig); +} + +#[test] +#[should_panic(expected = "Error(Contract, #2)")] +fn test_update_admin_before_init_fails() { + let env = Env::default(); + let contract_id = env.register_contract(None, StellarWrapContract); + let client = StellarWrapContractClient::new(&env, &contract_id); env.mock_all_auths(); - let period = 2026u64; - let archetype = symbol_short!("arch"); - let hash_a = BytesN::from_array(&env, &[40u8; 32]); - let hash_b = BytesN::from_array(&env, &[41u8; 32]); - - let sig_a = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - period, - &archetype, - &hash_a, - ); - let sig_b = sign_payload( - &env, - &signing_key, - &contract_id, - &user_b, - period, - &archetype, - &hash_b, - ); - - client.mint_wrap(&user_a, &period, &archetype, &hash_a, &sig_a); - client.mint_wrap(&user_b, &period, &archetype, &hash_b, &sig_b); - - let comparison = client.compare_wraps(&user_a, &user_b, &period); - assert_eq!(comparison.period, period); - assert!(comparison.both_have_wrap); - assert!(comparison.same_archetype); - assert_eq!( - comparison.user_a_wrap, - WrapRecordOption::Some(client.get_wrap(&user_a, &period).unwrap()) - ); - assert_eq!( - comparison.user_b_wrap, - WrapRecordOption::Some(client.get_wrap(&user_b, &period).unwrap()) - ); -} - -#[test] -fn test_compare_wraps_both_users_have_wraps_different_archetypes() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[41u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let period = 2026u64; - let archetype_a = symbol_short!("arch"); - let archetype_b = symbol_short!("defi"); - let hash_a = BytesN::from_array(&env, &[42u8; 32]); - let hash_b = BytesN::from_array(&env, &[43u8; 32]); - - let sig_a = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - period, - &archetype_a, - &hash_a, - ); - let sig_b = sign_payload( - &env, - &signing_key, - &contract_id, - &user_b, - period, - &archetype_b, - &hash_b, - ); - - client.mint_wrap(&user_a, &period, &archetype_a, &hash_a, &sig_a); - client.mint_wrap(&user_b, &period, &archetype_b, &hash_b, &sig_b); - - let comparison = client.compare_wraps(&user_a, &user_b, &period); - assert!(comparison.both_have_wrap); - assert!(!comparison.same_archetype); - assert_eq!( - comparison.user_a_wrap, - WrapRecordOption::Some(client.get_wrap(&user_a, &period).unwrap()) - ); - assert_eq!( - comparison.user_b_wrap, - WrapRecordOption::Some(client.get_wrap(&user_b, &period).unwrap()) - ); -} - -#[test] -fn test_compare_wraps_one_user_has_wrap() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[42u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let period = 2026u64; - let archetype = symbol_short!("arch"); - let hash_a = BytesN::from_array(&env, &[44u8; 32]); - let sig_a = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - period, - &archetype, - &hash_a, - ); - - client.mint_wrap(&user_a, &period, &archetype, &hash_a, &sig_a); - - let comparison = client.compare_wraps(&user_a, &user_b, &period); - assert!(!comparison.both_have_wrap); - assert!(!comparison.same_archetype); - assert!(comparison.user_a_wrap.is_some()); - assert!(comparison.user_b_wrap.is_none()); -} - -#[test] -fn test_compare_wraps_neither_user_has_wrap() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let pubkey = BytesN::from_array(&env, &[45u8; 32]); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); - - client.initialize(&admin, &pubkey); - - let comparison = client.compare_wraps(&user_a, &user_b, &2026u64); - assert!(!comparison.both_have_wrap); - assert!(!comparison.same_archetype); - assert!(comparison.user_a_wrap.is_none()); - assert!(comparison.user_b_wrap.is_none()); -} - -#[test] -fn test_compare_total_wraps_returns_lifetime_counts() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[43u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - - let archetype = symbol_short!("arch"); - let hash_a_1 = BytesN::from_array(&env, &[46u8; 32]); - let hash_a_2 = BytesN::from_array(&env, &[47u8; 32]); - let hash_b = BytesN::from_array(&env, &[48u8; 32]); - - let sig_a_1 = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - 2024, - &archetype, - &hash_a_1, - ); - let sig_a_2 = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - 2025, - &archetype, - &hash_a_2, - ); - let sig_b = sign_payload( - &env, - &signing_key, - &contract_id, - &user_b, - 2026, - &archetype, - &hash_b, - ); - - client.mint_wrap(&user_a, &2024, &archetype, &hash_a_1, &sig_a_1); - client.mint_wrap(&user_a, &2025, &archetype, &hash_a_2, &sig_a_2); - client.mint_wrap(&user_b, &2026, &archetype, &hash_b, &sig_b); - - let totals = client.compare_total_wraps(&user_a, &user_b); - assert_eq!(totals, (2, 1)); -} - -// ─── Issue #85: negative tests before initialize ──────────────────────────── - -#[test] -#[should_panic(expected = "Error(Contract, #2)")] -fn test_mint_wrap_before_init_fails() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - env.mock_all_auths(); - - let admin = Address::generate(&env); - let user = Address::generate(&env); - let hash = BytesN::from_array(&env, &[1u8; 32]); - let archetype = symbol_short!("arch"); - let sig = BytesN::from_array(&env, &[0u8; 64]); - -} - -#[test] -#[should_panic(expected = "Error(Contract, #2)")] -fn test_update_admin_before_init_fails() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - env.mock_all_auths(); - - let new_admin = Address::generate(&env); - client.update_admin(&new_admin); -} + let new_admin = Address::generate(&env); + client.update_admin(&new_admin); +} #[test] fn test_get_admin_before_init_returns_none() { @@ -1251,8 +713,6 @@ fn test_get_admin_before_init_returns_none() { assert!(client.get_admin().is_none()); } -// ─── Issue #27: revoke_wrap tests ───────────────────────────────────────── - #[test] fn test_revoke_wrap_flow_event_and_remint() { let env = Env::default(); @@ -1266,7 +726,6 @@ fn test_revoke_wrap_flow_event_and_remint() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2026u64; let archetype = symbol_short!("arch"); @@ -1281,8 +740,8 @@ fn test_revoke_wrap_flow_event_and_remint() { period, &archetype, &hash_1, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash_1, &sig_1); assert_eq!(client.balance_of(&user), 1); client.revoke_wrap(&user, &period); @@ -1294,17 +753,18 @@ fn test_revoke_wrap_flow_event_and_remint() { let last_event = events.last().expect("Expected revoke event"); let (_, topics, data) = last_event; + let event_version: Symbol = topics.get(0).unwrap().try_into_val(&env).unwrap(); let event_topic: Symbol = topics.get(1).unwrap().try_into_val(&env).unwrap(); let event_user: Address = topics.get(2).unwrap().try_into_val(&env).unwrap(); let event_period: u64 = topics.get(3).unwrap().try_into_val(&env).unwrap(); let revoked: bool = data.try_into_val(&env).unwrap(); + assert_eq!(event_version, symbol_short!("v1")); assert_eq!(event_topic, symbol_short!("revoke")); assert_eq!(event_user, user); assert_eq!(event_period, period); assert!(revoked); - // Re-mint the same period should now succeed after revoke. let sig_2 = sign_payload( &env, &signing_key, @@ -1313,8 +773,8 @@ fn test_revoke_wrap_flow_event_and_remint() { period, &archetype, &hash_2, - u32::MAX, ); + client.mint_wrap(&user, &period, &archetype, &hash_2, &sig_2); let wrap = client.get_wrap(&user, &period).unwrap(); assert_eq!(wrap.data_hash, hash_2); @@ -1351,7 +811,6 @@ fn test_revoke_requires_admin_auth() { client.initialize(&admin, &admin_pubkey); - // Seed one wrap record directly to isolate auth behavior on revoke. env.as_contract(&contract_id, || { let wrap_key = DataKey::Wrap(user.clone(), 2026); let count_key = DataKey::WrapCount(user.clone()); @@ -1360,18 +819,14 @@ fn test_revoke_requires_admin_auth() { data_hash: BytesN::from_array(&env, &[16u8; 32]), archetype: symbol_short!("arch"), period: 2026, - image_uri: String::from_str(&env, ""), }; env.storage().persistent().set(&wrap_key, &record); env.storage().persistent().set(&count_key, &1u32); }); - // No auth mocking: admin.require_auth() must fail. client.revoke_wrap(&user, &2026); } -// ─── Issue #82: temporary mint guard tests ───────────────────────────────── - #[test] fn test_mint_guard_uses_temporary_storage_and_clears_on_success() { let env = Env::default(); @@ -1385,7 +840,6 @@ fn test_mint_guard_uses_temporary_storage_and_clears_on_success() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2026u64; let archetype = symbol_short!("arch"); @@ -1398,10 +852,8 @@ fn test_mint_guard_uses_temporary_storage_and_clears_on_success() { period, &archetype, &data_hash, - u32::MAX, ); - // Successful mint — guard must be set then cleared. client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); let guard_key = DataKey::MintGuard(user.clone()); @@ -1424,7 +876,6 @@ fn test_mint_guard_on_failure_leaves_no_residual_state() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2026u64; let archetype = symbol_short!("arch"); @@ -1437,28 +888,22 @@ fn test_mint_guard_on_failure_leaves_no_residual_state() { period, &archetype, &data_hash, - u32::MAX, ); - // First mint succeeds. client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - // Second mint with same (user, period) fails due to duplicate guard. - let duplicate = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); + let duplicate = catch_unwind(AssertUnwindSafe(|| { + client.mint_wrap(&user, &period, &archetype, &data_hash, &signature) })); assert!(duplicate.is_err()); let guard_key = DataKey::MintGuard(user.clone()); env.as_contract(&contract_id, || { - // Failed invocations revert, so no leftover guard entry remains. assert!(!env.storage().temporary().has(&guard_key)); assert!(!env.storage().persistent().has(&guard_key)); }); } -// ─── Issue #39: update_admin event emission test ──────────────────────────── - #[test] fn test_update_admin_emits_event() { let env = Env::default(); @@ -1478,24 +923,21 @@ fn test_update_admin_emits_event() { let last_event = events.last().expect("Expected at least one event"); let (_, topics, data) = last_event; - let topic_0: Address = topics.get(0).unwrap().try_into_val(&env).unwrap(); + let topic_0: Symbol = topics.get(0).unwrap().try_into_val(&env).unwrap(); let topic_1: Symbol = topics.get(1).unwrap().try_into_val(&env).unwrap(); let topic_2: Symbol = topics.get(2).unwrap().try_into_val(&env).unwrap(); + assert_eq!(topic_0, symbol_short!("v1")); assert_eq!(topic_1, symbol_short!("admin")); assert_eq!(topic_2, symbol_short!("updated")); - // data is (old_admin, new_admin) let (old_admin_val, new_admin_val): (Address, Address) = data.try_into_val(&env).unwrap(); assert_eq!(old_admin_val, admin); assert_eq!(new_admin_val, new_admin); } -// ─── Issue #34: update_admin authorization failure tests ──────────────────── - #[test] #[should_panic] fn test_update_admin_unauthorized_fails() { - // No mock_all_auths — auth requirement is not satisfied, should panic let env = Env::default(); let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); @@ -1511,7 +953,6 @@ fn test_update_admin_unauthorized_fails() { #[test] #[should_panic] fn test_update_admin_by_non_admin_fails() { - // A different address tries to call update_admin — should panic let env = Env::default(); let contract_id = env.register_contract(None, StellarWrapContract); let client = StellarWrapContractClient::new(&env, &contract_id); @@ -1523,7 +964,6 @@ fn test_update_admin_by_non_admin_fails() { client.initialize(&admin, &pubkey); - // Only mock auth for non_admin — current_admin.require_auth() will fail env.mock_auths(&[soroban_sdk::testutils::MockAuth { address: &non_admin, invoke: &soroban_sdk::testutils::MockAuthInvoke { @@ -1537,8 +977,6 @@ fn test_update_admin_by_non_admin_fails() { client.update_admin(&new_admin); } -// ─── Issue #55: zero-hash and edge-case hash tests ────────────────────────── - #[test] #[should_panic] fn test_mint_wrap_zero_hash_rejected() { @@ -1558,6 +996,16 @@ fn test_mint_wrap_zero_hash_rejected() { let archetype = symbol_short!("arch"); let period = 2024u64; + let sig = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &zero_hash, + ); + client.mint_wrap(&user, &period, &archetype, &zero_hash, &sig); } #[test] @@ -1573,15 +1021,23 @@ fn test_mint_wrap_non_zero_hash_succeeds() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); - // A hash with only the last byte set — not all-zero, should succeed let mut hash_bytes = [0u8; 32]; hash_bytes[31] = 1; let edge_hash = BytesN::from_array(&env, &hash_bytes); let archetype = symbol_short!("arch"); let period = 2024u64; + let sig = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &edge_hash, + ); + client.mint_wrap(&user, &period, &archetype, &edge_hash, &sig); let wrap = client.get_wrap(&user, &period).unwrap(); assert_eq!(wrap.data_hash, edge_hash); @@ -1600,19 +1056,26 @@ fn test_mint_wrap_max_hash_succeeds() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let max_hash = BytesN::from_array(&env, &[0xff; 32]); let archetype = symbol_short!("arch"); let period = 2024u64; + let sig = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &max_hash, + ); + client.mint_wrap(&user, &period, &archetype, &max_hash, &sig); let wrap = client.get_wrap(&user, &period).unwrap(); assert_eq!(wrap.data_hash, max_hash); } -// ─── Issue #30: upgrade authorization test ────────────────────────────────── - #[test] #[should_panic] fn test_upgrade_requires_admin_auth() { @@ -1624,13 +1087,10 @@ fn test_upgrade_requires_admin_auth() { let pubkey = BytesN::from_array(&env, &[1u8; 32]); client.initialize(&admin, &pubkey); - // No auth mocked — must panic because admin did not authorize let fake_wasm = BytesN::from_array(&env, &[0u8; 32]); client.upgrade(&fake_wasm); } -// ─── Issue #59: update_wrap tests ────────────────────────────────────────── - fn sign_update_payload( env: &Env, signer: &SigningKey, @@ -1640,6 +1100,15 @@ fn sign_update_payload( new_archetype: &Symbol, new_data_hash: &BytesN<32>, ) -> BytesN<64> { + sign_payload( + env, + signer, + contract, + user, + period, + new_archetype, + new_data_hash, + ) } #[test] @@ -1655,17 +1124,36 @@ fn test_update_wrap_succeeds_and_preserves_timestamp() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2025u64; let archetype = symbol_short!("arch"); let hash1 = BytesN::from_array(&env, &[41u8; 32]); + let sig1 = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &hash1, + ); + client.mint_wrap(&user, &period, &archetype, &hash1, &sig1); let before = client.get_wrap(&user, &period).unwrap(); let new_hash = BytesN::from_array(&env, &[99u8; 32]); let new_arch = symbol_short!("builder"); + let sig2 = sign_update_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &new_arch, + &new_hash, + ); + client.update_wrap(&user, &period, &new_hash, &new_arch, &sig2); let after = client.get_wrap(&user, &period).unwrap(); assert_eq!( @@ -1690,21 +1178,48 @@ fn test_update_wrap_emits_update_event() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2025u64; let archetype = symbol_short!("arch"); let hash1 = BytesN::from_array(&env, &[41u8; 32]); + let sig1 = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &hash1, + ); + client.mint_wrap(&user, &period, &archetype, &hash1, &sig1); + + let new_hash = BytesN::from_array(&env, &[98u8; 32]); + let new_arch = symbol_short!("defi"); + let sig2 = sign_update_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &new_arch, + &new_hash, + ); + client.update_wrap(&user, &period, &new_hash, &new_arch, &sig2); let events = env.events().all(); let last_event = events.last().unwrap(); let (_, topics, data) = last_event; + let topic_0: Symbol = topics.get(0).unwrap().try_into_val(&env).unwrap(); let topic_1: Symbol = topics.get(1).unwrap().try_into_val(&env).unwrap(); let topic_2: Address = topics.get(2).unwrap().try_into_val(&env).unwrap(); let topic_3: u64 = topics.get(3).unwrap().try_into_val(&env).unwrap(); let ev_arch: Symbol = data.try_into_val(&env).unwrap(); + assert_eq!(topic_0, symbol_short!("v1")); + assert_eq!(topic_1, symbol_short!("update")); + assert_eq!(topic_2, user); + assert_eq!(topic_3, period); assert_eq!(ev_arch, new_arch); } @@ -1722,10 +1237,19 @@ fn test_update_wrap_nonexistent_fails() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let new_hash = BytesN::from_array(&env, &[99u8; 32]); let new_arch = symbol_short!("arch"); + let sig = sign_update_payload( + &env, + &signing_key, + &contract_id, + &user, + 9999, + &new_arch, + &new_hash, + ); + client.update_wrap(&user, &9999, &new_hash, &new_arch, &sig); } #[test] @@ -1742,13 +1266,21 @@ fn test_update_wrap_requires_admin_auth() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2025u64; let archetype = symbol_short!("arch"); let hash1 = BytesN::from_array(&env, &[41u8; 32]); + let sig1 = sign_payload( + &env, + &signing_key, + &contract_id, + &user, + period, + &archetype, + &hash1, + ); + client.mint_wrap(&user, &period, &archetype, &hash1, &sig1); - // Reset auth — no admin auth mocked let env2 = Env::default(); let contract_id2 = env2.register_contract(None, StellarWrapContract); let client2 = StellarWrapContractClient::new(&env2, &contract_id2); @@ -1765,8 +1297,7 @@ fn test_update_wrap_requires_admin_auth() { &new_arch, &new_hash, ); - // No auth mocked — must panic - client2.update_wrap(&user, &period, &new_hash, &new_arch, &sig2, &None); + client2.update_wrap(&user, &period, &new_hash, &new_arch, &sig2); } #[test] @@ -1783,227 +1314,30 @@ fn test_update_wrap_zero_hash_rejected() { client.initialize(&admin, &admin_pubkey); env.mock_all_auths(); - register_test_archetypes(&client); let period = 2025u64; let archetype = symbol_short!("arch"); let hash1 = BytesN::from_array(&env, &[41u8; 32]); - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - -// ─── Issue #53: has_wrap() tests ──────────────────────────────────────────── - -/// has_wrap() returns false before any wrap is minted for a user+period pair. -#[test] -fn test_has_wrap_returns_false_before_mint() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let admin = Address::generate(&env); - let pubkey = BytesN::from_array(&env, &[60u8; 32]); - client.initialize(&admin, &pubkey); - - let user = Address::generate(&env); - let period = 202406u64; - - assert!(!client.has_wrap(&user, &period)); -} - -/// has_wrap() returns true after a successful mint for the same user+period. -#[test] -fn test_has_wrap_returns_true_after_mint() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[61u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let period = 202406u64; - let archetype = symbol_short!("arch"); - let data_hash = BytesN::from_array(&env, &[61u8; 32]); - let signature = sign_payload( + let sig1 = sign_payload( &env, &signing_key, &contract_id, &user, period, &archetype, - &data_hash, - u32::MAX, - ); - - // Before mint: should not exist. - assert!(!client.has_wrap(&user, &period)); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - - // After mint: must exist. - assert!(client.has_wrap(&user, &period)); -} - -/// has_wrap() is period-scoped: true for minted period, false for different period. -#[test] -fn test_has_wrap_is_scoped_to_period() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[62u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let period_a = 202406u64; - let period_b = 202407u64; - let archetype = symbol_short!("arch"); - let data_hash = BytesN::from_array(&env, &[62u8; 32]); - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user, - period_a, - &archetype, - &data_hash, - u32::MAX, + &hash1, ); + client.mint_wrap(&user, &period, &archetype, &hash1, &sig1); - client.mint_wrap(&user, &period_a, &archetype, &data_hash, &signature); - - assert!(client.has_wrap(&user, &period_a)); - assert!(!client.has_wrap(&user, &period_b)); -} - -/// has_wrap() returns false after the wrap is revoked. -#[test] -fn test_has_wrap_returns_false_after_revoke() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[63u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let period = 202406u64; - let archetype = symbol_short!("arch"); - let data_hash = BytesN::from_array(&env, &[63u8; 32]); - let signature = sign_payload( + let zero_hash = BytesN::from_array(&env, &[0u8; 32]); + let sig2 = sign_update_payload( &env, &signing_key, &contract_id, &user, period, &archetype, - &data_hash, - u32::MAX, - ); - - client.mint_wrap(&user, &period, &archetype, &data_hash, &signature); - assert!(client.has_wrap(&user, &period)); - - client.revoke_wrap(&user, &period); - assert!(!client.has_wrap(&user, &period)); -} - -/// has_wrap() is user-scoped: true for user A, false for user B on same period. -#[test] -fn test_has_wrap_is_scoped_to_user() { - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - let signing_key = SigningKey::from_bytes(&[64u8; 32]); - let admin_pubkey = BytesN::from_array(&env, &signing_key.verifying_key().to_bytes()); - let admin = Address::generate(&env); - let user_a = Address::generate(&env); - let user_b = Address::generate(&env); - - client.initialize(&admin, &admin_pubkey); - env.mock_all_auths(); - register_test_archetypes(&client); - - let period = 202406u64; - let archetype = symbol_short!("arch"); - let data_hash = BytesN::from_array(&env, &[64u8; 32]); - let signature = sign_payload( - &env, - &signing_key, - &contract_id, - &user_a, - period, - &archetype, - &data_hash, - u32::MAX, + &zero_hash, ); - - client.mint_wrap(&user_a, &period, &archetype, &data_hash, &signature); - - assert!(client.has_wrap(&user_a, &period)); - assert!(!client.has_wrap(&user_b, &period)); -} - - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - - &env, - &signing_key, - &contract_id, - &user, - period, - &archetype, - let env = Env::default(); - let contract_id = env.register_contract(None, StellarWrapContract); - let client = StellarWrapContractClient::new(&env, &contract_id); - + client.update_wrap(&user, &period, &zero_hash, &archetype, &sig2); }