diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index ea92d8d..9a9df45 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -3,14 +3,124 @@ pub mod storage; pub mod types; -use soroban_sdk::{contract, contractimpl, Env}; +use soroban_sdk::{contract, contractimpl, Env, Vec}; +use types::{AssetInfo, CampaignData, CampaignStatus, Error, MilestoneData}; +use storage::{get_campaign, set_campaign, set_milestone}; #[contract] pub struct CampaignContract; #[contractimpl] impl CampaignContract { + /// Initialize a new campaign with strict validation on all inputs. + /// + /// # Panics + /// - `Error::InvalidGoalAmount` if goal_amount <= 0 + /// - `Error::InvalidEndTime` if end_time <= current ledger timestamp + /// - `Error::InvalidAssets` if accepted_assets is empty + /// - `Error::InvalidMilestones` if milestones are not sorted ascending by target_amount + /// - `Error::MilestoneMismatch` if last milestone.target_amount != goal_amount + pub fn initialize( + env: Env, + creator: soroban_sdk::Address, + goal_amount: i128, + end_time: u64, + accepted_assets: Vec, + milestones: Vec, + ) -> Result<(), Error> { + // Validation 1: goal_amount > 0 + if goal_amount <= 0 { + panic_with_error(&env, Error::InvalidGoalAmount); + } + + // Validation 2: end_time > current ledger timestamp + let current_timestamp = env.ledger().timestamp(); + if end_time <= current_timestamp { + panic_with_error(&env, Error::InvalidEndTime); + } + + // Validation 3: accepted_assets non-empty + if accepted_assets.is_empty() { + panic_with_error(&env, Error::InvalidAssets); + } + + // Validation 4 & 5: milestones sorted ascending and last == goal_amount + if !milestones.is_empty() { + validate_milestones(&env, &milestones, goal_amount)?; + } + + // All validations passed, store campaign data + let campaign = CampaignData { + creator, + goal_amount, + raised_amount: 0, + end_time, + status: CampaignStatus::Active, + accepted_assets, + milestone_count: milestones.len() as u32, + }; + + set_campaign(&env, &campaign); + + // Store each milestone + for (index, milestone) in milestones.iter().enumerate() { + set_milestone(&env, index as u32, milestone); + } + + Ok(()) + } + pub fn hello(env: Env) -> soroban_sdk::Symbol { soroban_sdk::Symbol::new(&env, "campaign") } } + +/// Helper function to validate milestone conditions +fn validate_milestones( + env: &Env, + milestones: &Vec, + goal_amount: i128, +) -> Result<(), Error> { + // Check if milestones are sorted ascending by target_amount + for i in 1..milestones.len() { + let prev = &milestones.get(i - 1).unwrap(); + let current = &milestones.get(i).unwrap(); + + if prev.target_amount >= current.target_amount { + panic_with_error(env, Error::InvalidMilestones); + } + } + + // Check if last milestone.target_amount == goal_amount + if let Some(last_milestone) = milestones.last() { + if last_milestone.target_amount != goal_amount { + panic_with_error(env, Error::MilestoneMismatch); + } + } else { + panic_with_error(env, Error::InvalidMilestones); + } + + Ok(()) +} + +/// Helper function to panic with a descriptive error message +fn panic_with_error(env: &Env, error: Error) -> ! { + match error { + Error::InvalidGoalAmount => { + env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidGoalAmount")) + } + Error::InvalidEndTime => { + env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidEndTime")) + } + Error::InvalidAssets => { + env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidAssets")) + } + Error::InvalidMilestones => { + env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidMilestones")) + } + Error::MilestoneMismatch => { + env.panic_with_error(soroban_sdk::Symbol::new(env, "MilestoneMismatch")) + } + } +} + diff --git a/campaign/src/types.rs b/campaign/src/types.rs index 6e4f0de..5cb3c94 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -1,5 +1,18 @@ use soroban_sdk::{contracttype, Address, BytesN, Vec}; +// ── Error enum ────────────────────────────────────────────────────────────── + +/// Initialization validation errors +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Error { + InvalidGoalAmount, // goal_amount must be > 0 + InvalidEndTime, // end_time must be > current ledger timestamp + InvalidAssets, // accepted_assets must be non-empty + InvalidMilestones, // milestones must be sorted ascending and last must equal goal + MilestoneMismatch, // last milestone.target_amount != goal_amount +} + // ── Supporting enums ───────────────────────────────────────────────────────── /// Issue #167 – campaign lifecycle status