From 441c57e162d387604435416ee014dc1a52c8cd0c Mon Sep 17 00:00:00 2001 From: davidugorji Date: Tue, 30 Jun 2026 09:30:05 +0100 Subject: [PATCH 1/2] feat(crowdfund): add affiliate system with comprehensive tests Closes #351 --- contracts/crowdfund/src/errors.rs | 8 + contracts/crowdfund/src/lib.rs | 207 +++++++++- contracts/crowdfund/src/tests/mod.rs | 3 + .../crowdfund/src/tests/test_feature_214.rs | 367 ++++++++++++++++++ 4 files changed, 584 insertions(+), 1 deletion(-) create mode 100644 contracts/crowdfund/src/tests/mod.rs create mode 100644 contracts/crowdfund/src/tests/test_feature_214.rs diff --git a/contracts/crowdfund/src/errors.rs b/contracts/crowdfund/src/errors.rs index 6a16655..c0d5d8b 100644 --- a/contracts/crowdfund/src/errors.rs +++ b/contracts/crowdfund/src/errors.rs @@ -52,4 +52,12 @@ pub enum CrowdfundError { InsufficientMatchingPool = 26, // Pledge comment exceeds the configured maximum length. CommentTooLong = 27, + // Commission must be > 0 and <= 10 000 bps (100 %). + InvalidCommissionBps = 28, + // Address is already a registered affiliate. + AffiliateAlreadyRegistered = 29, + // Address is not a registered affiliate. + AffiliateNotRegistered = 30, + // Affiliate has no accrued commission to claim. + NoCommissionOwed = 31, } diff --git a/contracts/crowdfund/src/lib.rs b/contracts/crowdfund/src/lib.rs index 4b938ff..4e64449 100644 --- a/contracts/crowdfund/src/lib.rs +++ b/contracts/crowdfund/src/lib.rs @@ -3,6 +3,8 @@ mod errors; #[cfg(test)] mod test; +#[cfg(test)] +mod tests; use errors::CrowdfundError; use soroban_sdk::{ @@ -10,11 +12,13 @@ use soroban_sdk::{ vec, Address, Env, String, Vec, }; +#[allow(dead_code)] #[contractclient(name = "InvoicePaymentClient")] trait InvoicePayment { fn pay_invoice(env: Env, payer: Address, invoice_id: u64); } +#[allow(dead_code)] #[contractclient(name = "MerchantAccountRefundClient")] trait MerchantAccountRefund { fn refund(env: Env, token: Address, amount: i128, to: Address); @@ -92,11 +96,30 @@ pub struct PledgeCommentAddedEvent { pub comment: String, } +#[contractevent] pub struct PledgeReceivedEvent { pub contributor: Address, pub amount: i128, } +#[contractevent] +pub struct AffiliateRegisteredEvent { + pub affiliate: Address, +} + +#[contractevent] +pub struct AffiliateAccruedEvent { + pub affiliate: Address, + pub contributor: Address, + pub commission: i128, +} + +#[contractevent] +pub struct AffiliateClaimedEvent { + pub affiliate: Address, + pub amount: i128, +} + #[contractevent] pub struct BatchRefundProcessedEvent { pub total_refunded: i128, @@ -149,6 +172,12 @@ enum DataKey { MatchingPool, // Public comment attached to a contributor pledge. PledgeComment(Address), + // Commission rate (bps) paid to affiliates on referred pledges. + AffiliateCommissionBps, + // Whether an address is a registered affiliate. + Affiliate(Address), + // Unclaimed commission balance owed to an affiliate. + AffiliateBalance(Address), } #[contract] @@ -357,6 +386,182 @@ impl CrowdfundContract { env.storage().persistent().get(&DataKey::MatchingPool).unwrap_or(0) } + // ── Affiliate system (#351) ─────────────────────────────────────────────── + + /// Set the commission rate (bps, 1 bp = 0.01%) paid to affiliates on + /// pledges they refer. Organizer-only; callable any time, including + /// updates to a previously set rate. + pub fn set_affiliate_commission_bps(env: Env, organizer: Address, bps: u32) { + let stored_organizer: Address = env + .storage() + .persistent() + .get(&DataKey::Organizer) + .unwrap_or_else(|| panic_with_error!(&env, CrowdfundError::NotInitialized)); + if organizer != stored_organizer { + panic_with_error!(&env, CrowdfundError::NotInitialized); + } + organizer.require_auth(); + + if bps == 0 || bps > 10_000 { + panic_with_error!(&env, CrowdfundError::InvalidCommissionBps); + } + env.storage() + .persistent() + .set(&DataKey::AffiliateCommissionBps, &bps); + } + + /// Register `affiliate` as eligible to refer pledges and earn commission. + /// Organizer-only. + pub fn register_affiliate(env: Env, organizer: Address, affiliate: Address) { + let stored_organizer: Address = env + .storage() + .persistent() + .get(&DataKey::Organizer) + .unwrap_or_else(|| panic_with_error!(&env, CrowdfundError::NotInitialized)); + if organizer != stored_organizer { + panic_with_error!(&env, CrowdfundError::NotInitialized); + } + organizer.require_auth(); + + if env + .storage() + .persistent() + .get(&DataKey::Affiliate(affiliate.clone())) + .unwrap_or(false) + { + panic_with_error!(&env, CrowdfundError::AffiliateAlreadyRegistered); + } + + env.storage() + .persistent() + .set(&DataKey::Affiliate(affiliate.clone()), &true); + AffiliateRegisteredEvent { affiliate }.publish(&env); + } + + /// Returns `true` if `affiliate` is a registered affiliate for this campaign. + pub fn is_affiliate(env: Env, affiliate: Address) -> bool { + env.storage() + .persistent() + .get(&DataKey::Affiliate(affiliate)) + .unwrap_or(false) + } + + /// Returns the unclaimed commission balance owed to `affiliate`. + pub fn affiliate_balance(env: Env, affiliate: Address) -> i128 { + env.storage() + .persistent() + .get(&DataKey::AffiliateBalance(affiliate)) + .unwrap_or(0) + } + + /// Contribute `amount` tokens to the campaign via a registered `affiliate`, + /// accruing them a commission. Requires the caller to have pre-approved the + /// contract to spend at least `amount`. Panics after the deadline, if not + /// yet initialised, or if `affiliate` is not registered. + pub fn contribute_with_affiliate( + env: Env, + contributor: Address, + amount: i128, + affiliate: Address, + ) { + contributor.require_auth(); + + if amount <= 0 { + panic_with_error!(&env, CrowdfundError::InvalidAmount); + } + + let deadline: u64 = env + .storage() + .persistent() + .get(&DataKey::Deadline) + .unwrap_or_else(|| panic_with_error!(&env, CrowdfundError::NotInitialized)); + if env.ledger().timestamp() > deadline { + panic_with_error!(&env, CrowdfundError::CampaignEnded); + } + + if !env + .storage() + .persistent() + .get(&DataKey::Affiliate(affiliate.clone())) + .unwrap_or(false) + { + panic_with_error!(&env, CrowdfundError::AffiliateNotRegistered); + } + + let token_addr: Address = env + .storage() + .persistent() + .get(&DataKey::Token) + .unwrap_or_else(|| panic_with_error!(&env, CrowdfundError::NotInitialized)); + + let contract_addr = env.current_contract_address(); + token::TokenClient::new(&env, &token_addr) + .transfer(&contributor, &contract_addr, &amount); + + let new_raised = Self::apply_pledge_with_matching(&env, contributor.clone(), amount); + Self::track_contributor(&env, contributor.clone()); + Self::check_stretch_goals(&env, new_raised); + + let commission_bps: u32 = env + .storage() + .persistent() + .get(&DataKey::AffiliateCommissionBps) + .unwrap_or(0); + if commission_bps > 0 { + let commission = amount * (commission_bps as i128) / 10_000; + if commission > 0 { + let current: i128 = env + .storage() + .persistent() + .get(&DataKey::AffiliateBalance(affiliate.clone())) + .unwrap_or(0); + env.storage().persistent().set( + &DataKey::AffiliateBalance(affiliate.clone()), + ¤t.saturating_add(commission), + ); + AffiliateAccruedEvent { + affiliate, + contributor, + commission, + } + .publish(&env); + } + } + } + + /// Claim all accrued commission for the calling affiliate. + pub fn claim_affiliate_commission(env: Env, affiliate: Address) { + affiliate.require_auth(); + + let balance: i128 = env + .storage() + .persistent() + .get(&DataKey::AffiliateBalance(affiliate.clone())) + .unwrap_or(0); + if balance <= 0 { + panic_with_error!(&env, CrowdfundError::NoCommissionOwed); + } + + env.storage() + .persistent() + .set(&DataKey::AffiliateBalance(affiliate.clone()), &0_i128); + + let token_addr: Address = env + .storage() + .persistent() + .get(&DataKey::Token) + .unwrap_or_else(|| panic_with_error!(&env, CrowdfundError::NotInitialized)); + let contract_addr = env.current_contract_address(); + token::TokenClient::new(&env, &token_addr) + .transfer(&contract_addr, &affiliate, &balance); + + AffiliateClaimedEvent { + affiliate, + amount: balance, + } + .publish(&env); + } + /// Withdraw funds to the organizer after deadline if goal was met (#303). pub fn execute_campaign(env: Env) { let organizer: Address = env @@ -663,7 +868,7 @@ impl CrowdfundContract { } sum = sum.saturating_add(p); } - if sum != 10_000 || percentages.len() == 0 { + if sum != 10_000 || percentages.is_empty() { panic_with_error!(&env, CrowdfundError::InvalidMilestonePercentages); } diff --git a/contracts/crowdfund/src/tests/mod.rs b/contracts/crowdfund/src/tests/mod.rs new file mode 100644 index 0000000..b9e63f9 --- /dev/null +++ b/contracts/crowdfund/src/tests/mod.rs @@ -0,0 +1,3 @@ +#![cfg(test)] + +pub mod test_feature_214; diff --git a/contracts/crowdfund/src/tests/test_feature_214.rs b/contracts/crowdfund/src/tests/test_feature_214.rs new file mode 100644 index 0000000..ba16e85 --- /dev/null +++ b/contracts/crowdfund/src/tests/test_feature_214.rs @@ -0,0 +1,367 @@ +#![cfg(test)] + +extern crate std; + +use crate::{CrowdfundContract, CrowdfundContractClient}; +use soroban_sdk::testutils::{Address as _, Events, Ledger as _}; +use soroban_sdk::token::StellarAssetClient; +use soroban_sdk::{Address, Env, Map, Symbol, TryIntoVal, Val}; + +fn setup() -> (Env, Address, CrowdfundContractClient<'static>, Address, Address, Address) { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().with_mut(|l| l.timestamp = 1_000_000); + + let contract = env.register(CrowdfundContract, ()); + let client = CrowdfundContractClient::new(&env, &contract); + + let token_admin = Address::generate(&env); + let token = env + .register_stellar_asset_contract_v2(token_admin) + .address(); + let organizer = Address::generate(&env); + let contributor = Address::generate(&env); + + let goal = 10_000_i128; + let deadline = env.ledger().timestamp() + 86_400; + client.init_campaign(&organizer, &token, &goal, &deadline); + + (env, contract, client, token, organizer, contributor) +} + +// ── set_affiliate_commission_bps ────────────────────────────────────────────── + +#[test] +fn test_set_affiliate_commission_bps_succeeds() { + let (_env, _contract, client, _token, organizer, _contributor) = setup(); + client.set_affiliate_commission_bps(&organizer, &500); +} + +#[test] +#[should_panic] +fn test_set_affiliate_commission_bps_zero_panics() { + let (_env, _contract, client, _token, organizer, _contributor) = setup(); + client.set_affiliate_commission_bps(&organizer, &0); +} + +#[test] +#[should_panic] +fn test_set_affiliate_commission_bps_over_max_panics() { + let (_env, _contract, client, _token, organizer, _contributor) = setup(); + client.set_affiliate_commission_bps(&organizer, &10_001); +} + +#[test] +fn test_set_affiliate_commission_bps_at_max_boundary_succeeds() { + let (_env, _contract, client, _token, organizer, _contributor) = setup(); + client.set_affiliate_commission_bps(&organizer, &10_000); +} + +#[test] +#[should_panic] +fn test_non_organizer_cannot_set_commission_panics() { + let (env, _contract, client, _token, _organizer, _contributor) = setup(); + let impostor = Address::generate(&env); + client.set_affiliate_commission_bps(&impostor, &500); +} + +// ── register_affiliate ──────────────────────────────────────────────────────── + +#[test] +fn test_register_affiliate_emits_event() { + let (env, contract, client, _token, organizer, _contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + assert!(client.is_affiliate(&affiliate)); + + let events = env.events().all(); + let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap(); + assert_eq!(event_contract_id, contract); + let data_map: Map = data.try_into_val(&env).unwrap(); + let affiliate_in_event: Address = data_map + .get(Symbol::new(&env, "affiliate")) + .unwrap() + .try_into_val(&env) + .unwrap(); + assert_eq!(affiliate_in_event, affiliate); +} + +#[test] +#[should_panic] +fn test_register_duplicate_affiliate_panics() { + let (env, _contract, client, _token, organizer, _contributor) = setup(); + let affiliate = Address::generate(&env); + client.register_affiliate(&organizer, &affiliate); + client.register_affiliate(&organizer, &affiliate); +} + +#[test] +#[should_panic] +fn test_non_organizer_cannot_register_affiliate_panics() { + let (env, _contract, client, _token, _organizer, _contributor) = setup(); + let impostor = Address::generate(&env); + let affiliate = Address::generate(&env); + client.register_affiliate(&impostor, &affiliate); +} + +#[test] +fn test_malicious_address_cannot_register_affiliate_storage_rolls_back() { + let (env, _contract, client, _token, _organizer, _contributor) = setup(); + let impostor = Address::generate(&env); + let affiliate = Address::generate(&env); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.register_affiliate(&impostor, &affiliate); + })); + assert!(result.is_err()); + assert!(!client.is_affiliate(&affiliate)); +} + +#[test] +fn test_unregistered_address_is_not_affiliate() { + let (env, _contract, client, _token, _organizer, _contributor) = setup(); + let random = Address::generate(&env); + assert!(!client.is_affiliate(&random)); +} + +// ── contribute_with_affiliate: happy path + event verification ─────────────── + +#[test] +fn test_contribute_with_affiliate_accrues_exact_commission_and_emits_event() { + let (env, contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &500); // 5% + + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + + assert_eq!(client.affiliate_balance(&affiliate), 50); + assert_eq!(client.pledge_of(&contributor), 1_000); + assert_eq!(client.raised(), 1_000); + + let events = env.events().all(); + let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap(); + assert_eq!(event_contract_id, contract); + let data_map: Map = data.try_into_val(&env).unwrap(); + let affiliate_in_event: Address = data_map + .get(Symbol::new(&env, "affiliate")) + .unwrap() + .try_into_val(&env) + .unwrap(); + let contributor_in_event: Address = data_map + .get(Symbol::new(&env, "contributor")) + .unwrap() + .try_into_val(&env) + .unwrap(); + let commission_in_event: i128 = data_map + .get(Symbol::new(&env, "commission")) + .unwrap() + .try_into_val(&env) + .unwrap(); + assert_eq!(affiliate_in_event, affiliate); + assert_eq!(contributor_in_event, contributor); + assert_eq!(commission_in_event, 50); +} + +#[test] +fn test_contribute_with_affiliate_no_commission_when_rate_unset() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + client.register_affiliate(&organizer, &affiliate); + + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + + assert_eq!(client.affiliate_balance(&affiliate), 0); + assert_eq!(client.raised(), 1_000); +} + +#[test] +#[should_panic] +fn test_contribute_with_unregistered_affiliate_panics() { + let (env, _contract, client, token, _organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); +} + +#[test] +fn test_contribute_with_unregistered_affiliate_storage_rolls_back() { + let (env, _contract, client, token, _organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + })); + assert!(result.is_err()); + assert_eq!(client.pledge_of(&contributor), 0); + assert_eq!(client.raised(), 0); +} + +#[test] +#[should_panic] +fn test_contribute_with_affiliate_zero_amount_panics() { + let (env, _contract, client, _token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + client.register_affiliate(&organizer, &affiliate); + client.contribute_with_affiliate(&contributor, &0, &affiliate); +} + +#[test] +#[should_panic] +fn test_contribute_with_affiliate_after_deadline_panics() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + client.register_affiliate(&organizer, &affiliate); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + + env.ledger().with_mut(|l| l.timestamp += 100_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); +} + +#[test] +fn test_contribute_with_affiliate_accrues_across_multiple_contributors() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let contributor2 = Address::generate(&env); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &1_000); // 10% + + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + StellarAssetClient::new(&env, &token).mint(&contributor2, &2_000); + + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + client.contribute_with_affiliate(&contributor2, &2_000, &affiliate); + + assert_eq!(client.affiliate_balance(&affiliate), 300); + assert_eq!(client.raised(), 3_000); +} + +#[test] +fn test_contribute_with_affiliate_zero_commission_below_rounding_threshold() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &1); // 0.01% + + StellarAssetClient::new(&env, &token).mint(&contributor, &10); + client.contribute_with_affiliate(&contributor, &10, &affiliate); + + // 10 * 1 / 10_000 == 0 (integer division rounds down); no commission accrues. + assert_eq!(client.affiliate_balance(&affiliate), 0); + assert_eq!(client.raised(), 10); +} + +// ── claim_affiliate_commission ──────────────────────────────────────────────── + +#[test] +fn test_claim_affiliate_commission_transfers_and_zeroes_balance() { + let (env, contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &500); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + + client.claim_affiliate_commission(&affiliate); + + assert_eq!(client.affiliate_balance(&affiliate), 0); + let token_client = soroban_sdk::token::TokenClient::new(&env, &token); + assert_eq!(token_client.balance(&affiliate), 50); + assert_eq!(token_client.balance(&contract), 950); +} + +#[test] +fn test_claim_affiliate_commission_emits_exact_event_arguments() { + let (env, contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &500); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + + client.claim_affiliate_commission(&affiliate); + + let events = env.events().all(); + let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap(); + assert_eq!(event_contract_id, contract); + let data_map: Map = data.try_into_val(&env).unwrap(); + let affiliate_in_event: Address = data_map + .get(Symbol::new(&env, "affiliate")) + .unwrap() + .try_into_val(&env) + .unwrap(); + let amount_in_event: i128 = data_map + .get(Symbol::new(&env, "amount")) + .unwrap() + .try_into_val(&env) + .unwrap(); + assert_eq!(affiliate_in_event, affiliate); + assert_eq!(amount_in_event, 50); +} + +#[test] +#[should_panic] +fn test_claim_with_no_commission_owed_panics() { + let (env, _contract, client, _token, _organizer, _contributor) = setup(); + let affiliate = Address::generate(&env); + client.claim_affiliate_commission(&affiliate); +} + +#[test] +#[should_panic] +fn test_claim_twice_panics_second_time() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &500); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + + client.claim_affiliate_commission(&affiliate); + client.claim_affiliate_commission(&affiliate); +} + +#[test] +fn test_claim_twice_storage_rolls_back_on_second_attempt() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate); + client.set_affiliate_commission_bps(&organizer, &500); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + client.contribute_with_affiliate(&contributor, &1_000, &affiliate); + client.claim_affiliate_commission(&affiliate); + + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + client.claim_affiliate_commission(&affiliate); + })); + assert!(result.is_err()); + assert_eq!(client.affiliate_balance(&affiliate), 0); +} + +#[test] +fn test_affiliate_balance_isolated_per_affiliate() { + let (env, _contract, client, token, organizer, contributor) = setup(); + let affiliate_a = Address::generate(&env); + let affiliate_b = Address::generate(&env); + + client.register_affiliate(&organizer, &affiliate_a); + client.register_affiliate(&organizer, &affiliate_b); + client.set_affiliate_commission_bps(&organizer, &500); + StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); + + client.contribute_with_affiliate(&contributor, &1_000, &affiliate_a); + + assert_eq!(client.affiliate_balance(&affiliate_a), 50); + assert_eq!(client.affiliate_balance(&affiliate_b), 0); +} From f6b652c99382e960a3cf57eb164fe766a58bb9d4 Mon Sep 17 00:00:00 2001 From: davidugorji Date: Tue, 30 Jun 2026 09:33:29 +0100 Subject: [PATCH 2/2] fix(crowdfund): capture events before subsequent reads in affiliate tests Soroban testutils events().all() is scoped to the most recent contract invocation; an extra read call between the event-emitting call and the assertion cleared the events buffer, causing events.len()-1 to overflow. Closes #351 --- contracts/crowdfund/src/tests/test_feature_214.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/crowdfund/src/tests/test_feature_214.rs b/contracts/crowdfund/src/tests/test_feature_214.rs index ba16e85..597cd92 100644 --- a/contracts/crowdfund/src/tests/test_feature_214.rs +++ b/contracts/crowdfund/src/tests/test_feature_214.rs @@ -73,7 +73,6 @@ fn test_register_affiliate_emits_event() { let affiliate = Address::generate(&env); client.register_affiliate(&organizer, &affiliate); - assert!(client.is_affiliate(&affiliate)); let events = env.events().all(); let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap(); @@ -85,6 +84,8 @@ fn test_register_affiliate_emits_event() { .try_into_val(&env) .unwrap(); assert_eq!(affiliate_in_event, affiliate); + + assert!(client.is_affiliate(&affiliate)); } #[test] @@ -138,10 +139,6 @@ fn test_contribute_with_affiliate_accrues_exact_commission_and_emits_event() { StellarAssetClient::new(&env, &token).mint(&contributor, &1_000); client.contribute_with_affiliate(&contributor, &1_000, &affiliate); - assert_eq!(client.affiliate_balance(&affiliate), 50); - assert_eq!(client.pledge_of(&contributor), 1_000); - assert_eq!(client.raised(), 1_000); - let events = env.events().all(); let (event_contract_id, _topics, data) = events.get(events.len() - 1).unwrap(); assert_eq!(event_contract_id, contract); @@ -164,6 +161,10 @@ fn test_contribute_with_affiliate_accrues_exact_commission_and_emits_event() { assert_eq!(affiliate_in_event, affiliate); assert_eq!(contributor_in_event, contributor); assert_eq!(commission_in_event, 50); + + assert_eq!(client.affiliate_balance(&affiliate), 50); + assert_eq!(client.pledge_of(&contributor), 1_000); + assert_eq!(client.raised(), 1_000); } #[test]