Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion creator-keys/tests/buy_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod contract_test_env;

use contract_test_env::{
compute_expected_buy_price, register_creator_keys, register_test_creator,
set_key_price_for_tests, test_env_with_auths,
set_key_price_for_tests, set_protocol_fee_bps, test_env_with_auths,
};
use creator_keys::ContractError;
use soroban_sdk::{testutils::Address as _, Address};
Expand All @@ -15,12 +15,26 @@ fn test_buy_key_unregistered_creator_fails() {
let (client, _) = register_creator_keys(&env);
let base_price = 100i128;
set_key_price_for_tests(&env, &client, base_price);
set_protocol_fee_bps(&env, &client, 9000u32, 1000u32);
let creator = Address::generate(&env);
let buyer = Address::generate(&env);

let expected_price = compute_expected_buy_price(0, base_price);

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, &expected_price);

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]
Expand Down
86 changes: 86 additions & 0 deletions creator-keys/tests/contract_test_env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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;
Expand Down Expand Up @@ -43,6 +44,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, ());
Expand Down Expand Up @@ -132,6 +143,81 @@ pub fn set_stored_key_price(env: &Env, contract_id: &Address, price: i128) {
});
}

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
}
/// Computes the expected buy price for a given supply value.
///
/// Current bonding curve formula:
Expand Down
31 changes: 28 additions & 3 deletions creator-keys/tests/supply_invariants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Loading