From 779aa3818f92b16be9df76ed677569488028fbaa Mon Sep 17 00:00:00 2001 From: razeprasine Date: Tue, 2 Jun 2026 10:41:21 +0100 Subject: [PATCH 1/3] fixes issue 276 --- creator-keys/tests/contract_test_env/mod.rs | 87 +++++++++++++++++++++ creator-keys/tests/supply_invariants.rs | 31 +++++++- 2 files changed, 115 insertions(+), 3 deletions(-) diff --git a/creator-keys/tests/contract_test_env/mod.rs b/creator-keys/tests/contract_test_env/mod.rs index 3274978..d4b4bca 100644 --- a/creator-keys/tests/contract_test_env/mod.rs +++ b/creator-keys/tests/contract_test_env/mod.rs @@ -12,6 +12,7 @@ use soroban_sdk::{ testutils::{Address as _, Ledger}, Address, Env, String, }; +use std::string::String as StdString; /// Stable timestamp used by integration tests unless a test needs to override it. pub const DEFAULT_TEST_TIMESTAMP: u64 = 1_700_000_000; @@ -30,6 +31,16 @@ pub fn test_env_with_auths() -> Env { env } +/// Return a deterministic test wallet address for a seed string. +pub fn test_wallet_address(env: &Env, seed: &str) -> Address { + Address::from_string(&String::from_str(env, &account_strkey_from_seed(seed))) +} + +/// Return a deterministic test wallet address for an index. +pub fn test_wallet_address_from_index(env: &Env, index: u32) -> Address { + test_wallet_address(env, &std::format!("wallet-{index}")) +} + /// Register [`CreatorKeysContract`] and return a client and the contract id. pub fn register_creator_keys<'a>(env: &'a Env) -> (CreatorKeysContractClient<'a>, Address) { let id = env.register(CreatorKeysContract, ()); @@ -93,3 +104,79 @@ pub fn set_stored_key_price(env: &Env, contract_id: &Address, price: i128) { .set(&constants::storage::KEY_PRICE, &price); }); } + +fn account_strkey_from_seed(seed: &str) -> StdString { + let mut payload = [0u8; 32]; + let mut state = 0xcbf2_9ce4_8422_2325u64; + + for byte in seed.as_bytes() { + state ^= u64::from(*byte); + state = state.wrapping_mul(0x0000_0100_0000_01b3); + } + + for chunk in payload.chunks_mut(8) { + state ^= state >> 33; + state = state.wrapping_mul(0xff51_afd7_ed55_8ccd); + state ^= state >> 33; + state = state.wrapping_mul(0xc4ce_b9fe_1a85_ec53); + state ^= state >> 33; + chunk.copy_from_slice(&state.to_be_bytes()); + } + + let mut raw = [0u8; 35]; + raw[0] = 6 << 3; + raw[1..33].copy_from_slice(&payload); + let checksum = crc16_xmodem(&raw[..33]).to_le_bytes(); + raw[33..].copy_from_slice(&checksum); + + base32_encode(&raw) +} + +fn crc16_xmodem(bytes: &[u8]) -> u16 { + let mut crc = 0u16; + + for byte in bytes { + crc ^= u16::from(*byte) << 8; + for _ in 0..8 { + if crc & 0x8000 != 0 { + crc = (crc << 1) ^ 0x1021; + } else { + crc <<= 1; + } + } + } + + crc +} + +fn base32_encode(bytes: &[u8]) -> StdString { + const ALPHABET: &[u8; 32] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + + let mut encoded = StdString::new(); + let mut buffer = 0u16; + let mut bits = 0u8; + + for byte in bytes { + buffer = (buffer << 8) | u16::from(*byte); + bits += 8; + + while bits >= 5 { + bits -= 5; + let index = ((buffer >> bits) & 0x1f) as usize; + encoded.push(ALPHABET[index] as char); + } + + if bits > 0 { + buffer &= (1 << bits) - 1; + } else { + buffer = 0; + } + } + + if bits > 0 { + let index = ((buffer << (5 - bits)) & 0x1f) as usize; + encoded.push(ALPHABET[index] as char); + } + + encoded +} diff --git a/creator-keys/tests/supply_invariants.rs b/creator-keys/tests/supply_invariants.rs index 75b48eb..ad8007f 100644 --- a/creator-keys/tests/supply_invariants.rs +++ b/creator-keys/tests/supply_invariants.rs @@ -10,6 +10,7 @@ mod contract_test_env; use contract_test_env::{ register_creator_keys, register_test_creator, set_key_price_for_tests, test_env_with_auths, + test_wallet_address, test_wallet_address_from_index, }; use creator_keys::CreatorKeysContractClient; use soroban_sdk::{testutils::Address as _, Address, Env}; @@ -23,6 +24,30 @@ fn setup<'a>(env: &'a Env, price: i128) -> (CreatorKeysContractClient<'a>, Addre // ── Supply conservation: buy then sell returns to prior state ───────────���─ +#[test] +fn test_wallet_address_returns_same_address_for_same_seed() { + let env = test_env_with_auths(); + + assert_eq!( + test_wallet_address(&env, "same-seed"), + test_wallet_address(&env, "same-seed") + ); +} + +#[test] +fn test_wallet_address_returns_distinct_addresses_for_distinct_inputs() { + let env = test_env_with_auths(); + + assert_ne!( + test_wallet_address_from_index(&env, 1), + test_wallet_address_from_index(&env, 2) + ); + assert_ne!( + test_wallet_address(&env, "seed-a"), + test_wallet_address(&env, "seed-b") + ); +} + #[test] fn test_supply_buy_then_sell_returns_to_zero() { let env = test_env_with_auths(); @@ -73,9 +98,9 @@ fn test_supply_three_buyers_sum_equals_total() { let env = test_env_with_auths(); let (client, creator) = setup(&env, 100); - let b1 = Address::generate(&env); - let b2 = Address::generate(&env); - let b3 = Address::generate(&env); + let b1 = test_wallet_address_from_index(&env, 1); + let b2 = test_wallet_address_from_index(&env, 2); + let b3 = test_wallet_address_from_index(&env, 3); client.buy_key(&creator, &b1, &100); client.buy_key(&creator, &b2, &100); From 56b504474a0d2c7081cebcdbe790765f9253a800 Mon Sep 17 00:00:00 2001 From: razeprasine Date: Tue, 2 Jun 2026 11:16:33 +0100 Subject: [PATCH 2/3] fixes issue 275 --- creator-keys/tests/buy_key.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/creator-keys/tests/buy_key.rs b/creator-keys/tests/buy_key.rs index 8f183d6..998a7e1 100644 --- a/creator-keys/tests/buy_key.rs +++ b/creator-keys/tests/buy_key.rs @@ -3,7 +3,8 @@ mod contract_test_env; use contract_test_env::{ - register_creator_keys, register_test_creator, set_key_price_for_tests, test_env_with_auths, + register_creator_keys, register_test_creator, set_key_price_for_tests, set_protocol_fee_bps, + test_env_with_auths, }; use creator_keys::ContractError; use soroban_sdk::{testutils::Address as _, Address}; @@ -13,10 +14,24 @@ fn test_buy_key_unregistered_creator_fails() { let env = test_env_with_auths(); let (client, _) = register_creator_keys(&env); set_key_price_for_tests(&env, &client, 100i128); + set_protocol_fee_bps(&env, &client, 9000u32, 1000u32); let creator = Address::generate(&env); let buyer = Address::generate(&env); + + let fee_view_before = client.get_protocol_fee_view(); + assert_eq!(client.get_total_key_supply(&creator), 0); + assert_eq!(client.get_key_balance(&creator, &buyer), 0); + let result = client.try_buy_key(&creator, &buyer, &100i128); + assert_eq!(result, Err(Ok(ContractError::NotRegistered))); + assert_eq!(client.get_total_key_supply(&creator), 0); + assert_eq!(client.get_key_balance(&creator, &buyer), 0); + + let fee_view_after = client.get_protocol_fee_view(); + assert_eq!(fee_view_after.creator_bps, fee_view_before.creator_bps); + assert_eq!(fee_view_after.protocol_bps, fee_view_before.protocol_bps); + assert_eq!(fee_view_after.is_configured, fee_view_before.is_configured); } #[test] From 97a00a6a75e13b044878cae556126edffc82798b Mon Sep 17 00:00:00 2001 From: razeprasine Date: Tue, 2 Jun 2026 11:48:05 +0100 Subject: [PATCH 3/3] fixes issue 274 --- creator-keys/tests/key_supply.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/creator-keys/tests/key_supply.rs b/creator-keys/tests/key_supply.rs index 3bad7b0..69f7361 100644 --- a/creator-keys/tests/key_supply.rs +++ b/creator-keys/tests/key_supply.rs @@ -55,6 +55,30 @@ fn test_get_total_key_supply_increments_after_buy() { assert_eq!(client.get_total_key_supply(&creator), 2); } +#[test] +fn test_get_total_key_supply_increments_after_three_sequential_buys() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _) = setup(&env); + + let creator = Address::generate(&env); + let buyer1 = Address::generate(&env); + let buyer2 = Address::generate(&env); + let buyer3 = Address::generate(&env); + client.register_creator(&creator, &String::from_str(&env, "alice")); + + assert_eq!(client.get_total_key_supply(&creator), 0); + + client.buy_key(&creator, &buyer1, &100_i128); + assert_eq!(client.get_total_key_supply(&creator), 1); + + client.buy_key(&creator, &buyer2, &100_i128); + assert_eq!(client.get_total_key_supply(&creator), 2); + + client.buy_key(&creator, &buyer3, &100_i128); + assert_eq!(client.get_total_key_supply(&creator), 3); +} + #[test] fn test_get_total_key_supply_is_read_only() { let env = Env::default();