Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,17 @@ pub(crate) fn set_campaign_fee_override(
assert_admin(env, &admin)?;
// No require_not_paused: per-campaign fee overrides are admin governance (#388).
let mut campaign = get_campaign_or_error(env, campaign_id)?;

// Strong validation: reject any fee override > 100% (10,000 bps)
if fee_bps > crate::PLATFORM_FEE_ABSOLUTE_MAX_BPS {
return Err(Error::ValidationFailed);
return Err(Error::InvalidFeeOverride);
}

// Also enforce reasonable upper bound (platform max = 10% = 1000 bps)
if fee_bps > crate::PLATFORM_FEE_MAX_BPS {
return Err(Error::ValidationFailed);
return Err(Error::InvalidFeeOverride);
}

bump_instance_ttl(env);
campaign.fee_override = Some(fee_bps);
storage::set_campaign(env, campaign_id, &campaign);
Expand Down Expand Up @@ -510,8 +515,8 @@ pub(crate) fn resume_campaign(env: &Env, campaign_id: u32, caller: Address) -> R
Ok(())
}

use soroban_sdk::{contractimpl, Address, Env, String};
use crate::errors::Error;
use soroban_sdk::{contractimpl, Address, Env, String};

#[contractimpl]
impl ProofOfHeartContract {
Expand Down Expand Up @@ -543,4 +548,4 @@ impl ProofOfHeartContract {
let cap_key = DataKey::CategoryMaxGoalCap(category);
env.storage().persistent().get(&cap_key)
}
}
}
3 changes: 3 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ pub enum Error {
CampaignAlreadyBookmarked = 44,
/// The campaign is not in the wallet's saved/bookmarked list.
CampaignNotBookmarked = 45,
/// The fee override basis points exceed the maximum allowed (100%).
InvalidFeeOverride = 46,
}

impl Error {
Expand Down Expand Up @@ -148,6 +150,7 @@ impl Error {
Error::InvalidStateTransition => "InvalidStateTransition",
Error::CampaignAlreadyBookmarked => "CampaignAlreadyBookmarked",
Error::CampaignNotBookmarked => "CampaignNotBookmarked",
Error::InvalidFeeOverride => "InvalidFeeOverride",
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/tests/test_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,10 +696,15 @@ fn test_campaign_fee_override_above_max_rejected() {
0,
0i128,
));
// Test 1001 bps (10.01%) - exceeds platform max (1000 bps = 10%)
let res = client2.try_set_campaign_fee_override(&id, &admin2, &1001);
assert_eq!(res.unwrap_err().unwrap(), Error::ValidationFailed);
assert_eq!(res.unwrap_err().unwrap(), Error::InvalidFeeOverride);
// Test 10001 bps (100.01%) - exceeds absolute max (10000 bps = 100%)
let res = client2.try_set_campaign_fee_override(&id, &admin2, &10001);
assert_eq!(res.unwrap_err().unwrap(), Error::ValidationFailed);
assert_eq!(res.unwrap_err().unwrap(), Error::InvalidFeeOverride);
// Test edge case: exactly 10000 bps (100%) - should be rejected as well
let res = client2.try_set_campaign_fee_override(&id, &admin2, &10000);
assert_eq!(res.unwrap_err().unwrap(), Error::InvalidFeeOverride);
}

#[test]
Expand Down
Loading