Skip to content
Merged
3 changes: 3 additions & 0 deletions creator-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2444,6 +2444,7 @@ impl CreatorKeysContract {
protocol_bps: u32,
) -> Result<(), ContractError> {
admin.require_auth();
assert_is_admin(&env, &admin)?;
fee::assert_valid_fee_bps(creator_bps, protocol_bps)?;

let config = fee::FeeConfig {
Expand Down Expand Up @@ -2668,6 +2669,7 @@ impl CreatorKeysContract {
recipient: Address,
) -> Result<(), ContractError> {
admin.require_auth();
assert_is_admin(&env, &admin)?;
validate_non_zero_address(&env, &recipient)?;

let old_recipient: Option<Address> = env
Expand Down Expand Up @@ -4527,6 +4529,7 @@ mod tests {
let client = super::CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);

client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &9000, &1000);

let bps = env.as_contract(&contract_id, || super::read_protocol_fee_bps(&env));
Expand Down
2 changes: 2 additions & 0 deletions creator-keys/src/test_issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ mod issue_tests {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
client.set_key_price(&admin, &KEY_PRICE);
client.set_fee_config(&admin, &10_000, &0);

Expand Down Expand Up @@ -788,6 +789,7 @@ mod issue_tests {

let protocol_bps = 250u32; // 2.5%
let creator_bps = 500u32; // 5.0%
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &creator_bps, &protocol_bps);

let creator = register_creator(&env, &client, None);
Expand Down
78 changes: 68 additions & 10 deletions creator-keys/tests/admin_unauthorized.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Integration tests for admin-only functions reverting when called by a non-admin.
//!
//
//! Every function gated by `assert_is_admin` must reject a non-admin caller with
//! `ContractError::Unauthorized` and must not mutate any contract state.

Expand All @@ -11,18 +11,10 @@ use soroban_sdk::{testutils::Address as _, Address, Env};

// ── helpers ───────────────────────────────────────────────────────────────────

/// Register a known admin into contract storage and return it.
fn setup_admin(env: &Env, client: &CreatorKeysContractClient<'_>) -> Address {
let admin = Address::generate(env);
client.set_protocol_admin(&admin, &admin);
admin
}

/// Full setup: contract + pricing + fees + admin. Returns (client, admin).
fn full_setup(env: &Env) -> (CreatorKeysContractClient<'_>, Address) {
let (client, _) = register_creator_keys(env);
set_pricing_and_fees(env, &client, 100i128, 9000, 1000);
let admin = setup_admin(env, &client);
let admin = set_pricing_and_fees(env, &client, 100i128, 9000, 1000);
(client, admin)
}

Expand Down Expand Up @@ -131,6 +123,72 @@ fn test_update_protocol_fee_recipient_no_state_change_on_non_admin_call() {
);
}

// ── set_fee_config ──────────────────────────────────────────────────────

#[test]
fn test_set_fee_config_reverts_for_non_admin() {
let env = test_env_with_auths();
let (client, _admin) = full_setup(&env);

let non_admin = Address::generate(&env);
let result = client.try_set_fee_config(&non_admin, &8000u32, &2000u32);
assert_eq!(result, Err(Ok(ContractError::Unauthorized)));
}

#[test]
fn test_set_fee_config_no_state_change_on_non_admin_call() {
let env = test_env_with_auths();
let (client, _admin) = full_setup(&env);

let protocol_bps_before = client.get_protocol_fee_bps();

let non_admin = Address::generate(&env);
let result = client.try_set_fee_config(&non_admin, &8000u32, &2000u32);
assert_eq!(result, Err(Ok(ContractError::Unauthorized)));

let protocol_bps_after = client.get_protocol_fee_bps();
assert_eq!(
protocol_bps_before, protocol_bps_after,
"protocol fee bps must not change when non-admin set_fee_config call is rejected"
);
}

// ── set_protocol_fee_recipient ──────────────────────────────────────────

#[test]
fn test_set_protocol_fee_recipient_reverts_for_non_admin() {
let env = test_env_with_auths();
let (client, admin) = full_setup(&env);

let original_recipient = Address::generate(&env);
client.set_protocol_fee_recipient(&admin, &original_recipient);

let non_admin = Address::generate(&env);
let new_recipient = Address::generate(&env);
let result = client.try_set_protocol_fee_recipient(&non_admin, &new_recipient);
assert_eq!(result, Err(Ok(ContractError::Unauthorized)));
}

#[test]
fn test_set_protocol_fee_recipient_no_state_change_on_non_admin_call() {
let env = test_env_with_auths();
let (client, admin) = full_setup(&env);

let original_recipient = Address::generate(&env);
client.set_protocol_fee_recipient(&admin, &original_recipient);

let non_admin = Address::generate(&env);
let new_recipient = Address::generate(&env);
let _ = client.try_set_protocol_fee_recipient(&non_admin, &new_recipient);

let stored = client.get_protocol_fee_recipient();
assert_eq!(
stored,
Some(original_recipient),
"fee recipient must not change when non-admin set_protocol_fee_recipient call is rejected"
);
}

// ── withdraw_treasury ─────────────────────────────────────────────────────────

#[test]
Expand Down
4 changes: 1 addition & 3 deletions creator-keys/tests/batch_claim_dividend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn test_batch_claim_exceeds_limit_reverts() {
fn test_batch_claim_while_paused_fails() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
set_pricing_and_fees(
let admin = set_pricing_and_fees(
&env,
&client,
100,
Expand All @@ -153,8 +153,6 @@ fn test_batch_claim_while_paused_fails() {
let distributor = Address::generate(&env);
distribute_test_dividend(&client, &creator, &distributor, 10_000);

let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
client.pause(&admin);

let creators = Vec::from_array(&env, [creator.clone()]);
Expand Down
3 changes: 3 additions & 0 deletions creator-keys/tests/buy_fee_split_treasury_and_creator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ fn test_buy_splits_fees_correctly_between_treasury_and_creator() {

// Configure pricing and fee split: 500 bps (5%) protocol fee, 200 bps (2%) creator fee
client.set_key_price(&admin, &gross_cost);
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &CREATOR_BPS, &PROTOCOL_BPS);

let protocol_recipient = soroban_sdk::Address::generate(&env);
Expand Down Expand Up @@ -106,6 +107,7 @@ fn test_buy_fee_split_accumulates_across_multiple_buys() {
let gross_cost: i128 = 5 * STROOPS_PER_DISPLAY_UNIT; // 5 XLM = 50,000,000 stroops

client.set_key_price(&admin, &gross_cost);
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &CREATOR_BPS, &PROTOCOL_BPS);

let creator = register_test_creator(&env, &client, "bob");
Expand Down Expand Up @@ -145,6 +147,7 @@ fn test_buy_fee_split_no_stroop_rounding_error_at_odd_gross_cost() {
let gross_cost: i128 = 1_234_567; // Odd stroop amount to verify integer arithmetic precision

client.set_key_price(&admin, &gross_cost);
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &CREATOR_BPS, &PROTOCOL_BPS);

let creator = register_test_creator(&env, &client, "carol");
Expand Down
3 changes: 1 addition & 2 deletions creator-keys/tests/buy_quote_monotonicity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ fn test_buy_quote_updates_after_fee_config_mutation() {
let price = 1_000_i128;

// Set initial fee config: 90% creator, 10% protocol
set_pricing_and_fees(&env, &client, price, 9000, 1000);
let admin = set_pricing_and_fees(&env, &client, price, 9000, 1000);
let creator = register_test_creator(&env, &client, "alice");

// Get quote with initial fee config
Expand All @@ -380,7 +380,6 @@ fn test_buy_quote_updates_after_fee_config_mutation() {
assert_eq!(q_before.total_amount, price + 900 + 100);

// Update fee config: 50% creator, 50% protocol
let admin = Address::generate(&env);
client.set_fee_config(&admin, &5000u32, &5000u32);

// Get quote after fee config update
Expand Down
8 changes: 6 additions & 2 deletions creator-keys/tests/buy_quote_stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod contract_test_env;
use contract_test_env::{
register_creator_keys, register_test_creator, set_pricing_and_fees, test_env_with_auths,
};
use soroban_sdk::Vec;
use soroban_sdk::{testutils::Address as _, Address, Vec};

#[test]
fn test_buy_quote_is_stable_across_multiple_calls() {
Expand Down Expand Up @@ -51,6 +51,9 @@ fn test_buy_quote_stability_with_different_fee_configs() {
let (client, _) = register_creator_keys(&env);
let creator = register_test_creator(&env, &client, "alice");

let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);

// Test cases for different fee configurations
let configs = [
(1000, 9500, 500), // 5% protocol fee
Expand All @@ -59,7 +62,8 @@ fn test_buy_quote_stability_with_different_fee_configs() {
];

for (price, c_bps, p_bps) in configs {
set_pricing_and_fees(&env, &client, price, c_bps, p_bps);
client.set_key_price(&admin, &price);
client.set_fee_config(&admin, &c_bps, &p_bps);

let supply_before = client.get_creator_supply(&creator);

Expand Down
4 changes: 3 additions & 1 deletion creator-keys/tests/buy_quote_zero_supply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ fn test_buy_quote_zero_supply_various_prices() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);

let admin = set_pricing_and_fees(&env, &client, 1000, 9000, 1000);

let test_prices = [1, 10, 100, 500, 1000, 10000];

for (i, price) in test_prices.iter().enumerate() {
set_pricing_and_fees(&env, &client, *price, 9000, 1000);
client.set_key_price(&admin, price);
let creator = register_test_creator(&env, &client, &format!("creator{}", i));

let quote = client.get_buy_quote(&creator);
Expand Down
4 changes: 1 addition & 3 deletions creator-keys/tests/claim_dividend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ fn test_double_claim_fails_with_no_claimable() {
fn test_claim_dividend_while_paused_fails() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
set_pricing_and_fees(
let admin = set_pricing_and_fees(
&env,
&client,
100,
Expand All @@ -117,8 +117,6 @@ fn test_claim_dividend_while_paused_fails() {
let distributor = Address::generate(&env);
distribute_test_dividend(&client, &creator, &distributor, 10_000);

let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
client.pause(&admin);

let result = client.try_claim_dividend(&creator, &buyer);
Expand Down
4 changes: 1 addition & 3 deletions creator-keys/tests/claimable_dividend_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ fn test_get_claimable_dividend_accumulates_across_distributions() {
fn test_get_claimable_dividend_works_while_paused() {
let env = test_env_with_auths();
let (client, _) = register_creator_keys(&env);
set_pricing_and_fees(
let admin = set_pricing_and_fees(
&env,
&client,
100,
Expand All @@ -139,8 +139,6 @@ fn test_get_claimable_dividend_works_while_paused() {
let distributor = Address::generate(&env);
distribute_test_dividend(&client, &creator, &distributor, 10_000);

let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
client.pause(&admin);

// Read-only view must work even when protocol is paused.
Expand Down
3 changes: 3 additions & 0 deletions creator-keys/tests/contract_initialization_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn test_initialization_event_emitted_on_first_set_fee_config() {

let admin = Address::generate(&env);
let recipient = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
client.set_protocol_fee_recipient(&admin, &recipient);

let test_ledger = 10u32;
Expand Down Expand Up @@ -68,6 +69,8 @@ fn test_initialization_event_not_emitted_on_reinit() {

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

client.set_protocol_admin(&admin, &admin);

// First initialization
client.set_fee_config(&admin, &9000u32, &1000u32);

Expand Down
3 changes: 3 additions & 0 deletions creator-keys/tests/contract_test_env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ pub fn set_protocol_fee_bps(
protocol_bps: u32,
) -> Address {
let admin = Address::generate(env);
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &creator_bps, &protocol_bps);
admin
}
Expand All @@ -93,6 +94,7 @@ pub fn set_pricing_and_fees(
protocol_bps: u32,
) -> Address {
let admin = Address::generate(env);
client.set_protocol_admin(&admin, &admin);
client.set_key_price(&admin, &key_price);
client.set_fee_config(&admin, &creator_bps, &protocol_bps);
admin
Expand Down Expand Up @@ -139,6 +141,7 @@ pub fn register_test_creator_with_fee_config(
protocol_bps: u32,
) -> Address {
let admin = Address::generate(env);
client.set_protocol_admin(&admin, &admin);
client.set_fee_config(&admin, &creator_bps, &protocol_bps);
let creator = Address::generate(env);
client.register_creator(
Expand Down
3 changes: 3 additions & 0 deletions creator-keys/tests/creator_fee_bps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ fn test_get_creator_fee_bps_returns_configured_value() {
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = Address::generate(&env);

client.register_creator(
Expand Down Expand Up @@ -38,6 +39,7 @@ fn test_get_creator_fee_bps_is_read_only() {
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = Address::generate(&env);

client.register_creator(
Expand Down Expand Up @@ -68,6 +70,7 @@ fn test_get_creator_fee_bps_tracks_fee_config_updates() {
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = Address::generate(&env);

client.register_creator(
Expand Down
5 changes: 5 additions & 0 deletions creator-keys/tests/creator_fee_config_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ fn test_get_creator_fee_config_registered_with_fee_config() {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");

Expand Down Expand Up @@ -92,6 +93,7 @@ fn test_get_creator_fee_config_is_read_only() {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");

Expand Down Expand Up @@ -127,6 +129,7 @@ fn test_get_creator_fee_config_updates_after_fee_reconfiguration() {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");

Expand Down Expand Up @@ -163,6 +166,7 @@ fn test_get_creator_fee_config_multiple_creators_independent() {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator1 = soroban_sdk::Address::generate(&env);
let creator2 = soroban_sdk::Address::generate(&env);
let handle1 = String::from_str(&env, "creator_one");
Expand Down Expand Up @@ -212,6 +216,7 @@ fn test_get_creator_fee_config_unregistered_after_fee_config_set() {
let client = CreatorKeysContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let unregistered_creator = soroban_sdk::Address::generate(&env);

client.set_fee_config(&admin, &9000u32, &1000u32);
Expand Down
2 changes: 2 additions & 0 deletions creator-keys/tests/creator_treasury_share.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ fn test_get_creator_treasury_share_returns_configured_value() {
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = Address::generate(&env);

client.register_creator(
Expand Down Expand Up @@ -38,6 +39,7 @@ fn test_get_creator_treasury_share_is_read_only() {
let contract_id = env.register(CreatorKeysContract, ());
let client = CreatorKeysContractClient::new(&env, &contract_id);
let admin = Address::generate(&env);
client.set_protocol_admin(&admin, &admin);
let creator = Address::generate(&env);

client.register_creator(
Expand Down
Loading
Loading