From be604e4cc2491291bbbe8c5044d81501de1368e3 Mon Sep 17 00:00:00 2001 From: Ryan Chen <109298725+Bismithblde@users.noreply.github.com> Date: Mon, 9 Nov 2026 18:34:02 -0500 Subject: [PATCH] fix(fundraiser): avoid overflow in contribution limits --- .../fundraiser/src/instructions/contribute.rs | 8 ++-- .../fundraiser/tests/test_fundraiser.rs | 48 +++++++++++++++++++ tokens/token-fundraiser/anchor/readme.MD | 8 ++-- 3 files changed, 58 insertions(+), 6 deletions(-) diff --git a/tokens/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs b/tokens/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs index f217c2a79..76e0d5b6c 100644 --- a/tokens/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs +++ b/tokens/token-fundraiser/anchor/programs/fundraiser/src/instructions/contribute.rs @@ -62,9 +62,12 @@ impl<'info> Contribute<'info> { FundraiserError::ContributionTooSmall ); + let max_contribution = (u128::from(self.fundraiser.amount_to_raise) + * u128::from(MAX_CONTRIBUTION_PERCENTAGE)) / u128::from(PERCENTAGE_SCALER); + // Check if the amount to contribute is less than the maximum allowed contribution require!( - amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER, + u128::from(amount) <= max_contribution, FundraiserError::ContributionTooBig ); @@ -77,8 +80,7 @@ impl<'info> Contribute<'info> { // Check if the maximum contributions per contributor have been reached require!( - (self.contributor_account.amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER) - && (self.contributor_account.amount + amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER), + u128::from(self.contributor_account.amount) + u128::from(amount) <= max_contribution, FundraiserError::MaximumContributionsReached ); diff --git a/tokens/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs b/tokens/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs index f1618296e..a3a435f4f 100644 --- a/tokens/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs +++ b/tokens/token-fundraiser/anchor/programs/fundraiser/tests/test_fundraiser.rs @@ -262,3 +262,51 @@ fn check_contributions_rejects_mint_other_than_recorded() { assert_eq!(state.mint_to_raise, real_mint); assert_eq!(token_balance(&env.svm, &fake_vault), amount_to_raise); } + +#[test] +fn large_targets_accept_contributions_and_enforce_ten_percent_cap() { + for target in [u64::MAX / 10 + 1, u64::MAX] { + let mut env = setup(); + let mint = create_mint(&mut env.svm, &env.payer, &env.payer.pubkey()); + let contributor_ata = get_associated_token_address(&env.payer.pubkey(), &mint); + let setup_ixs = [ + create_associated_token_account(&env.payer.pubkey(), &env.payer.pubkey(), &mint, &spl_token::ID), + spl_token::instruction::mint_to( + &spl_token::ID, + &mint, + &contributor_ata, + &env.payer.pubkey(), + &[], + u64::MAX, + ) + .unwrap(), + ]; + send(&mut env.svm, &setup_ixs, &env.payer, &[&env.payer]).unwrap(); + let ix = initialize(&env, &mint, target, 1); + send(&mut env.svm, &[ix], &env.maker, &[&env.maker]).unwrap(); + let campaign = Campaign { + mint, + vault: get_associated_token_address(&env.fundraiser, &mint), + contributor_ata, + contributor_account: Pubkey::find_program_address( + &[b"contributor", env.fundraiser.as_ref(), env.payer.pubkey().as_ref()], + &fundraiser::id(), + ) + .0, + }; + let ix = campaign.contribute(&env, 1_000_000); + send(&mut env.svm, &[ix], &env.payer, &[&env.payer]).unwrap(); + assert_eq!(token_balance(&env.svm, &campaign.vault), 1_000_000); + let cap = target / 10; + let ix = campaign.contribute(&env, cap + 1); + assert_anchor_error(send(&mut env.svm, &[ix], &env.payer, &[&env.payer]), "ContributionTooBig"); + let ix = campaign.contribute(&env, cap - 1_000_000); + send(&mut env.svm, &[ix], &env.payer, &[&env.payer]).unwrap(); + let ix = campaign.contribute(&env, 1); + assert_anchor_error(send(&mut env.svm, &[ix], &env.payer, &[&env.payer]), "MaximumContributionsReached"); + assert_eq!(token_balance(&env.svm, &campaign.vault), cap); + assert_eq!(token_balance(&env.svm, &contributor_ata), u64::MAX - cap); + assert_eq!(read::(&env.svm, &campaign.contributor_account).amount, cap); + assert_eq!(read::(&env.svm, &env.fundraiser).current_amount, cap); + } +} diff --git a/tokens/token-fundraiser/anchor/readme.MD b/tokens/token-fundraiser/anchor/readme.MD index 165d026e6..655aa8aee 100644 --- a/tokens/token-fundraiser/anchor/readme.MD +++ b/tokens/token-fundraiser/anchor/readme.MD @@ -199,16 +199,18 @@ impl<'info> Contribute<'info> { FundraiserError::ContributionTooSmall ); + let max_contribution = (u128::from(self.fundraiser.amount_to_raise) + * u128::from(MAX_CONTRIBUTION_PERCENTAGE)) / u128::from(PERCENTAGE_SCALER); + // Check if the amount to contribute is less than the maximum allowed contribution require!( - amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER, + u128::from(amount) <= max_contribution, FundraiserError::ContributionTooBig ); // Check if the maximum contributions per contributor have been reached require!( - (self.contributor_account.amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER) - && (self.contributor_account.amount + amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER), + u128::from(self.contributor_account.amount) + u128::from(amount) <= max_contribution, FundraiserError::MaximumContributionsReached );