diff --git a/contracts/royalty-splitter/src/contract.rs b/contracts/royalty-splitter/src/contract.rs index 42ee77d..643f9f6 100644 --- a/contracts/royalty-splitter/src/contract.rs +++ b/contracts/royalty-splitter/src/contract.rs @@ -4,9 +4,9 @@ use soroban_sdk::{ use crate::{ storage::{ - is_initialized, load_beneficiaries, load_shares, load_token, save_beneficiaries, - save_shares, save_token, set_initialized, LEDGER_TTL_BUMP, LEDGER_TTL_THRESHOLD, - MAX_BENEFICIARIES, + is_initialized, load_admin, load_beneficiaries, load_shares, load_token, + save_admin, save_beneficiaries, save_shares, save_token, set_initialized, + LEDGER_TTL_BUMP, LEDGER_TTL_THRESHOLD, MAX_BENEFICIARIES, }, types::SplitterError, }; @@ -16,9 +16,15 @@ pub struct RoyaltySplitter; #[contractimpl] impl RoyaltySplitter { - /// Lock in the token, beneficiaries, and BPS shares forever. + /// Lock in the admin, token, beneficiaries, and BPS shares forever. /// Shares must sum to exactly 10 000. Can only be called once. - pub fn initialize(env: Env, token: Address, beneficiaries: Vec
, shares: Vec) { + pub fn initialize( + env: Env, + admin: Address, + token: Address, + beneficiaries: Vec
, + shares: Vec, + ) { if is_initialized(&env) { panic_with_error!(&env, SplitterError::AlreadyInitialized); } @@ -40,6 +46,7 @@ impl RoyaltySplitter { panic_with_error!(&env, SplitterError::InvalidShares); } + save_admin(&env, &admin); save_token(&env, &token); save_beneficiaries(&env, &beneficiaries); save_shares(&env, &shares); @@ -88,6 +95,55 @@ impl RoyaltySplitter { } } + /// Update the royalty split configuration. Can only be called by the admin. + /// Shares must sum to exactly 10 000. + pub fn update_royalty_split( + env: Env, + admin: Address, + beneficiaries: Vec
, + shares: Vec, + ) { + // Require admin authentication + admin.require_auth(); + + if !is_initialized(&env) { + panic_with_error!(&env, SplitterError::NotInitialized); + } + + // Verify the provided admin matches the stored admin + let stored_admin = load_admin(&env); + if admin != stored_admin { + panic_with_error!(&env, SplitterError::Unauthorized); + } + + // Validate inputs + if beneficiaries.is_empty() { + panic_with_error!(&env, SplitterError::NoBeneficiaries); + } + if beneficiaries.len() > MAX_BENEFICIARIES { + panic_with_error!(&env, SplitterError::TooManyBeneficiaries); + } + if beneficiaries.len() != shares.len() { + panic_with_error!(&env, SplitterError::LengthMismatch); + } + + let mut total: u32 = 0; + for share in shares.iter() { + total += share; + } + if total != 10_000 { + panic_with_error!(&env, SplitterError::InvalidShares); + } + + // Update storage + save_beneficiaries(&env, &beneficiaries); + save_shares(&env, &shares); + + env.storage() + .instance() + .extend_ttl(LEDGER_TTL_THRESHOLD, LEDGER_TTL_BUMP); + } + pub fn get_token(env: Env) -> Address { load_token(&env) } diff --git a/contracts/royalty-splitter/src/storage.rs b/contracts/royalty-splitter/src/storage.rs index bff8492..66b1785 100644 --- a/contracts/royalty-splitter/src/storage.rs +++ b/contracts/royalty-splitter/src/storage.rs @@ -4,6 +4,9 @@ pub const MAX_BENEFICIARIES: u32 = 20; pub const LEDGER_TTL_BUMP: u32 = 432_000; pub const LEDGER_TTL_THRESHOLD: u32 = 144_000; +// Add these to the storage module +const ADMIN_KEY: &[u8] = b"admin"; + #[contracttype] pub enum DataKey { Initialized, @@ -73,3 +76,16 @@ pub fn load_shares(env: &Env) -> Vec { .get::>(&DataKey::Shares) .expect("shares not set") } + + +pub fn save_admin(env: &Env, admin: &Address) { + env.storage().instance().set(&ADMIN_KEY, admin); +} + +pub fn load_admin(env: &Env) -> Address { + env.storage().instance().get(&ADMIN_KEY).unwrap() +} + +pub fn has_admin(env: &Env) -> bool { + env.storage().instance().has(&ADMIN_KEY) +} diff --git a/contracts/royalty-splitter/src/test.rs b/contracts/royalty-splitter/src/test.rs index 7cc297e..1e9d93c 100644 --- a/contracts/royalty-splitter/src/test.rs +++ b/contracts/royalty-splitter/src/test.rs @@ -13,6 +13,7 @@ fn setup() -> ( RoyaltySplitterClient<'static>, Address, // token Address, // contract_id + Address, // admin ) { let env = Env::default(); env.mock_all_auths(); @@ -20,23 +21,25 @@ fn setup() -> ( let contract_id = env.register(RoyaltySplitter, ()); let client = RoyaltySplitterClient::new(&env, &contract_id); + let admin = Address::generate(&env); let token_admin = Address::generate(&env); let token = env .register_stellar_asset_contract_v2(token_admin) .address(); - (env, client, token, contract_id) + (env, client, token, contract_id, admin) } // ── initialize ──────────────────────────────────────────────── #[test] fn test_initialize_stores_config() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 6_000_u32, 4_000_u32], @@ -53,11 +56,12 @@ fn test_initialize_stores_config() { #[test] fn test_double_initialize_is_rejected() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 5_000_u32, 5_000_u32], @@ -65,6 +69,7 @@ fn test_double_initialize_is_rejected() { let err = client .try_initialize( + &admin, &token, &vec![&env, alice, bob], &vec![&env, 5_000_u32, 5_000_u32], @@ -77,12 +82,13 @@ fn test_double_initialize_is_rejected() { #[test] fn test_shares_not_summing_to_10000_is_rejected() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let err = client .try_initialize( + &admin, &token, &vec![&env, alice, bob], &vec![&env, 5_000_u32, 4_000_u32], // sums to 9000 @@ -95,11 +101,12 @@ fn test_shares_not_summing_to_10000_is_rejected() { #[test] fn test_length_mismatch_is_rejected() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let err = client .try_initialize( + &admin, &token, &vec![&env, alice], &vec![&env, 5_000_u32, 5_000_u32], @@ -112,10 +119,10 @@ fn test_length_mismatch_is_rejected() { #[test] fn test_empty_beneficiaries_is_rejected() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let err = client - .try_initialize(&token, &vec![&env], &vec![&env]) + .try_initialize(&admin, &token, &vec![&env], &vec![&env]) .unwrap_err() .unwrap(); @@ -126,12 +133,13 @@ fn test_empty_beneficiaries_is_rejected() { #[test] fn test_distribute_two_parties() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 6_000_u32, 4_000_u32], @@ -150,13 +158,14 @@ fn test_distribute_two_parties() { #[test] fn test_distribute_three_parties() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let a = Address::generate(&env); let b = Address::generate(&env); let c = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, a.clone(), b.clone(), c.clone()], &vec![&env, 3_334_u32, 3_333_u32, 3_333_u32], @@ -178,13 +187,14 @@ fn test_distribute_three_parties() { #[test] fn test_distribute_rounding_no_dust_trapped() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); // 3333 + 6667 = 10000; with balance=10 alice gets floor(3.333)=3, bob gets 6, caller gets 1 client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 3_333_u32, 6_667_u32], @@ -205,12 +215,13 @@ fn test_distribute_rounding_no_dust_trapped() { #[test] fn test_distribute_empty_balance_is_noop() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 5_000_u32, 5_000_u32], @@ -227,12 +238,13 @@ fn test_distribute_empty_balance_is_noop() { #[test] fn test_distribute_callable_by_anyone() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 7_000_u32, 3_000_u32], @@ -252,7 +264,7 @@ fn test_distribute_callable_by_anyone() { #[test] fn test_distribute_before_initialize_is_rejected() { - let (env, client, token, _) = setup(); + let (env, client, token, _, _) = setup(); let caller = Address::generate(&env); let err = client.try_distribute(&token, &caller).unwrap_err().unwrap(); @@ -261,11 +273,16 @@ fn test_distribute_before_initialize_is_rejected() { #[test] fn test_distribute_single_beneficiary_gets_all() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let caller = Address::generate(&env); - client.initialize(&token, &vec![&env, alice.clone()], &vec![&env, 10_000_u32]); + client.initialize( + &admin, + &token, + &vec![&env, alice.clone()], + &vec![&env, 10_000_u32], + ); let sac = StellarAssetClient::new(&env, &token); sac.mint(&contract_id, &5_000); @@ -279,12 +296,13 @@ fn test_distribute_single_beneficiary_gets_all() { #[test] fn test_distribute_can_be_called_multiple_times() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 5_000_u32, 5_000_u32], @@ -306,12 +324,13 @@ fn test_distribute_can_be_called_multiple_times() { #[test] fn test_distribute_dust_goes_to_caller() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let caller = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 5_000_u32, 5_000_u32], @@ -333,11 +352,12 @@ fn test_distribute_dust_goes_to_caller() { #[test] fn test_get_share_for_beneficiary() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice.clone(), bob.clone()], &vec![&env, 6_000_u32, 4_000_u32], @@ -349,12 +369,13 @@ fn test_get_share_for_beneficiary() { #[test] fn test_get_share_for_nonexistent_beneficiary() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); let bob = Address::generate(&env); let charlie = Address::generate(&env); client.initialize( + &admin, &token, &vec![&env, alice, bob], &vec![&env, 5_000_u32, 5_000_u32], @@ -367,7 +388,7 @@ fn test_get_share_for_nonexistent_beneficiary() { #[test] fn test_get_share_before_initialize() { - let (env, client, _token, _) = setup(); + let (env, client, _token, _, _) = setup(); let alice = Address::generate(&env); let err = client.try_get_share(&alice).unwrap_err().unwrap(); @@ -376,10 +397,15 @@ fn test_get_share_before_initialize() { #[test] fn test_get_share_single_beneficiary() { - let (env, client, token, _) = setup(); + let (env, client, token, _, admin) = setup(); let alice = Address::generate(&env); - client.initialize(&token, &vec![&env, alice.clone()], &vec![&env, 10_000_u32]); + client.initialize( + &admin, + &token, + &vec![&env, alice.clone()], + &vec![&env, 10_000_u32], + ); assert_eq!(client.get_share(&alice), 10_000_u32); } @@ -388,7 +414,7 @@ fn test_get_share_single_beneficiary() { #[test] fn test_distribute_royalties_exact_100_percent_success() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let recipient_a = Address::generate(&env); let recipient_b = Address::generate(&env); let recipient_c = Address::generate(&env); @@ -400,6 +426,7 @@ fn test_distribute_royalties_exact_100_percent_success() { // Recipient C -> 20% (2_000 BPS) // Total = 10_000 BPS (100%) client.initialize( + &admin, &token, &vec![ &env, @@ -453,7 +480,7 @@ fn test_distribute_royalties_exact_100_percent_success() { #[test] fn test_distribute_royalties_less_than_100_percent_fails() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let recipient_a = Address::generate(&env); let recipient_b = Address::generate(&env); let recipient_c = Address::generate(&env); @@ -463,6 +490,7 @@ fn test_distribute_royalties_less_than_100_percent_fails() { // 40% + 30% + 20% = 90% (9_000 BPS) let err = client .try_initialize( + &admin, &token, &vec![ &env, @@ -508,7 +536,7 @@ fn test_distribute_royalties_less_than_100_percent_fails() { #[test] fn test_distribute_royalties_greater_than_100_percent_fails() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let recipient_a = Address::generate(&env); let recipient_b = Address::generate(&env); let recipient_c = Address::generate(&env); @@ -518,6 +546,7 @@ fn test_distribute_royalties_greater_than_100_percent_fails() { // 50% + 40% + 20% = 110% (11_000 BPS) let err = client .try_initialize( + &admin, &token, &vec![ &env, @@ -563,7 +592,7 @@ fn test_distribute_royalties_greater_than_100_percent_fails() { #[test] fn test_distribute_royalties_max_recipients_100_percent_success() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let caller = Address::generate(&env); // Max beneficiaries supported is 20, total must equal 10_000 BPS (500 BPS each = 5% each) @@ -578,7 +607,7 @@ fn test_distribute_royalties_max_recipients_100_percent_success() { recipients.push(recipient); } - client.initialize(&token, &beneficiaries, &shares); + client.initialize(&admin, &token, &beneficiaries, &shares); let sac = StellarAssetClient::new(&env, &token); sac.mint(&contract_id, &20_000); @@ -602,7 +631,7 @@ fn test_distribute_royalties_max_recipients_100_percent_success() { #[test] fn test_distribute_royalties_smallest_valid_percentages_100_percent_success() { - let (env, client, token, contract_id) = setup(); + let (env, client, token, contract_id, admin) = setup(); let caller = Address::generate(&env); // Smallest BPS unit is 1 BPS (0.01%). @@ -623,7 +652,7 @@ fn test_distribute_royalties_smallest_valid_percentages_100_percent_success() { beneficiaries.push_back(large_recipient.clone()); shares.push_back(9_981_u32); - client.initialize(&token, &beneficiaries, &shares); + client.initialize(&admin, &token, &beneficiaries, &shares); let sac = StellarAssetClient::new(&env, &token); // Mint 10,000,000 tokens so 1 BPS = 1,000 tokens cleanly @@ -647,6 +676,272 @@ fn test_distribute_royalties_smallest_valid_percentages_100_percent_success() { assert_eq!(tc.balance(&contract_id), 0); } +// ── update_royalty_split ───────────────────────────────────────────── + +#[test] +fn test_update_royalty_split_by_admin() { + let (env, client, token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + let charlie = Address::generate(&env); + + // Initialize with initial config + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Update to new config + client.update_royalty_split( + &admin, + &vec![&env, alice.clone(), bob.clone(), charlie.clone()], + &vec![&env, 4_000_u32, 3_000_u32, 3_000_u32], + ); + + // Verify the update + let beneficiaries = client.get_beneficiaries(); + assert_eq!(beneficiaries.len(), 3); + assert_eq!(beneficiaries.get(0).unwrap(), alice); + assert_eq!(beneficiaries.get(1).unwrap(), bob); + assert_eq!(beneficiaries.get(2).unwrap(), charlie); + + let shares = client.get_shares(); + assert_eq!(shares.get(0).unwrap(), 4_000_u32); + assert_eq!(shares.get(1).unwrap(), 3_000_u32); + assert_eq!(shares.get(2).unwrap(), 3_000_u32); +} + +#[test] +fn test_update_royalty_split_by_non_admin_fails() { + let (env, client, token, _, admin) = setup(); + let non_admin = Address::generate(&env); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + + // Initialize with initial config + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Try to update as non-admin + let err = client + .try_update_royalty_split( + &non_admin, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 7_000_u32, 3_000_u32], + ) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::Unauthorized.into()); + + // Verify state didn't change + let shares = client.get_shares(); + assert_eq!(shares.get(0).unwrap(), 5_000_u32); + assert_eq!(shares.get(1).unwrap(), 5_000_u32); +} + +#[test] +fn test_update_royalty_split_before_initialize_fails() { + let (env, client, _token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + + // Try to update before initialization + let err = client + .try_update_royalty_split( + &admin, + &vec![&env, alice, bob], + &vec![&env, 5_000_u32, 5_000_u32], + ) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::NotInitialized.into()); +} + +#[test] +fn test_update_royalty_split_with_invalid_shares_fails() { + let (env, client, token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + let charlie = Address::generate(&env); + + // Initialize + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Try to update with shares that don't sum to 10,000 + let err = client + .try_update_royalty_split( + &admin, + &vec![&env, alice.clone(), bob.clone(), charlie.clone()], + &vec![&env, 4_000_u32, 3_000_u32, 2_000_u32], // Sums to 9,000 + ) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::InvalidShares.into()); + + // Verify state didn't change + let shares = client.get_shares(); + assert_eq!(shares.get(0).unwrap(), 5_000_u32); + assert_eq!(shares.get(1).unwrap(), 5_000_u32); +} + +#[test] +fn test_update_royalty_split_with_empty_beneficiaries_fails() { + let (env, client, token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + + // Initialize + client.initialize( + &admin, + &token, + &vec![&env, alice, bob], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Try to update with empty beneficiaries + let err = client + .try_update_royalty_split(&admin, &vec![&env], &vec![&env]) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::NoBeneficiaries.into()); +} + +#[test] +fn test_update_royalty_split_with_too_many_beneficiaries_fails() { + let (env, client, token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + + // Initialize + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Create 21 beneficiaries (MAX_BENEFICIARIES is 20) + let mut beneficiaries = vec![&env]; + let mut shares = vec![&env]; + for _ in 0..21 { + beneficiaries.push_back(Address::generate(&env)); + shares.push_back(1_u32); + } + + // Try to update with too many beneficiaries + let err = client + .try_update_royalty_split(&admin, &beneficiaries, &shares) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::TooManyBeneficiaries.into()); +} + +#[test] +fn test_update_royalty_split_length_mismatch_fails() { + let (env, client, token, _, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + + // Initialize + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // Try to update with mismatched lengths + let err = client + .try_update_royalty_split( + &admin, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 7_000_u32], // Only 1 share for 2 beneficiaries + ) + .unwrap_err() + .unwrap(); + + assert_eq!(err, SplitterError::LengthMismatch.into()); +} + +#[test] +fn test_update_royalty_split_multiple_times() { + let (env, client, token, contract_id, admin) = setup(); + let alice = Address::generate(&env); + let bob = Address::generate(&env); + let charlie = Address::generate(&env); + let dave = Address::generate(&env); + + // Initialize with 2 beneficiaries + client.initialize( + &admin, + &token, + &vec![&env, alice.clone(), bob.clone()], + &vec![&env, 5_000_u32, 5_000_u32], + ); + + // First update - 3 beneficiaries + client.update_royalty_split( + &admin, + &vec![&env, alice.clone(), bob.clone(), charlie.clone()], + &vec![&env, 4_000_u32, 3_000_u32, 3_000_u32], + ); + + let shares = client.get_shares(); + assert_eq!(shares.len(), 3); + assert_eq!(shares.get(0).unwrap(), 4_000_u32); + assert_eq!(shares.get(1).unwrap(), 3_000_u32); + assert_eq!(shares.get(2).unwrap(), 3_000_u32); + + // Second update - 4 beneficiaries + client.update_royalty_split( + &admin, + &vec![ + &env, + alice.clone(), + bob.clone(), + charlie.clone(), + dave.clone(), + ], + &vec![&env, 2_500_u32, 2_500_u32, 2_500_u32, 2_500_u32], + ); + + let shares = client.get_shares(); + assert_eq!(shares.len(), 4); + assert_eq!(shares.get(0).unwrap(), 2_500_u32); + assert_eq!(shares.get(1).unwrap(), 2_500_u32); + assert_eq!(shares.get(2).unwrap(), 2_500_u32); + assert_eq!(shares.get(3).unwrap(), 2_500_u32); + + // Verify distribution still works + let caller = Address::generate(&env); + let sac = StellarAssetClient::new(&env, &token); + // Use contract_id from setup instead of env.current_contract_address() + sac.mint(&contract_id, &1_000_000); + client.distribute(&token, &caller); + + let tc = TokenClient::new(&env, &token); + assert_eq!(tc.balance(&alice), 250_000); + assert_eq!(tc.balance(&bob), 250_000); + assert_eq!(tc.balance(&charlie), 250_000); + assert_eq!(tc.balance(&dave), 250_000); +} + // ── Invalid Recipient Address Regression Tests ───────────────────────────── #[test] @@ -660,7 +955,7 @@ fn test_distribute_royalties_invalid_strkey_recipient_fails() { #[test] #[should_panic] fn test_distribute_royalties_invalid_recipient_first_position_fails() { - let (env, client, token, _contract_id) = setup(); + let (env, client, token, _, admin) = setup(); let valid_b = Address::generate(&env); let valid_c = Address::generate(&env); @@ -668,6 +963,7 @@ fn test_distribute_royalties_invalid_recipient_first_position_fails() { let invalid_a = Address::from_string_bytes(&invalid_bytes); client.initialize( + &admin, &token, &vec![&env, invalid_a, valid_b, valid_c], &vec![&env, 5_000_u32, 3_000_u32, 2_000_u32], @@ -677,7 +973,7 @@ fn test_distribute_royalties_invalid_recipient_first_position_fails() { #[test] #[should_panic] fn test_distribute_royalties_invalid_recipient_middle_position_fails() { - let (env, client, token, _contract_id) = setup(); + let (env, client, token, _, admin) = setup(); let valid_a = Address::generate(&env); let valid_c = Address::generate(&env); @@ -685,6 +981,7 @@ fn test_distribute_royalties_invalid_recipient_middle_position_fails() { let invalid_b = Address::from_string_bytes(&invalid_bytes); client.initialize( + &admin, &token, &vec![&env, valid_a, invalid_b, valid_c], &vec![&env, 5_000_u32, 3_000_u32, 2_000_u32], @@ -694,7 +991,7 @@ fn test_distribute_royalties_invalid_recipient_middle_position_fails() { #[test] #[should_panic] fn test_distribute_royalties_invalid_recipient_final_position_fails() { - let (env, client, token, _contract_id) = setup(); + let (env, client, token, _, admin) = setup(); let valid_a = Address::generate(&env); let valid_b = Address::generate(&env); @@ -702,8 +999,9 @@ fn test_distribute_royalties_invalid_recipient_final_position_fails() { let invalid_c = Address::from_string_bytes(&invalid_bytes); client.initialize( + &admin, &token, &vec![&env, valid_a, valid_b, invalid_c], &vec![&env, 5_000_u32, 3_000_u32, 2_000_u32], ); -} +} \ No newline at end of file diff --git a/contracts/royalty-splitter/src/types.rs b/contracts/royalty-splitter/src/types.rs index 3c2656e..73125ee 100644 --- a/contracts/royalty-splitter/src/types.rs +++ b/contracts/royalty-splitter/src/types.rs @@ -12,4 +12,5 @@ pub enum SplitterError { InvalidShares = 5, TooManyBeneficiaries = 6, BeneficiaryNotFound = 7, + Unauthorized = 8, }