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: 13 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(grep -E \"\\\\.\\(cairo|rs\\)$\")",
"Bash(cargo test *)",
"Read(//home/feyishola/.cargo/bin/**)",
"Read(//home/feyishola/**)",
"Read(//usr/local/bin/**)",
"Read(//opt/**)",
"Read(//home/feyishola/.rustup/**)"
]
}
}
2 changes: 2 additions & 0 deletions apps/onchain/contracts/matching_pool/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ pub enum MatchingPoolError {
InvalidRoundDates = 15,
ContractPaused = 16,
Reentrancy = 17,
ContributorCapExceeded = 18,
RoundCapExceeded = 19,
}
8 changes: 8 additions & 0 deletions apps/onchain/contracts/matching_pool/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,11 @@ pub struct AllMatchesDistributedEvent {
pub round_id: u64,
pub total_distributed: i128,
}

#[contractevent]
pub struct RoundCapsSetEvent {
#[topic]
pub round_id: u64,
pub per_contributor_cap: i128,
pub round_contribution_cap: i128,
}
100 changes: 99 additions & 1 deletion apps/onchain/contracts/matching_pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use math::{sqrt_scaled, unscale};
use reentrancy_guard::{acquire as acquire_reentrancy, release as release_reentrancy};
use soroban_sdk::token::TokenClient;
use soroban_sdk::{contract, contractimpl, vec, Address, BytesN, Env, Symbol, Vec};
use storage::{DataKey, RoundData};
use storage::{CapData, DataKey, RoundData};

#[contract]
pub struct MatchingPoolContract;
Expand Down Expand Up @@ -112,6 +112,15 @@ impl MatchingPoolContract {
env.storage()
.instance()
.set(&DataKey::NextRoundId, &(round_id + 1));
env.storage()
.persistent()
.set(&DataKey::RoundContributorCap(round_id), &0i128);
env.storage()
.persistent()
.set(&DataKey::RoundContributionCap(round_id), &0i128);
env.storage()
.persistent()
.set(&DataKey::RoundTotalContributions(round_id), &0i128);
events::RoundCreatedEvent {
admin,
round_id,
Expand Down Expand Up @@ -278,6 +287,30 @@ impl MatchingPoolContract {
}
let contrib_key = DataKey::ContributorAmount(round_id, project_id, contributor.clone());
let prev: i128 = env.storage().persistent().get(&contrib_key).unwrap_or(0);

let contributor_cap: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundContributorCap(round_id))
.unwrap_or(0);
if contributor_cap > 0 && prev.saturating_add(amount) > contributor_cap {
return Err(MatchingPoolError::ContributorCapExceeded);
}

let round_cap: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundContributionCap(round_id))
.unwrap_or(0);
let current_round_total: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundTotalContributions(round_id))
.unwrap_or(0);
if round_cap > 0 && current_round_total.saturating_add(amount) > round_cap {
return Err(MatchingPoolError::RoundCapExceeded);
}

if prev == 0 {
let cnt_key = DataKey::ProjectContributorCount(round_id, project_id);
let cnt: u32 = env.storage().persistent().get(&cnt_key).unwrap_or(0);
Expand All @@ -295,6 +328,10 @@ impl MatchingPoolContract {
env.storage()
.persistent()
.set(&total_key, &(total + amount));
env.storage().persistent().set(
&DataKey::RoundTotalContributions(round_id),
&(current_round_total + amount),
);
events::ContributionRecordedEvent {
round_id,
project_id,
Expand Down Expand Up @@ -503,6 +540,67 @@ impl MatchingPoolContract {
unscale(unscale(squared))
}

pub fn set_round_caps(
env: Env,
admin: Address,
round_id: u64,
per_contributor_cap: i128,
round_contribution_cap: i128,
) -> Result<(), MatchingPoolError> {
Self::require_admin(&env, &admin)?;
if per_contributor_cap < 0 || round_contribution_cap < 0 {
return Err(MatchingPoolError::InvalidAmount);
}
let round: RoundData = env
.storage()
.persistent()
.get(&DataKey::Round(round_id))
.ok_or(MatchingPoolError::RoundNotFound)?;
if round.is_finalized {
return Err(MatchingPoolError::RoundAlreadyFinalized);
}
env.storage()
.persistent()
.set(&DataKey::RoundContributorCap(round_id), &per_contributor_cap);
env.storage()
.persistent()
.set(&DataKey::RoundContributionCap(round_id), &round_contribution_cap);
events::RoundCapsSetEvent {
round_id,
per_contributor_cap,
round_contribution_cap,
}
.publish(&env);
Ok(())
}

pub fn get_round_caps(env: Env, round_id: u64) -> Result<CapData, MatchingPoolError> {
env.storage()
.persistent()
.get::<_, RoundData>(&DataKey::Round(round_id))
.ok_or(MatchingPoolError::RoundNotFound)?;
let per_contributor_cap: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundContributorCap(round_id))
.unwrap_or(0);
let round_contribution_cap: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundContributionCap(round_id))
.unwrap_or(0);
let total_contributions: i128 = env
.storage()
.persistent()
.get(&DataKey::RoundTotalContributions(round_id))
.unwrap_or(0);
Ok(CapData {
per_contributor_cap,
round_contribution_cap,
total_contributions,
})
}

pub fn get_round(env: Env, round_id: u64) -> Result<RoundData, MatchingPoolError> {
env.storage()
.persistent()
Expand Down
12 changes: 12 additions & 0 deletions apps/onchain/contracts/matching_pool/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub enum DataKey {
ContributorAmount(u64, u64, Address), // (round_id, project_id, contributor) -> i128
MatchDistributed(u64), // round_id -> bool
RoundStatus(u64), // round_id -> Symbol ("ACTIVE"|"FINALIZED"|"DISTRIBUTED")
RoundContributorCap(u64), // round_id -> i128 (0=no cap; per-contributor per-project)
RoundContributionCap(u64), // round_id -> i128 (0=no cap; total across all projects)
RoundTotalContributions(u64), // round_id -> i128 (running sum of all contributions)
}

/// Core data for a funding round
Expand All @@ -33,3 +36,12 @@ pub struct RoundData {
pub is_finalized: bool,
pub is_distributed: bool,
}

/// Cap configuration and live state for a round (returned by get_round_caps)
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapData {
pub per_contributor_cap: i128, // 0 = uncapped
pub round_contribution_cap: i128, // 0 = uncapped
pub total_contributions: i128, // running sum of all recorded contributions
}
Loading
Loading