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
61 changes: 61 additions & 0 deletions creator-keys/tests/creator_fee_rotation_regression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Regression test verifying creator fee recipient updates correctly after rotation.

mod contract_test_env;

use contract_test_env::{register_creator_keys, set_pricing_and_fees, test_env_with_auths};
use soroban_sdk::{testutils::Address as _, Address, String};

#[test]
fn test_creator_fee_recipient_rotation_regression() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
set_pricing_and_fees(&env, &client, 1000, 9000, 1000);

let creator = Address::generate(&env);
let initial_recipient = Address::generate(&env);
client.register_creator(
&creator,
&String::from_str(&env, "alice"),
&None,
&Some(initial_recipient.clone()),
&None,
&None,
);

// Initial fee recipient is set correctly
assert_eq!(
client.get_creator_fee_recipient(&creator),
initial_recipient
);

let buyer = Address::generate(&env);
let quote1 = client.get_buy_quote(&creator);

// Execute buy 1
client.buy_key(&creator, &buyer, &quote1.total_amount, &None);

// Initial recipient received the fee (tracked under creator's fee balance)
let balance_after_buy1 = client.get_creator_fee_balance(&creator);
assert_eq!(balance_after_buy1, quote1.creator_fee);

// Rotate the creator fee recipient to a new address
let new_recipient = Address::generate(&env);
client.update_creator_fee_recipient(&creator, &new_recipient);

// New recipient is set correctly
assert_eq!(client.get_creator_fee_recipient(&creator), new_recipient);

// Execute buy 2
let quote2 = client.get_buy_quote(&creator);
client.buy_key(&creator, &buyer, &quote2.total_amount, &None);

// New recipient receives fee after rotation (tracked under creator's fee balance)
let balance_after_buy2 = client.get_creator_fee_balance(&creator);
assert_eq!(balance_after_buy2 - balance_after_buy1, quote2.creator_fee);

// Since the old recipient is no longer the fee recipient, they receive nothing from subsequent trades.
assert_ne!(
client.get_creator_fee_recipient(&creator),
initial_recipient
);
}
51 changes: 51 additions & 0 deletions creator-keys/tests/dividend_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,54 @@ fn test_claim_dividend_event_topics_and_payload() {
assert_eq!(event.claimant, buyer);
assert_eq!(event.amount, claimed);
}

#[test]
fn test_distribute_dividend_event_fields_individual_assertions() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
// Set pricing and fees with 10% protocol fee
set_pricing_and_fees(&env, &client, 100, 9000, 1000);
let creator = register_test_creator(&env, &client, "alice");
let buyer = Address::generate(&env);

// Distribute to a creator with a known supply (e.g. 2 keys bought)
client.buy_key(&creator, &buyer, &100, &None);
client.buy_key(&creator, &buyer, &100, &None);

// Set env ledger to a positive non-zero sequence
let mut ledger_info = env.ledger().get();
ledger_info.sequence_number = 12345;
env.ledger().set(ledger_info);

let distributor = Address::generate(&env);
let gross_amount = 20_000i128;
distribute_test_dividend(&client, &creator, &distributor, gross_amount);

let events = env.events().all();
let (_, data) = events
.iter()
.rev()
.find_map(|(_, topics, data)| {
if topics == dividend_distributed_topics(&creator).into_val(&env) {
Some((topics, data))
} else {
None
}
})
.expect("DividendDistributed event not found");

let event: DividendDistributedEvent = data.into_val(&env);

// Assert creator matches the creator used
assert_eq!(event.creator, creator);

// Assert total_amount reflects the gross amount before protocol fee deduction (not the net amount)
assert_eq!(event.total_amount, gross_amount);

// Assert snapshot_supply matches total supply at distribution time
assert_eq!(event.snapshot_supply, 2);

// Assert ledger is a positive non-zero value
assert!(event.ledger > 0);
assert_eq!(event.ledger, 12345);
}
64 changes: 64 additions & 0 deletions creator-keys/tests/locked_allocation_claimed_view_regression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Regression test verifying `get_locked_allocation` returns claimed `true` after claim.

mod contract_test_env;

use contract_test_env::{register_creator_keys, test_env_with_auths};
use creator_keys::LockedAllocation;
use soroban_sdk::testutils::{Address as _, Ledger};
use soroban_sdk::{Address, String};

#[test]
fn test_get_locked_allocation_returns_claimed_true_after_claim() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);

let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
let unlock_ledger: u32 = 1000;
let amount: u32 = 50;

let mut ledger_info = env.ledger().get();
ledger_info.sequence_number = 1;
env.ledger().set(ledger_info.clone());

client.register_creator(
&creator,
&handle,
&Some(LockedAllocation {
amount,
unlock_ledger,
claimed: false,
}),
&None,
&None,
&None,
);

// Assert claimed field is false before claim
let alloc_before = client.get_locked_allocation(&creator).unwrap();
assert!(
!alloc_before.claimed,
"claimed field must be false before claim"
);

// Advance to exactly unlock_ledger.
ledger_info.sequence_number = unlock_ledger;
env.ledger().set(ledger_info);

// Claim the allocation
client.claim_locked_allocation(&creator);

// Call get_locked_allocation and assert claimed is true
let alloc_after1 = client.get_locked_allocation(&creator).unwrap();
assert!(
alloc_after1.claimed,
"claimed field must be true after successful claim"
);

// Call again and assert it is still true
let alloc_after2 = client.get_locked_allocation(&creator).unwrap();
assert!(
alloc_after2.claimed,
"claimed field must remain true on subsequent reads"
);
}
110 changes: 110 additions & 0 deletions creator-keys/tests/quadratic_curve_symmetry_regression.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Regression test verifying buy and sell quote symmetry for the Quadratic preset.
//!
//! The cost (price/fees) to buy N keys at supply S must equal the proceeds (price/fees)
//! from selling N keys at supply S+N.

mod contract_test_env;

use contract_test_env::{
register_creator_keys, set_curve_slope, set_pricing_and_fees, test_env_with_auths,
};
use creator_keys::CurvePreset;
use soroban_sdk::{testutils::Address as _, Address, String};

const KEY_PRICE: i128 = 1000;
const CREATOR_BPS: u32 = 9000;
const PROTOCOL_BPS: u32 = 1000;
const QUADRATIC_SLOPE: i128 = 10;

fn assert_symmetry_for_params(
client: &creator_keys::CreatorKeysContractClient<'_>,
creator: &Address,
buyer: &Address,
start_supply: u32,
n: u32,
) {
// 1. Advance supply to start_supply
let current_supply = client.get_total_key_supply(creator);
if current_supply < start_supply {
for _ in current_supply..start_supply {
let quote = client.get_buy_quote(creator);
client.buy_key(creator, buyer, &quote.total_amount, &None);
}
}

assert_eq!(client.get_total_key_supply(creator), start_supply);

// 2. Accumulate buy quotes for N keys and execute buys
let mut total_buy_price = 0;
let mut total_buy_creator_fee = 0;
let mut total_buy_protocol_fee = 0;

for _ in 0..n {
let quote = client.get_buy_quote(creator);
total_buy_price += quote.price;
total_buy_creator_fee += quote.creator_fee;
total_buy_protocol_fee += quote.protocol_fee;
client.buy_key(creator, buyer, &quote.total_amount, &None);
}

assert_eq!(client.get_total_key_supply(creator), start_supply + n);

// 3. Accumulate sell quotes for N keys and execute sells
let mut total_sell_price = 0;
let mut total_sell_creator_fee = 0;
let mut total_sell_protocol_fee = 0;

for _ in 0..n {
let quote = client.get_sell_quote(creator, buyer);
total_sell_price += quote.price;
total_sell_creator_fee += quote.creator_fee;
total_sell_protocol_fee += quote.protocol_fee;
client.sell_key(creator, buyer, &None);
}

assert_eq!(client.get_total_key_supply(creator), start_supply);

// 4. Assert symmetry
assert_eq!(
total_buy_price, total_sell_price,
"Price asymmetry at supply {} for N {}",
start_supply, n
);
assert_eq!(
total_buy_creator_fee, total_sell_creator_fee,
"Creator fee asymmetry at supply {} for N {}",
start_supply, n
);
assert_eq!(
total_buy_protocol_fee, total_sell_protocol_fee,
"Protocol fee asymmetry at supply {} for N {}",
start_supply, n
);
}

#[test]
fn test_quadratic_curve_symmetry() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
set_pricing_and_fees(&env, &client, KEY_PRICE, CREATOR_BPS, PROTOCOL_BPS);
set_curve_slope(&env, &client, QUADRATIC_SLOPE);

let creator = Address::generate(&env);
client.register_creator(
&creator,
&String::from_str(&env, "quadcreator"),
&None,
&None,
&Some(CurvePreset::Quadratic),
&None,
);

let buyer = Address::generate(&env);

// Cover supply levels: 0, 50, and 500
// Cover both small and large key amounts
assert_symmetry_for_params(&client, &creator, &buyer, 0, 1);
assert_symmetry_for_params(&client, &creator, &buyer, 0, 10);
assert_symmetry_for_params(&client, &creator, &buyer, 50, 5);
assert_symmetry_for_params(&client, &creator, &buyer, 500, 50);
}
Loading