From c248261855336793c36603fd11c2874ca4428878 Mon Sep 17 00:00:00 2001 From: Queenode Date: Thu, 20 Aug 2026 23:31:03 +0100 Subject: [PATCH] fix(#526): Add multi-asset support to revenue_split contract --- contracts/revenue_split/src/lib.rs | 57 ++++++++++++++++------------- contracts/revenue_split/src/test.rs | 55 +++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/contracts/revenue_split/src/lib.rs b/contracts/revenue_split/src/lib.rs index 64a28760..c3158981 100644 --- a/contracts/revenue_split/src/lib.rs +++ b/contracts/revenue_split/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, Vec, token}; +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, Vec, token, Symbol}; use common::CommonError; #[cfg(test)] @@ -82,16 +82,12 @@ impl RevenueSplitContract { Ok(()) } - /// Distributes a specific token amount from a sender to the listed recipients based on their shares. - pub fn distribute(env: Env, token: Address, from: Address, amount: i128) -> Result<(), ContractError> { + /// Distributes multiple assets from a sender to the listed recipients based on their shares. + pub fn distribute(env: Env, from: Address, assets: Vec<(Address, i128)>) -> Result<(), ContractError> { if !env.storage().instance().has(&DataKey::Admin) { return Err(ContractError::NotInitialized); } - if amount <= 0 { - return Err(ContractError::InvalidAmount); - } - from.require_auth(); let shares: Vec = env @@ -100,27 +96,38 @@ impl RevenueSplitContract { .get(&DataKey::Recipients) .ok_or(ContractError::NotInitialized)?; - let client = token::Client::new(&env, &token); - - let mut amount_distributed = 0; - - for (i, share) in shares.iter().enumerate() { - // Calculate slice of the total amount using basis points - // Formula: amount * basis_points / 10000 - let recipient_amount = (amount as i128 * share.basis_points as i128) / TOTAL_BASIS_POINTS as i128; - - if recipient_amount > 0 { - // To avoid precision loss dust, the last recipient takes any minor remainders. - if i as u32 == shares.len() - 1 { - let final_amount = amount - amount_distributed; - if final_amount > 0 { - client.transfer(&from, &share.destination, &final_amount); + for asset_pair in assets.iter() { + let token = asset_pair.0; + let amount = asset_pair.1; + + if amount <= 0 { + return Err(ContractError::InvalidAmount); + } + + let client = token::Client::new(&env, &token); + + let mut amount_distributed = 0; + + for (i, share) in shares.iter().enumerate() { + // Calculate slice of the total amount using basis points + // Formula: amount * basis_points / 10000 + let recipient_amount = (amount as i128 * share.basis_points as i128) / TOTAL_BASIS_POINTS as i128; + + if recipient_amount > 0 { + // To avoid precision loss dust, the last recipient takes any minor remainders. + if i as u32 == shares.len() - 1 { + let final_amount = amount - amount_distributed; + if final_amount > 0 { + client.transfer(&from, &share.destination, &final_amount); + } + } else { + client.transfer(&from, &share.destination, &recipient_amount); + amount_distributed += recipient_amount; } - } else { - client.transfer(&from, &share.destination, &recipient_amount); - amount_distributed += recipient_amount; } } + + env.events().publish((Symbol::new(&env, "distribute"), token.clone()), amount); } Ok(()) diff --git a/contracts/revenue_split/src/test.rs b/contracts/revenue_split/src/test.rs index a4fed35a..46edc104 100644 --- a/contracts/revenue_split/src/test.rs +++ b/contracts/revenue_split/src/test.rs @@ -32,7 +32,8 @@ fn test_distribution_invalid_amount_returns_error() { let sender = Address::generate(&env); stellar_asset_client.mint(&sender, &1000); - assert!(contract_client.try_distribute(&token_id, &sender, &0).is_err()); + let assets = Vec::from_array(&env, [(token_id.clone(), 0i128)]); + assert!(contract_client.try_distribute(&sender, &assets).is_err()); assert_eq!(token_client.balance(&sender), 1000); } @@ -154,7 +155,8 @@ fn test_distribution() { stellar_asset_client.mint(&sender, &1000); // Distribute 1000 tokens - contract_client.distribute(&token_id, &sender, &1000); + let assets = Vec::from_array(&env, [(token_id.clone(), 1000i128)]); + contract_client.distribute(&sender, &assets); // Verify balances assert_eq!(token_client.balance(&sender), 0); @@ -163,6 +165,55 @@ fn test_distribution() { assert_eq!(token_client.balance(&recipient3), 200); } +#[test] +fn test_distribution_multiple_assets() { + let env = Env::default(); + env.mock_all_auths(); + + // Create tokens + let token_admin = Address::generate(&env); + let (token_id1, stellar_asset_client1, token_client1) = create_token_contract(&env, &token_admin); + let (token_id2, stellar_asset_client2, token_client2) = create_token_contract(&env, &token_admin); + + // Setup revenue split contract + let contract_id = env.register(RevenueSplitContract, ()); + let contract_client = RevenueSplitContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let recipient1 = Address::generate(&env); + let recipient2 = Address::generate(&env); + + // 60%, 40% + let shares = Vec::from_array(&env, [ + RecipientShare { destination: recipient1.clone(), basis_points: 6000 }, + RecipientShare { destination: recipient2.clone(), basis_points: 4000 }, + ]); + + contract_client.init(&admin, &shares); + + // Fund a sender + let sender = Address::generate(&env); + stellar_asset_client1.mint(&sender, &1000); + stellar_asset_client2.mint(&sender, &2000); + + // Distribute multiple assets + let assets = Vec::from_array(&env, [ + (token_id1.clone(), 1000i128), + (token_id2.clone(), 2000i128) + ]); + contract_client.distribute(&sender, &assets); + + // Verify balances token 1 + assert_eq!(token_client1.balance(&sender), 0); + assert_eq!(token_client1.balance(&recipient1), 600); + assert_eq!(token_client1.balance(&recipient2), 400); + + // Verify balances token 2 + assert_eq!(token_client2.balance(&sender), 0); + assert_eq!(token_client2.balance(&recipient1), 1200); + assert_eq!(token_client2.balance(&recipient2), 800); +} + #[test] fn test_update_recipients() { let env = Env::default();