From 018fbd9d410eac77e3b5ecd1009ba1b047f7faef Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Mon, 2 Jun 2025 08:19:57 +0100 Subject: [PATCH 1/9] added get campaign progress function and get campaign donor count function --- src/campaign_donation.cairo | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/campaign_donation.cairo b/src/campaign_donation.cairo index 6f9c2ba..5980dc2 100644 --- a/src/campaign_donation.cairo +++ b/src/campaign_donation.cairo @@ -47,6 +47,8 @@ pub mod CampaignDonation { campaign_closed: Map, // Map campaign ids to closing boolean campaign_withdrawn: Map, //Map campaign ids to whether they have been withdrawn donation_token: ContractAddress, + unique_donors_count: Map, // Number of unique donors per campaign + campaign_donors: Map<(u256, ContractAddress), bool>, // Track if an address has donated to a campaign } @@ -182,6 +184,15 @@ pub mod CampaignDonation { campaign.is_closed = true; } + // Check if this is the donor's first donation to this campaign + if !self.campaign_donors.read((campaign_id, donor)) { + // Mark that this address has donated to this campaign + self.campaign_donors.write((campaign_id, donor), true); + // Increment the unique donors count + let current_count = self.unique_donors_count.read(campaign_id); + self.unique_donors_count.write(campaign_id, current_count + 1); + } + self.campaigns.write(campaign_id, campaign); // Create donation record @@ -296,6 +307,33 @@ pub mod CampaignDonation { let campaign: Campaigns = self.campaigns.read(campaign_id); campaign } + fn get_campaign_progress(self: @ContractState, campaign_id: u256) -> u8 { + + let campaign: Campaigns = self.campaigns.read(campaign_id); + if campaign.target_amount == 0 { + return 0_u8; + } + + let progress = (campaign.current_balance * 100) / campaign.target_amount; + + // // Cap at 100% for overfunded campaigns + if progress > 100_u256 { + return 100_u8; + } + + // Convert the calculated progress to u8 using try_into(). + // This is a fallible conversion. unwrap() will panic if the value > 255. + let progress_u8: u8 = progress.try_into().unwrap(); + + // Return the u8 value. + progress_u8 + } + + + fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { + // Simply return the stored count of unique donors + self.unique_donors_count.read(campaign_id) + } } #[generate_trait] From de9cd5d34f6d46c79aca4d12c899f1b4616a7e07 Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Mon, 2 Jun 2025 08:20:33 +0100 Subject: [PATCH 2/9] modified the icampaigndonation interface --- src/interfaces/ICampaignDonation.cairo | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/interfaces/ICampaignDonation.cairo b/src/interfaces/ICampaignDonation.cairo index 0a747f1..711960e 100644 --- a/src/interfaces/ICampaignDonation.cairo +++ b/src/interfaces/ICampaignDonation.cairo @@ -103,7 +103,8 @@ pub trait ICampaignDonation { /// # Returns /// * `Array` - An array of all donations made to the campaign fn get_campaign_donations(self: @TContractState, campaign_id: u256) -> Array; - // ************************************************************************* + +// ************************************************************************* // USER EXPERIENCE ENHANCEMENTS // ************************************************************************* @@ -214,16 +215,18 @@ pub trait ICampaignDonation { /// /// # Returns /// * `u8` - Progress percentage (0-100) -// fn get_campaign_progress(self: @TContractState, campaign_id: u256) -> u8; - /// Gets the number of unique donors for a campaign +fn get_campaign_progress(self: @TContractState, campaign_id: u256) -> u8; + +/// Gets the number of unique donors for a campaign /// /// # Arguments /// * `campaign_id` - The campaign ID /// /// # Returns /// * `u32` - Number of unique donors -// fn get_campaign_donor_count(self: @TContractState, campaign_id: u256) -> u32; + +fn get_campaign_donor_count(self: @TContractState, campaign_id: u256) -> u32; /// Gets campaigns close to reaching their goal /// From b611ec50c1cbfc52a4f04b4517dbcf6fd57e5944 Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Mon, 2 Jun 2025 08:21:16 +0100 Subject: [PATCH 3/9] added tests for get campaign progress and get donor count functions --- .tool-versions | 2 +- tests/test_campaign_donation.cairo | 239 +++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 29753a3..9f71edc 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.11.2 +scarb 2.11.4 starknet-foundry 0.40.0 diff --git a/tests/test_campaign_donation.cairo b/tests/test_campaign_donation.cairo index 6428cd5..08a7615 100644 --- a/tests/test_campaign_donation.cairo +++ b/tests/test_campaign_donation.cairo @@ -511,3 +511,242 @@ fn test_withdraw_funds_from_campaign_successful() { assert(owner_balance_after - owner_balance_before == 800, 'Withdrawal error') } +#[test] +fn test_get_campaign_progress() { + let (token_address, sender, campaign_donation, _erc721) = setup(); + let target_amount = 1000_u256; + + // Create multiple campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); + let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); + let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approvals + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Make donations to campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + let _donation_id_1 = campaign_donation.donate_to_campaign(campaign_id_1, 100); // 10% + let _donation_id_2 = campaign_donation.donate_to_campaign(campaign_id_1, 200); // +20% = 30% + let _donation_id_3 = campaign_donation.donate_to_campaign(campaign_id_2, 500); // 50% + let _donation_id_4 = campaign_donation.donate_to_campaign(campaign_id_3, 1000); // 100% + stop_cheat_caller_address(campaign_donation.contract_address); + + // Test cases + // Partially funded: 300/1000 = 30% + let progress_1 = campaign_donation.get_campaign_progress(campaign_id_1); + assert(progress_1 == 30, 'partially funded'); + + // Partially funded: 500/1000 = 50% + let progress_2 = campaign_donation.get_campaign_progress(campaign_id_2); + assert(progress_2 == 50, 'partially funded'); + + // Fully/overfunded: 1000/1000 = 100% + let progress_3 = campaign_donation.get_campaign_progress(campaign_id_3); + assert(progress_3 == 100, 'fully/overfunded'); +} + +#[test] +fn test_campaign_progress_precision() { + let (token_address, sender, campaign_donation, _erc721) = setup(); + let target_amount = 1000_u256; + + // Create test campaign + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approval + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Test various precise percentages + start_cheat_caller_address(campaign_donation.contract_address, sender); + + // Test 51% + campaign_donation.donate_to_campaign(campaign_id, 512); + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 51, 'incorrect 51%'); + + // Test 79% + campaign_donation.donate_to_campaign(campaign_id, 284); // 512 + 284 = 796 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 79, 'incorrect 79%'); + + // Test 83% + campaign_donation.donate_to_campaign(campaign_id, 40); // 796 + 40 = 836 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 83, 'incorrect 83%'); + + // Test 93% + campaign_donation.donate_to_campaign(campaign_id, 94); // 836 + 94 = 930 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 93, 'incorrect 93%'); + + // Test 99% + campaign_donation.donate_to_campaign(campaign_id, 60); // 930 + 60 = 990 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 99, 'incorrect 99%'); + + // Test edge case: 99.9% (should round down to 99%) + campaign_donation.donate_to_campaign(campaign_id, 9); // 990 + 9 = 999 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 99, 'incorrect 99.9%'); + + stop_cheat_caller_address(campaign_donation.contract_address); +} + +#[test] +fn test_unique_donor_count() { + let (token_address, sender, campaign_donation, _erc721) = setup(); + let target_amount = 1000_u256; + let another_user: ContractAddress = contract_address_const::<'another_user'>(); + let third_user: ContractAddress = contract_address_const::<'third_user'>(); + + // Create a campaign + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id = campaign_donation.create_campaign('DonorTest', target_amount); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approvals and balances for all users + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + token_dispatcher.transfer(another_user, 10000); + token_dispatcher.transfer(third_user, 10000); + stop_cheat_caller_address(token_address); + + start_cheat_caller_address(token_address, another_user); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + start_cheat_caller_address(token_address, third_user); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Test initial state + let initial_count = campaign_donation.get_campaign_donor_count(campaign_id); + assert(initial_count == 0, 'Initial count should be 0'); + + // First donation from sender + start_cheat_caller_address(campaign_donation.contract_address, sender); + campaign_donation.donate_to_campaign(campaign_id, 100); + stop_cheat_caller_address(campaign_donation.contract_address); + + let count_after_first = campaign_donation.get_campaign_donor_count(campaign_id); + assert(count_after_first == 1, 'Count should be 1'); + + // Second donation from another_user + start_cheat_caller_address(campaign_donation.contract_address, another_user); + campaign_donation.donate_to_campaign(campaign_id, 200); + stop_cheat_caller_address(campaign_donation.contract_address); + + let count_after_second = campaign_donation.get_campaign_donor_count(campaign_id); + assert(count_after_second == 2, 'Count should be 2'); + + // Third donation from third_user + start_cheat_caller_address(campaign_donation.contract_address, third_user); + campaign_donation.donate_to_campaign(campaign_id, 150); + stop_cheat_caller_address(campaign_donation.contract_address); + + let count_after_third = campaign_donation.get_campaign_donor_count(campaign_id); + assert(count_after_third == 3, 'Count should be 3'); +} + +#[test] +fn test_repeat_donor_count() { + let (token_address, sender, campaign_donation, _erc721) = setup(); + let target_amount = 1000_u256; + + // Create a campaign + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id = campaign_donation.create_campaign('RepeatDonorTest', target_amount); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approval + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // First donation + start_cheat_caller_address(campaign_donation.contract_address, sender); + campaign_donation.donate_to_campaign(campaign_id, 100); + stop_cheat_caller_address(campaign_donation.contract_address); + + let count_after_first = campaign_donation.get_campaign_donor_count(campaign_id); + assert(count_after_first == 1, 'Count should be 1'); + + // Second donation from same user + start_cheat_caller_address(campaign_donation.contract_address, sender); + campaign_donation.donate_to_campaign(campaign_id, 200); + stop_cheat_caller_address(campaign_donation.contract_address); + + let count_after_repeat = campaign_donation.get_campaign_donor_count(campaign_id); + assert(count_after_repeat == 1, 'Count should still be 1'); +} + +#[test] +fn test_multiple_campaigns_donor_count() { + let (token_address, sender, campaign_donation, _erc721) = setup(); + let target_amount = 1000_u256; + let another_user: ContractAddress = contract_address_const::<'another_user'>(); + + // Create two campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); + let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approvals and balances + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + token_dispatcher.transfer(another_user, 10000); + stop_cheat_caller_address(token_address); + + start_cheat_caller_address(token_address, another_user); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Sender donates to campaign 1 + start_cheat_caller_address(campaign_donation.contract_address, sender); + campaign_donation.donate_to_campaign(campaign_id_1, 100); + stop_cheat_caller_address(campaign_donation.contract_address); + + // Another user donates to campaign 2 + start_cheat_caller_address(campaign_donation.contract_address, another_user); + campaign_donation.donate_to_campaign(campaign_id_2, 200); + stop_cheat_caller_address(campaign_donation.contract_address); + + // Verify counts for both campaigns + let count_campaign_1 = campaign_donation.get_campaign_donor_count(campaign_id_1); + let count_campaign_2 = campaign_donation.get_campaign_donor_count(campaign_id_2); + + assert(count_campaign_1 == 1, 'Campaign 1 count should be 1'); + assert(count_campaign_2 == 1, 'Campaign 2 count should be 1'); + + // Same user donates to both campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + campaign_donation.donate_to_campaign(campaign_id_2, 150); + stop_cheat_caller_address(campaign_donation.contract_address); + + // Verify updated counts + let new_count_campaign_1 = campaign_donation.get_campaign_donor_count(campaign_id_1); + let new_count_campaign_2 = campaign_donation.get_campaign_donor_count(campaign_id_2); + + assert(new_count_campaign_1 == 1, 'should still be 1'); + assert(new_count_campaign_2 == 2, 'should be 2'); +} \ No newline at end of file From 29840806af0a3e949e7de5bad6e7199941629d0f Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Mon, 2 Jun 2025 08:39:33 +0100 Subject: [PATCH 4/9] ran scarb fmt --- src/campaign_donation.cairo | 11 +- src/interfaces/ICampaignDonation.cairo | 189 ++++++++++++------------- tests/test_campaign_donation.cairo | 8 +- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/src/campaign_donation.cairo b/src/campaign_donation.cairo index 5980dc2..c52f92e 100644 --- a/src/campaign_donation.cairo +++ b/src/campaign_donation.cairo @@ -48,7 +48,9 @@ pub mod CampaignDonation { campaign_withdrawn: Map, //Map campaign ids to whether they have been withdrawn donation_token: ContractAddress, unique_donors_count: Map, // Number of unique donors per campaign - campaign_donors: Map<(u256, ContractAddress), bool>, // Track if an address has donated to a campaign + campaign_donors: Map< + (u256, ContractAddress), bool, + > // Track if an address has donated to a campaign } @@ -308,23 +310,22 @@ pub mod CampaignDonation { campaign } fn get_campaign_progress(self: @ContractState, campaign_id: u256) -> u8 { - let campaign: Campaigns = self.campaigns.read(campaign_id); if campaign.target_amount == 0 { return 0_u8; } - + let progress = (campaign.current_balance * 100) / campaign.target_amount; // // Cap at 100% for overfunded campaigns if progress > 100_u256 { return 100_u8; } - + // Convert the calculated progress to u8 using try_into(). // This is a fallible conversion. unwrap() will panic if the value > 255. let progress_u8: u8 = progress.try_into().unwrap(); - + // Return the u8 value. progress_u8 } diff --git a/src/interfaces/ICampaignDonation.cairo b/src/interfaces/ICampaignDonation.cairo index 711960e..873d9f6 100644 --- a/src/interfaces/ICampaignDonation.cairo +++ b/src/interfaces/ICampaignDonation.cairo @@ -104,130 +104,129 @@ pub trait ICampaignDonation { /// * `Array` - An array of all donations made to the campaign fn get_campaign_donations(self: @TContractState, campaign_id: u256) -> Array; -// ************************************************************************* -// USER EXPERIENCE ENHANCEMENTS -// ************************************************************************* + // ************************************************************************* + // USER EXPERIENCE ENHANCEMENTS + // ************************************************************************* /// Gets all active (non-closed) campaigns -/// -/// # Returns -/// * `Array` - Array of campaigns that are still accepting donations -// fn get_active_campaigns(self: @TContractState) -> Array; + /// + /// # Returns + /// * `Array` - Array of campaigns that are still accepting donations + // fn get_active_campaigns(self: @TContractState) -> Array; /// Gets all campaigns created by a specific address -/// -/// # Arguments -/// * `owner` - The address of the campaign creator -/// -/// # Returns -/// * `Array` - Array of campaigns created by the owner -// fn get_campaigns_by_owner(self: @TContractState, owner: ContractAddress) -> Array; + /// + /// # Arguments + /// * `owner` - The address of the campaign creator + /// + /// # Returns + /// * `Array` - Array of campaigns created by the owner + // fn get_campaigns_by_owner(self: @TContractState, owner: ContractAddress) -> Array; /// Gets a campaign by its unique reference identifier -/// -/// # Arguments -/// * `campaign_ref` - The unique 5-character campaign reference -/// -/// # Returns -/// * `Option` - The campaign if found, None otherwise -// fn get_campaign_by_ref(self: @TContractState, campaign_ref: felt252) -> Option; + /// + /// # Arguments + /// * `campaign_ref` - The unique 5-character campaign reference + /// + /// # Returns + /// * `Option` - The campaign if found, None otherwise + // fn get_campaign_by_ref(self: @TContractState, campaign_ref: felt252) -> Option; // ************************************************************************* -// DONOR EXPERIENCE -// ************************************************************************* + // DONOR EXPERIENCE + // ************************************************************************* /// Gets all donations made by a specific donor across all campaigns -/// -/// # Arguments -/// * `donor` - The address of the donor -/// -/// # Returns -/// * `Array<(u256, Donations)>` - Array of tuples (campaign_id, donation) -// fn get_donations_by_donor(self: @TContractState, donor: ContractAddress) -> Array<(u256, -// Donations)>; + /// + /// # Arguments + /// * `donor` - The address of the donor + /// + /// # Returns + /// * `Array<(u256, Donations)>` - Array of tuples (campaign_id, donation) + // fn get_donations_by_donor(self: @TContractState, donor: ContractAddress) -> Array<(u256, + // Donations)>; /// Gets the total amount donated by a specific address -/// -/// # Arguments -/// * `donor` - The address of the donor -/// -/// # Returns -/// * `u256` - Total amount donated across all campaigns -// fn get_total_donated_by_donor(self: @TContractState, donor: ContractAddress) -> u256; + /// + /// # Arguments + /// * `donor` - The address of the donor + /// + /// # Returns + /// * `u256` - Total amount donated across all campaigns + // fn get_total_donated_by_donor(self: @TContractState, donor: ContractAddress) -> u256; /// Checks if a donor has contributed to a specific campaign -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// * `donor` - The donor address -/// -/// # Returns -/// * `bool` - True if the donor has contributed, false otherwise -// fn has_donated_to_campaign(self: @TContractState, campaign_id: u256, donor: ContractAddress) -// -> bool; + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// * `donor` - The donor address + /// + /// # Returns + /// * `bool` - True if the donor has contributed, false otherwise + // fn has_donated_to_campaign(self: @TContractState, campaign_id: u256, donor: ContractAddress) + // -> bool; // ************************************************************************* -// CAMPAIGN MANAGEMENT -// ************************************************************************* + // CAMPAIGN MANAGEMENT + // ************************************************************************* /// Updates the target amount for a campaign (only by owner before any donations) -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// * `new_target` - The new target amount -/// -/// # Requirements -/// * Caller must be campaign owner -/// * Campaign must have zero balance -/// * New target must be greater than zero -// fn update_campaign_target(ref self: TContractState, campaign_id: u256, new_target: u256); + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// * `new_target` - The new target amount + /// + /// # Requirements + /// * Caller must be campaign owner + /// * Campaign must have zero balance + /// * New target must be greater than zero + // fn update_campaign_target(ref self: TContractState, campaign_id: u256, new_target: u256); /// Cancels a campaign and enables refunds (only if no withdrawals have occurred) -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// -/// # Requirements -/// * Caller must be campaign owner -/// * Campaign must not be withdrawn -/// * Campaign must not have reached its goal -// fn cancel_campaign(ref self: TContractState, campaign_id: u256); + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// + /// # Requirements + /// * Caller must be campaign owner + /// * Campaign must not be withdrawn + /// * Campaign must not have reached its goal + // fn cancel_campaign(ref self: TContractState, campaign_id: u256); /// Allows donors to claim refunds from cancelled campaigns -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// -/// # Requirements -/// * Campaign must be cancelled -/// * Caller must have donated to the campaign -/// * Refund must not have been claimed already -// fn claim_refund(ref self: TContractState, campaign_id: u256); + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// + /// # Requirements + /// * Campaign must be cancelled + /// * Caller must have donated to the campaign + /// * Refund must not have been claimed already + // fn claim_refund(ref self: TContractState, campaign_id: u256); // ************************************************************************* -// ANALYTICS & INSIGHTS -// ************************************************************************* + // ANALYTICS & INSIGHTS + // ************************************************************************* /// Gets the progress percentage of a campaign -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// -/// # Returns -/// * `u8` - Progress percentage (0-100) - -fn get_campaign_progress(self: @TContractState, campaign_id: u256) -> u8; + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// + /// # Returns + /// * `u8` - Progress percentage (0-100) -/// Gets the number of unique donors for a campaign -/// -/// # Arguments -/// * `campaign_id` - The campaign ID -/// -/// # Returns -/// * `u32` - Number of unique donors + fn get_campaign_progress(self: @TContractState, campaign_id: u256) -> u8; -fn get_campaign_donor_count(self: @TContractState, campaign_id: u256) -> u32; + /// Gets the number of unique donors for a campaign + /// + /// # Arguments + /// * `campaign_id` - The campaign ID + /// + /// # Returns + /// * `u32` - Number of unique donors + fn get_campaign_donor_count(self: @TContractState, campaign_id: u256) -> u32; /// Gets campaigns close to reaching their goal /// /// # Arguments diff --git a/tests/test_campaign_donation.cairo b/tests/test_campaign_donation.cairo index 08a7615..6502366 100644 --- a/tests/test_campaign_donation.cairo +++ b/tests/test_campaign_donation.cairo @@ -571,7 +571,7 @@ fn test_campaign_progress_precision() { // Test various precise percentages start_cheat_caller_address(campaign_donation.contract_address, sender); - + // Test 51% campaign_donation.donate_to_campaign(campaign_id, 512); let progress = campaign_donation.get_campaign_progress(campaign_id); @@ -734,7 +734,7 @@ fn test_multiple_campaigns_donor_count() { // Verify counts for both campaigns let count_campaign_1 = campaign_donation.get_campaign_donor_count(campaign_id_1); let count_campaign_2 = campaign_donation.get_campaign_donor_count(campaign_id_2); - + assert(count_campaign_1 == 1, 'Campaign 1 count should be 1'); assert(count_campaign_2 == 1, 'Campaign 2 count should be 1'); @@ -746,7 +746,7 @@ fn test_multiple_campaigns_donor_count() { // Verify updated counts let new_count_campaign_1 = campaign_donation.get_campaign_donor_count(campaign_id_1); let new_count_campaign_2 = campaign_donation.get_campaign_donor_count(campaign_id_2); - + assert(new_count_campaign_1 == 1, 'should still be 1'); assert(new_count_campaign_2 == 2, 'should be 2'); -} \ No newline at end of file +} From 0c977fc7df2fd4fc1f502a8d70776059694027b9 Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Fri, 18 Jul 2025 00:52:30 +0100 Subject: [PATCH 5/9] Implemented Analytics & Insights Functions - post merge --- src/campaign_donation.cairo | 65 ++++--------------------------------- 1 file changed, 7 insertions(+), 58 deletions(-) diff --git a/src/campaign_donation.cairo b/src/campaign_donation.cairo index 4a3026c..f92dbb6 100644 --- a/src/campaign_donation.cairo +++ b/src/campaign_donation.cairo @@ -52,11 +52,6 @@ pub mod CampaignDonation { campaign_closed: Map, // Map campaign ids to closing boolean campaign_withdrawn: Map, //Map campaign ids to whether they have been withdrawn donation_token: ContractAddress, - unique_donors_count: Map, // Number of unique donors per campaign - campaign_donors: Map< - (u256, ContractAddress), bool, - > // Track if an address has donated to a campaign - donation_nft_address: ContractAddress, // Address of the Donation NFT contract donor_donations: Map< ContractAddress, Vec<(u256, u256)>, @@ -68,6 +63,10 @@ pub mod CampaignDonation { /// Protocol fee percentage using 10000 basis points (e.g. 250 = 2.5%). Default is 0. protocol_fee_percent: u256, protocol_fee_address: ContractAddress, + unique_donors_count: Map, // Number of unique donors per campaign + campaign_donors: Map< + (u256, ContractAddress), bool, + >, // Track if an address has donated to a campaign } @@ -315,59 +314,9 @@ pub mod CampaignDonation { } - fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { - // Simply return the stored count of unique donors - self.unique_donors_count.read(campaign_id) - <<<<<< feat/analytics_and_insight_functions - let mut campaign = self.get_campaign(campaign_id); - let contract_address = get_contract_address(); - let timestamp = get_block_timestamp(); - let donation_token = self.donation_token.read(); - // cannot send more than target amount - assert!(amount <= campaign.target_amount, "Error: More than Target"); - - let donation_id = self.donation_count.read() + 1; - - // Ensure the campaign is still accepting donations - assert(!campaign.is_goal_reached, TARGET_REACHED); - - // Prepare the ERC20 interface - let token_dispatcher = IERC20Dispatcher { contract_address: donation_token }; - - // Transfer funds to contract — requires prior approval - token_dispatcher.transfer_from(donor, contract_address, amount); - - // Update campaign amount - campaign.current_balance = campaign.current_balance + amount; - - // If goal reached, mark as closed - if (campaign.current_balance >= campaign.target_amount) { - campaign.is_goal_reached = true; - campaign.is_closed = true; - } - - // Check if this is the donor's first donation to this campaign - if !self.campaign_donors.read((campaign_id, donor)) { - // Mark that this address has donated to this campaign - self.campaign_donors.write((campaign_id, donor), true); - // Increment the unique donors count - let current_count = self.unique_donors_count.read(campaign_id); - self.unique_donors_count.write(campaign_id, current_count + 1); - } - - self.campaigns.write(campaign_id, campaign); - - // Create donation record - let donation = Donations { donation_id, donor, campaign_id, amount }; - - // Properly append to the Vec using push - self.donations.entry(campaign_id).push(donation); - - self.donation_count.write(donation_id); - - // Update the per-campaign donation count - let campaign_donation_count = self.donation_counts.read(campaign_id); - self.donation_counts.write(campaign_id, campaign_donation_count + 1); +fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { + // Simply return the stored count of unique donors + self.unique_donors_count.read(campaign_id) } fn set_donation_nft_address( ref self: ContractState, donation_nft_address: ContractAddress, From a2460b8a3346402cd8bff109054bb91b5cd3c30e Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Fri, 18 Jul 2025 00:52:58 +0100 Subject: [PATCH 6/9] tests not fully implemented --- tests/test_campaign_donation.cairo | 181 +++++++++++++++-------------- 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/tests/test_campaign_donation.cairo b/tests/test_campaign_donation.cairo index ad11e76..9ed7585 100644 --- a/tests/test_campaign_donation.cairo +++ b/tests/test_campaign_donation.cairo @@ -593,96 +593,97 @@ fn test_withdraw_funds_from_campaign_successful() { assert(owner_balance_after - owner_balance_before == 800, 'Withdrawal error') } -#[test] -fn test_get_campaign_progress() { - let (token_address, sender, campaign_donation, _erc721) = setup(); - let target_amount = 1000_u256; - - // Create multiple campaigns - start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); - let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); - let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount); - stop_cheat_caller_address(campaign_donation.contract_address); - - let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - - // Setup token approvals - start_cheat_caller_address(token_address, sender); - token_dispatcher.approve(campaign_donation.contract_address, 10000); - stop_cheat_caller_address(token_address); - - // Make donations to campaigns - start_cheat_caller_address(campaign_donation.contract_address, sender); - let _donation_id_1 = campaign_donation.donate_to_campaign(campaign_id_1, 100); // 10% - let _donation_id_2 = campaign_donation.donate_to_campaign(campaign_id_1, 200); // +20% = 30% - let _donation_id_3 = campaign_donation.donate_to_campaign(campaign_id_2, 500); // 50% - let _donation_id_4 = campaign_donation.donate_to_campaign(campaign_id_3, 1000); // 100% - stop_cheat_caller_address(campaign_donation.contract_address); - - // Test cases - // Partially funded: 300/1000 = 30% - let progress_1 = campaign_donation.get_campaign_progress(campaign_id_1); - assert(progress_1 == 30, 'partially funded'); - - // Partially funded: 500/1000 = 50% - let progress_2 = campaign_donation.get_campaign_progress(campaign_id_2); - assert(progress_2 == 50, 'partially funded'); - - // Fully/overfunded: 1000/1000 = 100% - let progress_3 = campaign_donation.get_campaign_progress(campaign_id_3); - assert(progress_3 == 100, 'fully/overfunded'); -} - -#[test] -fn test_campaign_progress_precision() { - let (token_address, sender, campaign_donation, _erc721) = setup(); - let target_amount = 1000_u256; - - // Create test campaign - start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount); - stop_cheat_caller_address(campaign_donation.contract_address); - - let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - - // Setup token approval - start_cheat_caller_address(token_address, sender); - token_dispatcher.approve(campaign_donation.contract_address, 10000); - stop_cheat_caller_address(token_address); - - // Test various precise percentages - start_cheat_caller_address(campaign_donation.contract_address, sender); - - // Test 51% - campaign_donation.donate_to_campaign(campaign_id, 512); - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 51, 'incorrect 51%'); - - // Test 79% - campaign_donation.donate_to_campaign(campaign_id, 284); // 512 + 284 = 796 - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 79, 'incorrect 79%'); - - // Test 83% - campaign_donation.donate_to_campaign(campaign_id, 40); // 796 + 40 = 836 - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 83, 'incorrect 83%'); - - // Test 93% - campaign_donation.donate_to_campaign(campaign_id, 94); // 836 + 94 = 930 - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 93, 'incorrect 93%'); - - // Test 99% - campaign_donation.donate_to_campaign(campaign_id, 60); // 930 + 60 = 990 - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 99, 'incorrect 99%'); - - // Test edge case: 99.9% (should round down to 99%) - campaign_donation.donate_to_campaign(campaign_id, 9); // 990 + 9 = 999 - let progress = campaign_donation.get_campaign_progress(campaign_id); - assert(progress == 99, 'incorrect 99.9%'); +// #[test] +// fn test_get_campaign_progress() { +// let (token_address, sender, campaign_donation, _erc721) = setup(); +// let target_amount = 1000_u256; + +// // Create multiple campaigns +// start_cheat_caller_address(campaign_donation.contract_address, sender); +// let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); +// let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); +// let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount); +// stop_cheat_caller_address(campaign_donation.contract_address); + +// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + +// // Setup token approvals +// start_cheat_caller_address(token_address, sender); +// token_dispatcher.approve(campaign_donation.contract_address, 10000); +// stop_cheat_caller_address(token_address); + +// // Make donations to campaigns +// start_cheat_caller_address(campaign_donation.contract_address, sender); +// let _donation_id_1 = campaign_donation.donate_to_campaign(campaign_id_1, 100); // 10% +// let _donation_id_2 = campaign_donation.donate_to_campaign(campaign_id_1, 200); // +20% = 30% +// let _donation_id_3 = campaign_donation.donate_to_campaign(campaign_id_2, 500); // 50% +// let _donation_id_4 = campaign_donation.donate_to_campaign(campaign_id_3, 1000); // 100% +// stop_cheat_caller_address(campaign_donation.contract_address); + +// // Test cases +// // Partially funded: 300/1000 = 30% +// let progress_1 = campaign_donation.get_campaign_progress(campaign_id_1); +// assert(progress_1 == 30, 'partially funded'); + +// // Partially funded: 500/1000 = 50% +// let progress_2 = campaign_donation.get_campaign_progress(campaign_id_2); +// assert(progress_2 == 50, 'partially funded'); + +// // Fully/overfunded: 1000/1000 = 100% +// let progress_3 = campaign_donation.get_campaign_progress(campaign_id_3); +// assert(progress_3 == 100, 'fully/overfunded'); +// } + +// #[test] +// fn test_campaign_progress_precision() { +// let (token_address, sender, campaign_donation, _erc721) = setup(); +// let target_amount = 1000_u256; + +// // Create test campaign +// start_cheat_caller_address(campaign_donation.contract_address, sender); +// let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount); +// stop_cheat_caller_address(campaign_donation.contract_address); + +// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + +// // Setup token approval +// start_cheat_caller_address(token_address, sender); +// token_dispatcher.approve(campaign_donation.contract_address, 10000); +// stop_cheat_caller_address(token_address); + +// // Test various precise percentages +// start_cheat_caller_address(campaign_donation.contract_address, sender); + +// // Test 51% +// campaign_donation.donate_to_campaign(campaign_id, 512); +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 51, 'incorrect 51%'); + +// // Test 79% +// campaign_donation.donate_to_campaign(campaign_id, 284); // 512 + 284 = 796 +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 79, 'incorrect 79%'); + +// // Test 83% +// campaign_donation.donate_to_campaign(campaign_id, 40); // 796 + 40 = 836 +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 83, 'incorrect 83%'); + +// // Test 93% +// campaign_donation.donate_to_campaign(campaign_id, 94); // 836 + 94 = 930 +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 93, 'incorrect 93%'); + +// // Test 99% +// campaign_donation.donate_to_campaign(campaign_id, 60); // 930 + 60 = 990 +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 99, 'incorrect 99%'); + +// // Test edge case: 99.9% (should round down to 99%) +// campaign_donation.donate_to_campaign(campaign_id, 9); // 990 + 9 = 999 +// let progress = campaign_donation.get_campaign_progress(campaign_id); +// assert(progress == 99, 'incorrect 99.9%'); +// } fn test_update_campaign_target_successful() { let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); From 6aec7196b9c0047abe48f0f692061e4d616ed0c2 Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Fri, 18 Jul 2025 02:03:43 +0100 Subject: [PATCH 7/9] final contract fixes --- src/campaign_donation.cairo | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/campaign_donation.cairo b/src/campaign_donation.cairo index f92dbb6..defede4 100644 --- a/src/campaign_donation.cairo +++ b/src/campaign_donation.cairo @@ -293,6 +293,10 @@ pub mod CampaignDonation { } fn get_campaign_progress(self: @ContractState, campaign_id: u256) -> u8 { + // Validate campaign exists + assert(campaign_id > 0, CAMPAIGN_NOT_FOUND); + assert(campaign_id <= self.campaign_counts.read(), CAMPAIGN_NOT_FOUND); + let campaign: Campaigns = self.campaigns.read(campaign_id); if campaign.target_amount == 0 { return 0_u8; @@ -306,18 +310,24 @@ pub mod CampaignDonation { } // Convert the calculated progress to u8 using try_into(). - // This is a fallible conversion. unwrap() will panic if the value > 255. - let progress_u8: u8 = progress.try_into().unwrap(); + // Since we already capped at 100, this should always succeed + let progress_u8: u8 = match progress.try_into() { + Option::Some(val) => val, + Option::None => 100_u8, // Fallback to 100% if conversion fails + }; - // Return the u8 value. progress_u8 } -fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { - // Simply return the stored count of unique donors - self.unique_donors_count.read(campaign_id) -} + fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { + // Validate campaign exists + assert(campaign_id > 0, CAMPAIGN_NOT_FOUND); + assert(campaign_id <= self.campaign_counts.read(), CAMPAIGN_NOT_FOUND); + + // Simply return the stored count of unique donors + self.unique_donors_count.read(campaign_id) + } fn set_donation_nft_address( ref self: ContractState, donation_nft_address: ContractAddress, ) { @@ -584,6 +594,14 @@ fn get_campaign_donor_count(self: @ContractState, campaign_id: u256) -> u32 { // Save donation reference for the donor self.donor_donations.entry(donor).push((campaign_id, donation_id)); + // Update unique donor count if this is the first donation from this donor to this campaign + let has_donated_before = self.campaign_donors.read((campaign_id, donor)); + if !has_donated_before { + self.campaign_donors.write((campaign_id, donor), true); + let current_unique_donors = self.unique_donors_count.read(campaign_id); + self.unique_donors_count.write(campaign_id, current_unique_donors + 1); + } + // Update the per-campaign donation count let campaign_donation_count = self.donation_counts.read(campaign_id); self.donation_counts.write(campaign_id, campaign_donation_count + 1); From dbc14acd4e6243e3609ad2845d2613f9318c2971 Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Fri, 18 Jul 2025 02:03:52 +0100 Subject: [PATCH 8/9] final test fixes --- tests/test_campaign_donation.cairo | 205 +++++++++++++++-------------- 1 file changed, 107 insertions(+), 98 deletions(-) diff --git a/tests/test_campaign_donation.cairo b/tests/test_campaign_donation.cairo index 9ed7585..b890880 100644 --- a/tests/test_campaign_donation.cairo +++ b/tests/test_campaign_donation.cairo @@ -593,98 +593,103 @@ fn test_withdraw_funds_from_campaign_successful() { assert(owner_balance_after - owner_balance_before == 800, 'Withdrawal error') } -// #[test] -// fn test_get_campaign_progress() { -// let (token_address, sender, campaign_donation, _erc721) = setup(); -// let target_amount = 1000_u256; - -// // Create multiple campaigns -// start_cheat_caller_address(campaign_donation.contract_address, sender); -// let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); -// let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); -// let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount); -// stop_cheat_caller_address(campaign_donation.contract_address); - -// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - -// // Setup token approvals -// start_cheat_caller_address(token_address, sender); -// token_dispatcher.approve(campaign_donation.contract_address, 10000); -// stop_cheat_caller_address(token_address); - -// // Make donations to campaigns -// start_cheat_caller_address(campaign_donation.contract_address, sender); -// let _donation_id_1 = campaign_donation.donate_to_campaign(campaign_id_1, 100); // 10% -// let _donation_id_2 = campaign_donation.donate_to_campaign(campaign_id_1, 200); // +20% = 30% -// let _donation_id_3 = campaign_donation.donate_to_campaign(campaign_id_2, 500); // 50% -// let _donation_id_4 = campaign_donation.donate_to_campaign(campaign_id_3, 1000); // 100% -// stop_cheat_caller_address(campaign_donation.contract_address); - -// // Test cases -// // Partially funded: 300/1000 = 30% -// let progress_1 = campaign_donation.get_campaign_progress(campaign_id_1); -// assert(progress_1 == 30, 'partially funded'); - -// // Partially funded: 500/1000 = 50% -// let progress_2 = campaign_donation.get_campaign_progress(campaign_id_2); -// assert(progress_2 == 50, 'partially funded'); - -// // Fully/overfunded: 1000/1000 = 100% -// let progress_3 = campaign_donation.get_campaign_progress(campaign_id_3); -// assert(progress_3 == 100, 'fully/overfunded'); -// } - -// #[test] -// fn test_campaign_progress_precision() { -// let (token_address, sender, campaign_donation, _erc721) = setup(); -// let target_amount = 1000_u256; - -// // Create test campaign -// start_cheat_caller_address(campaign_donation.contract_address, sender); -// let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount); -// stop_cheat_caller_address(campaign_donation.contract_address); - -// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - -// // Setup token approval -// start_cheat_caller_address(token_address, sender); -// token_dispatcher.approve(campaign_donation.contract_address, 10000); -// stop_cheat_caller_address(token_address); - -// // Test various precise percentages -// start_cheat_caller_address(campaign_donation.contract_address, sender); - -// // Test 51% -// campaign_donation.donate_to_campaign(campaign_id, 512); -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 51, 'incorrect 51%'); - -// // Test 79% -// campaign_donation.donate_to_campaign(campaign_id, 284); // 512 + 284 = 796 -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 79, 'incorrect 79%'); - -// // Test 83% -// campaign_donation.donate_to_campaign(campaign_id, 40); // 796 + 40 = 836 -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 83, 'incorrect 83%'); - -// // Test 93% -// campaign_donation.donate_to_campaign(campaign_id, 94); // 836 + 94 = 930 -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 93, 'incorrect 93%'); - -// // Test 99% -// campaign_donation.donate_to_campaign(campaign_id, 60); // 930 + 60 = 990 -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 99, 'incorrect 99%'); - -// // Test edge case: 99.9% (should round down to 99%) -// campaign_donation.donate_to_campaign(campaign_id, 9); // 990 + 9 = 999 -// let progress = campaign_donation.get_campaign_progress(campaign_id); -// assert(progress == 99, 'incorrect 99.9%'); -// } +#[test] +fn test_get_campaign_progress() { + let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); + let target_amount = 1000_u256; + let donation_token = token_address; + + // Create multiple campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount, donation_token); + let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount, donation_token); + let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount, donation_token); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approvals + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Make donations to campaigns + start_cheat_caller_address(campaign_donation.contract_address, sender); + let _donation_id_1 = campaign_donation.donate_to_campaign(campaign_id_1, 100); // 10% + let _donation_id_2 = campaign_donation.donate_to_campaign(campaign_id_1, 200); // +20% = 30% + let _donation_id_3 = campaign_donation.donate_to_campaign(campaign_id_2, 500); // 50% + let _donation_id_4 = campaign_donation.donate_to_campaign(campaign_id_3, 1000); // 100% + stop_cheat_caller_address(campaign_donation.contract_address); + + // Test cases + // Partially funded: 300/1000 = 30% + let progress_1 = campaign_donation.get_campaign_progress(campaign_id_1); + assert(progress_1 == 30, 'partially funded'); + + // Partially funded: 500/1000 = 50% + let progress_2 = campaign_donation.get_campaign_progress(campaign_id_2); + assert(progress_2 == 50, 'partially funded'); + + // Fully/overfunded: 1000/1000 = 100% + let progress_3 = campaign_donation.get_campaign_progress(campaign_id_3); + assert(progress_3 == 100, 'fully/overfunded'); +} + +#[test] +fn test_campaign_progress_precision() { + let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); + let target_amount = 1000_u256; + let donation_token = token_address; + + // Create test campaign + start_cheat_caller_address(campaign_donation.contract_address, sender); + let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount, donation_token); + stop_cheat_caller_address(campaign_donation.contract_address); + + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + // Setup token approval + start_cheat_caller_address(token_address, sender); + token_dispatcher.approve(campaign_donation.contract_address, 10000); + stop_cheat_caller_address(token_address); + + // Test various precise percentages + start_cheat_caller_address(campaign_donation.contract_address, sender); + + // Test 51% + campaign_donation.donate_to_campaign(campaign_id, 512); + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 51, 'incorrect 51%'); + + // Test 79% + campaign_donation.donate_to_campaign(campaign_id, 284); // 512 + 284 = 796 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 79, 'incorrect 79%'); + + // Test 83% + campaign_donation.donate_to_campaign(campaign_id, 40); // 796 + 40 = 836 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 83, 'incorrect 83%'); + // Test 93% + campaign_donation.donate_to_campaign(campaign_id, 94); // 836 + 94 = 930 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 93, 'incorrect 93%'); + + // Test 99% + campaign_donation.donate_to_campaign(campaign_id, 60); // 930 + 60 = 990 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 99, 'incorrect 99%'); + + // Test edge case: 99.9% (should round down to 99%) + campaign_donation.donate_to_campaign(campaign_id, 9); // 990 + 9 = 999 + let progress = campaign_donation.get_campaign_progress(campaign_id); + assert(progress == 99, 'incorrect 99.9%'); + + stop_cheat_caller_address(campaign_donation.contract_address); +} + +#[test] fn test_update_campaign_target_successful() { let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); @@ -800,14 +805,15 @@ fn test_cancel_campaign_already_closed() { #[test] fn test_unique_donor_count() { - let (token_address, sender, campaign_donation, _erc721) = setup(); + let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); let target_amount = 1000_u256; let another_user: ContractAddress = contract_address_const::<'another_user'>(); let third_user: ContractAddress = contract_address_const::<'third_user'>(); // Create a campaign start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id = campaign_donation.create_campaign('DonorTest', target_amount); + let donation_token = token_address; + let campaign_id = campaign_donation.create_campaign('DonorTest', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -858,12 +864,13 @@ fn test_unique_donor_count() { #[test] fn test_repeat_donor_count() { - let (token_address, sender, campaign_donation, _erc721) = setup(); + let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); let target_amount = 1000_u256; // Create a campaign start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id = campaign_donation.create_campaign('RepeatDonorTest', target_amount); + let donation_token = token_address; + let campaign_id = campaign_donation.create_campaign('RepeatDonorTest', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -892,14 +899,15 @@ fn test_repeat_donor_count() { #[test] fn test_multiple_campaigns_donor_count() { - let (token_address, sender, campaign_donation, _erc721) = setup(); + let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); let target_amount = 1000_u256; let another_user: ContractAddress = contract_address_const::<'another_user'>(); // Create two campaigns start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount); - let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount); + let donation_token = token_address; + let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount, donation_token); + let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -944,6 +952,7 @@ fn test_multiple_campaigns_donor_count() { assert(new_count_campaign_2 == 2, 'should be 2'); } +#[test] fn test_claim_refund_successful() { let (token_address, sender, campaign_donation, _erc721, _, _) = setup(); let target_amount = 1000_u256; From b97d75455a29cbe484744ab193f6ad24ff0f524e Mon Sep 17 00:00:00 2001 From: SuperFranky Date: Tue, 22 Jul 2025 05:26:20 +0100 Subject: [PATCH 9/9] ran scarb fmt --- .tool-versions | 2 +- src/campaign_donation.cairo | 12 ++++++------ tests/test_campaign_donation.cairo | 21 ++++++++++++++------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.tool-versions b/.tool-versions index 9f71edc..29753a3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.11.4 +scarb 2.11.2 starknet-foundry 0.40.0 diff --git a/src/campaign_donation.cairo b/src/campaign_donation.cairo index defede4..8e9ff4f 100644 --- a/src/campaign_donation.cairo +++ b/src/campaign_donation.cairo @@ -66,8 +66,7 @@ pub mod CampaignDonation { unique_donors_count: Map, // Number of unique donors per campaign campaign_donors: Map< (u256, ContractAddress), bool, - >, // Track if an address has donated to a campaign - + > // Track if an address has donated to a campaign } @@ -296,7 +295,7 @@ pub mod CampaignDonation { // Validate campaign exists assert(campaign_id > 0, CAMPAIGN_NOT_FOUND); assert(campaign_id <= self.campaign_counts.read(), CAMPAIGN_NOT_FOUND); - + let campaign: Campaigns = self.campaigns.read(campaign_id); if campaign.target_amount == 0 { return 0_u8; @@ -313,7 +312,7 @@ pub mod CampaignDonation { // Since we already capped at 100, this should always succeed let progress_u8: u8 = match progress.try_into() { Option::Some(val) => val, - Option::None => 100_u8, // Fallback to 100% if conversion fails + Option::None => 100_u8 // Fallback to 100% if conversion fails }; progress_u8 @@ -324,7 +323,7 @@ pub mod CampaignDonation { // Validate campaign exists assert(campaign_id > 0, CAMPAIGN_NOT_FOUND); assert(campaign_id <= self.campaign_counts.read(), CAMPAIGN_NOT_FOUND); - + // Simply return the stored count of unique donors self.unique_donors_count.read(campaign_id) } @@ -594,7 +593,8 @@ pub mod CampaignDonation { // Save donation reference for the donor self.donor_donations.entry(donor).push((campaign_id, donation_id)); - // Update unique donor count if this is the first donation from this donor to this campaign + // Update unique donor count if this is the first donation from this donor to this + // campaign let has_donated_before = self.campaign_donors.read((campaign_id, donor)); if !has_donated_before { self.campaign_donors.write((campaign_id, donor), true); diff --git a/tests/test_campaign_donation.cairo b/tests/test_campaign_donation.cairo index b890880..60f7f9f 100644 --- a/tests/test_campaign_donation.cairo +++ b/tests/test_campaign_donation.cairo @@ -601,9 +601,12 @@ fn test_get_campaign_progress() { // Create multiple campaigns start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount, donation_token); - let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount, donation_token); - let campaign_id_3 = campaign_donation.create_campaign('Campaign3', target_amount, donation_token); + let campaign_id_1 = campaign_donation + .create_campaign('Campaign1', target_amount, donation_token); + let campaign_id_2 = campaign_donation + .create_campaign('Campaign2', target_amount, donation_token); + let campaign_id_3 = campaign_donation + .create_campaign('Campaign3', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -643,7 +646,8 @@ fn test_campaign_progress_precision() { // Create test campaign start_cheat_caller_address(campaign_donation.contract_address, sender); - let campaign_id = campaign_donation.create_campaign('PrecisionTest', target_amount, donation_token); + let campaign_id = campaign_donation + .create_campaign('PrecisionTest', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -870,7 +874,8 @@ fn test_repeat_donor_count() { // Create a campaign start_cheat_caller_address(campaign_donation.contract_address, sender); let donation_token = token_address; - let campaign_id = campaign_donation.create_campaign('RepeatDonorTest', target_amount, donation_token); + let campaign_id = campaign_donation + .create_campaign('RepeatDonorTest', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; @@ -906,8 +911,10 @@ fn test_multiple_campaigns_donor_count() { // Create two campaigns start_cheat_caller_address(campaign_donation.contract_address, sender); let donation_token = token_address; - let campaign_id_1 = campaign_donation.create_campaign('Campaign1', target_amount, donation_token); - let campaign_id_2 = campaign_donation.create_campaign('Campaign2', target_amount, donation_token); + let campaign_id_1 = campaign_donation + .create_campaign('Campaign1', target_amount, donation_token); + let campaign_id_2 = campaign_donation + .create_campaign('Campaign2', target_amount, donation_token); stop_cheat_caller_address(campaign_donation.contract_address); let token_dispatcher = IERC20Dispatcher { contract_address: token_address };