From 3e2af1c021197c90d6ce3867dd651669e5d55ace Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:03:57 +0000 Subject: [PATCH 1/9] implemented the input --- campaign/src/types.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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 From 63bbf454202d507ca4fc54d5a8fcb687cdb20455 Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:04:36 +0000 Subject: [PATCH 2/9] implemented the input --- campaign/src/lib.rs | 112 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) 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")) + } + } +} + From b1be0a35b8e71031f10e4036ebed4428cbfe4f77 Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:12:12 +0000 Subject: [PATCH 3/9] implemented the stayus --- campaign/src/lib.rs | 93 +++++++++++++++++++++++++++++++++++++------ campaign/src/types.rs | 43 ++++++++++++++------ 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index 9a9df45..efb3ed7 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -4,7 +4,7 @@ pub mod storage; pub mod types; use soroban_sdk::{contract, contractimpl, Env, Vec}; -use types::{AssetInfo, CampaignData, CampaignStatus, Error, MilestoneData}; +use types::{AssetInfo, CampaignData, CampaignStatus, Error, MilestoneData, MilestoneStatus}; use storage::{get_campaign, set_campaign, set_milestone}; #[contract] @@ -105,21 +105,90 @@ fn validate_milestones( /// 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")) + let error_name = match error { + Error::InvalidGoalAmount => "InvalidGoalAmount", + Error::InvalidEndTime => "InvalidEndTime", + Error::InvalidAssets => "InvalidAssets", + Error::InvalidMilestones => "InvalidMilestones", + Error::MilestoneMismatch => "MilestoneMismatch", + Error::InvalidCampaignTransition => "InvalidCampaignTransition", + Error::InvalidMilestoneTransition => "InvalidMilestoneTransition", + Error::CampaignNotActive => "CampaignNotActive", + Error::CampaignEnded => "CampaignEnded", + Error::GoalNotReached => "GoalNotReached", + }; + env.panic_with_error(soroban_sdk::Symbol::new(env, error_name)) +} + +/// Validates campaign status transitions and panics if invalid +/// +/// Valid transitions: +/// Active -> GoalReached (when goal reached) +/// Active -> Ended (when deadline passes) +/// GoalReached -> Ended (when deadline passes) +/// Active/GoalReached/Ended -> Cancelled (by creator) +pub fn validate_campaign_transition( + env: &Env, + current_status: &CampaignStatus, + next_status: &CampaignStatus, +) -> Result<(), Error> { + match (current_status, next_status) { + // Active can transition to GoalReached, Ended, or Cancelled + (CampaignStatus::Active, CampaignStatus::GoalReached) => Ok(()), + (CampaignStatus::Active, CampaignStatus::Ended) => Ok(()), + (CampaignStatus::Active, CampaignStatus::Cancelled) => Ok(()), + + // GoalReached can transition to Ended or Cancelled + (CampaignStatus::GoalReached, CampaignStatus::Ended) => Ok(()), + (CampaignStatus::GoalReached, CampaignStatus::Cancelled) => Ok(()), + + // Ended can only transition to Cancelled + (CampaignStatus::Ended, CampaignStatus::Cancelled) => Ok(()), + + // Cancelled is terminal + (CampaignStatus::Cancelled, _) => { + panic_with_error(env, Error::InvalidCampaignTransition); } - Error::InvalidEndTime => { - env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidEndTime")) + + // All other transitions are invalid + _ => { + panic_with_error(env, Error::InvalidCampaignTransition); } - Error::InvalidAssets => { - env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidAssets")) + } +} + +/// Validates milestone status transitions and panics if invalid +/// +/// Valid transitions: +/// Locked -> Unlocked (when target_amount reached) +/// Unlocked -> Released (when explicitly released) +/// Locked -> Released (direct transition allowed) +pub fn validate_milestone_transition( + env: &Env, + current_status: &MilestoneStatus, + next_status: &MilestoneStatus, +) -> Result<(), Error> { + match (current_status, next_status) { + // Locked can transition to Unlocked or Released + (MilestoneStatus::Locked, MilestoneStatus::Unlocked) => Ok(()), + (MilestoneStatus::Locked, MilestoneStatus::Released) => Ok(()), + + // Unlocked can transition to Released + (MilestoneStatus::Unlocked, MilestoneStatus::Released) => Ok(()), + + // Released is terminal + (MilestoneStatus::Released, _) => { + panic_with_error(env, Error::InvalidMilestoneTransition); } - Error::InvalidMilestones => { - env.panic_with_error(soroban_sdk::Symbol::new(env, "InvalidMilestones")) + + // Prevent Unlocked -> Locked (going backwards) + (MilestoneStatus::Unlocked, MilestoneStatus::Locked) => { + panic_with_error(env, Error::InvalidMilestoneTransition); } - Error::MilestoneMismatch => { - env.panic_with_error(soroban_sdk::Symbol::new(env, "MilestoneMismatch")) + + // All other transitions are invalid + _ => { + panic_with_error(env, Error::InvalidMilestoneTransition); } } } diff --git a/campaign/src/types.rs b/campaign/src/types.rs index 5cb3c94..f1d263a 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -2,36 +2,53 @@ use soroban_sdk::{contracttype, Address, BytesN, Vec}; // ── Error enum ────────────────────────────────────────────────────────────── -/// Initialization validation errors +/// All error types for validation and state transitions #[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 + // ── Initialization validation errors ── + 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 + + // ── State transition errors ── + InvalidCampaignTransition, // campaign status transition not allowed + InvalidMilestoneTransition,// milestone status transition not allowed + CampaignNotActive, // campaign must be Active to accept donations + CampaignEnded, // campaign end_time has passed + GoalNotReached, // cannot transition to GoalReached before reaching goal } // ── Supporting enums ───────────────────────────────────────────────────────── /// Issue #167 – campaign lifecycle status +/// State transitions: +/// Active -> GoalReached (goal reached) +/// Active -> Ended (deadline passed) +/// GoalReached -> Ended (deadline passed) +/// Active/GoalReached/Ended -> Cancelled (by creator) #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum CampaignStatus { - Active, - Successful, - Failed, - Cancelled, + Active, // Campaign accepting donations + GoalReached, // Goal amount reached, still accepting donations until deadline + Ended, // Deadline passed or campaign concluded + Cancelled, // Campaign cancelled by creator } /// Issue #168 – milestone release status +/// State transitions: +/// Locked -> Unlocked (when target_amount reached) +/// Unlocked -> Released (when explicitly released by admin) +/// Locked/Unlocked -> Released (milestone marked as released) #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum MilestoneStatus { - Pending, - Released, - Cancelled, + Locked, // Milestone condition not yet met + Unlocked, // Target amount reached, awaiting release + Released, // Funds released to beneficiary } /// Accepted asset descriptor (native XLM or a Stellar asset) From eaa307381196b2bcacc42151a9f36912d766541d Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:22:05 +0000 Subject: [PATCH 4/9] implemented the assetinfo --- campaign/src/types.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/campaign/src/types.rs b/campaign/src/types.rs index f1d263a..ea5c1e9 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -10,6 +10,7 @@ pub enum Error { InvalidGoalAmount, // goal_amount must be > 0 InvalidEndTime, // end_time must be > current ledger timestamp InvalidAssets, // accepted_assets must be non-empty + InvalidAssetCode, // asset_code must be non-empty and valid InvalidMilestones, // milestones must be sorted ascending and last must equal goal MilestoneMismatch, // last milestone.target_amount != goal_amount @@ -51,7 +52,26 @@ pub enum MilestoneStatus { Released, // Funds released to beneficiary } +/// Reusable struct for Stellar asset representation +/// Enables consistent multi-asset support across the contract +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StellarAsset { + /// Asset code (e.g., "XLM", "USDC", "EUR") + pub asset_code: soroban_sdk::String, + /// Issuer address; None for native XLM + pub issuer: Option
, +} + +impl StellarAsset { + /// Helper function to check if this asset is native XLM + pub fn is_xlm(&self) -> bool { + self.issuer.is_none() + } +} + /// Accepted asset descriptor (native XLM or a Stellar asset) +/// Deprecated: Use StellarAsset instead #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum AssetInfo { From 4c0c704a2276b3737b18019b43aede3b7e4da13d Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:22:33 +0000 Subject: [PATCH 5/9] implemented the assetinfo --- campaign/src/lib.rs | 8 ++++++-- campaign/src/types.rs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index efb3ed7..5dfe652 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -4,7 +4,7 @@ pub mod storage; pub mod types; use soroban_sdk::{contract, contractimpl, Env, Vec}; -use types::{AssetInfo, CampaignData, CampaignStatus, Error, MilestoneData, MilestoneStatus}; +use types::{CampaignData, CampaignStatus, Error, MilestoneData, MilestoneStatus, StellarAsset}; use storage::{get_campaign, set_campaign, set_milestone}; #[contract] @@ -18,6 +18,7 @@ impl CampaignContract { /// - `Error::InvalidGoalAmount` if goal_amount <= 0 /// - `Error::InvalidEndTime` if end_time <= current ledger timestamp /// - `Error::InvalidAssets` if accepted_assets is empty + /// - `Error::InvalidAssetCode` if any asset_code is empty or invalid /// - `Error::InvalidMilestones` if milestones are not sorted ascending by target_amount /// - `Error::MilestoneMismatch` if last milestone.target_amount != goal_amount pub fn initialize( @@ -25,7 +26,7 @@ impl CampaignContract { creator: soroban_sdk::Address, goal_amount: i128, end_time: u64, - accepted_assets: Vec, + accepted_assets: Vec, milestones: Vec, ) -> Result<(), Error> { // Validation 1: goal_amount > 0 @@ -44,6 +45,9 @@ impl CampaignContract { panic_with_error(&env, Error::InvalidAssets); } + // Validation 3b: validate each asset code + validate_assets(&env, &accepted_assets)?; + // Validation 4 & 5: milestones sorted ascending and last == goal_amount if !milestones.is_empty() { validate_milestones(&env, &milestones, goal_amount)?; diff --git a/campaign/src/types.rs b/campaign/src/types.rs index ea5c1e9..3d54079 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -103,7 +103,7 @@ pub struct CampaignData { pub raised_amount: i128, pub end_time: u64, pub status: CampaignStatus, - pub accepted_assets: Vec, + pub accepted_assets: Vec, pub milestone_count: u32, } From 2cf5569b73192406fc387ed7c599a33e777b8463 Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:23:13 +0000 Subject: [PATCH 6/9] implemented the assetinfo --- campaign/src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index 5dfe652..d4a33d8 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -79,6 +79,18 @@ impl CampaignContract { } } +/// Helper function to validate Stellar assets +/// Ensures each asset has a non-empty asset_code +fn validate_assets(env: &Env, assets: &Vec) -> Result<(), Error> { + for asset in assets.iter() { + // asset_code must be non-empty + if asset.asset_code.len() == 0 { + panic_with_error(env, Error::InvalidAssetCode); + } + } + Ok(()) +} + /// Helper function to validate milestone conditions fn validate_milestones( env: &Env, @@ -113,6 +125,7 @@ fn panic_with_error(env: &Env, error: Error) -> ! { Error::InvalidGoalAmount => "InvalidGoalAmount", Error::InvalidEndTime => "InvalidEndTime", Error::InvalidAssets => "InvalidAssets", + Error::InvalidAssetCode => "InvalidAssetCode", Error::InvalidMilestones => "InvalidMilestones", Error::MilestoneMismatch => "MilestoneMismatch", Error::InvalidCampaignTransition => "InvalidCampaignTransition", From 6b6ed7df4a2b0016eb9f3e40120b8127c690786b Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:24:21 +0000 Subject: [PATCH 7/9] implemented the initialized --- campaign/src/types.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/campaign/src/types.rs b/campaign/src/types.rs index 3d54079..601cb08 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -13,6 +13,9 @@ pub enum Error { InvalidAssetCode, // asset_code must be non-empty and valid InvalidMilestones, // milestones must be sorted ascending and last must equal goal MilestoneMismatch, // last milestone.target_amount != goal_amount + InvalidMilestoneCount, // milestone count must be 1-5 + AlreadyInitialized, // campaign already initialized + UnauthorizedCreator, // caller is not the creator or lacks authorization // ── State transition errors ── InvalidCampaignTransition, // campaign status transition not allowed From 4edb490e0527ec7e5f3eab9dcdfab42d371f69c2 Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:24:36 +0000 Subject: [PATCH 8/9] implemented the initialized --- campaign/src/lib.rs | 2 +- campaign/src/types.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index d4a33d8..2b2a615 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -4,7 +4,7 @@ pub mod storage; pub mod types; use soroban_sdk::{contract, contractimpl, Env, Vec}; -use types::{CampaignData, CampaignStatus, Error, MilestoneData, MilestoneStatus, StellarAsset}; +use types::{CampaignData, CampaignStatus, Error, MilestoneData, MilestoneStatus, StellarAsset, CampaignEvent}; use storage::{get_campaign, set_campaign, set_milestone}; #[contract] diff --git a/campaign/src/types.rs b/campaign/src/types.rs index 601cb08..52c3c80 100644 --- a/campaign/src/types.rs +++ b/campaign/src/types.rs @@ -55,6 +55,21 @@ pub enum MilestoneStatus { Released, // Funds released to beneficiary } +// ── Contract events ────────────────────────────────────────────────────────── + +/// Campaign lifecycle events +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum CampaignEvent { + Initialized { + creator: Address, + goal_amount: i128, + end_time: u64, + asset_count: u32, + milestone_count: u32, + }, +} + /// Reusable struct for Stellar asset representation /// Enables consistent multi-asset support across the contract #[contracttype] From 02538cf277b2c0b4515a5122c89f191b52cb7f5a Mon Sep 17 00:00:00 2001 From: nafsonig Date: Thu, 28 May 2026 23:25:49 +0000 Subject: [PATCH 9/9] implemented the initialized --- campaign/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/campaign/src/lib.rs b/campaign/src/lib.rs index 2b2a615..6edf275 100644 --- a/campaign/src/lib.rs +++ b/campaign/src/lib.rs @@ -14,13 +14,22 @@ pub struct CampaignContract; impl CampaignContract { /// Initialize a new campaign with strict validation on all inputs. /// + /// Requires: Creator authorization via `creator.require_auth()` + /// Can only be called once per contract instance + /// /// # Panics + /// - `Error::UnauthorizedCreator` if caller is not the creator or lacks authorization + /// - `Error::AlreadyInitialized` if campaign already exists /// - `Error::InvalidGoalAmount` if goal_amount <= 0 /// - `Error::InvalidEndTime` if end_time <= current ledger timestamp /// - `Error::InvalidAssets` if accepted_assets is empty /// - `Error::InvalidAssetCode` if any asset_code is empty or invalid + /// - `Error::InvalidMilestoneCount` if milestone count is not 1-5 /// - `Error::InvalidMilestones` if milestones are not sorted ascending by target_amount /// - `Error::MilestoneMismatch` if last milestone.target_amount != goal_amount + /// + /// # Events + /// Emits `campaign_initialized` event with campaign details pub fn initialize( env: Env, creator: soroban_sdk::Address, @@ -29,6 +38,14 @@ impl CampaignContract { accepted_assets: Vec, milestones: Vec, ) -> Result<(), Error> { + // Authorization check: creator must authorize this call + creator.require_auth(); + + // Check if already initialized - can only initialize once + if get_campaign(&env).is_some() { + panic_with_error(&env, Error::AlreadyInitialized); + } + // Validation 1: goal_amount > 0 if goal_amount <= 0 { panic_with_error(&env, Error::InvalidGoalAmount); @@ -48,20 +65,24 @@ impl CampaignContract { // Validation 3b: validate each asset code validate_assets(&env, &accepted_assets)?; - // Validation 4 & 5: milestones sorted ascending and last == goal_amount - if !milestones.is_empty() { - validate_milestones(&env, &milestones, goal_amount)?; + // Validation 4: milestone count must be 1-5 + let milestone_count = milestones.len() as u32; + if milestone_count == 0 || milestone_count > types::MAX_MILESTONES { + panic_with_error(&env, Error::InvalidMilestoneCount); } + // Validation 5 & 6: milestones sorted ascending and last == goal_amount + validate_milestones(&env, &milestones, goal_amount)?; + // All validations passed, store campaign data let campaign = CampaignData { - creator, + creator: creator.clone(), goal_amount, raised_amount: 0, end_time, status: CampaignStatus::Active, - accepted_assets, - milestone_count: milestones.len() as u32, + accepted_assets: accepted_assets.clone(), + milestone_count, }; set_campaign(&env, &campaign); @@ -71,6 +92,16 @@ impl CampaignContract { set_milestone(&env, index as u32, milestone); } + // Emit campaign_initialized event + let event = CampaignEvent::Initialized { + creator, + goal_amount, + end_time, + asset_count: accepted_assets.len() as u32, + milestone_count, + }; + env.events().publish(("campaign", "initialized"), event); + Ok(()) } @@ -128,6 +159,9 @@ fn panic_with_error(env: &Env, error: Error) -> ! { Error::InvalidAssetCode => "InvalidAssetCode", Error::InvalidMilestones => "InvalidMilestones", Error::MilestoneMismatch => "MilestoneMismatch", + Error::InvalidMilestoneCount => "InvalidMilestoneCount", + Error::AlreadyInitialized => "AlreadyInitialized", + Error::UnauthorizedCreator => "UnauthorizedCreator", Error::InvalidCampaignTransition => "InvalidCampaignTransition", Error::InvalidMilestoneTransition => "InvalidMilestoneTransition", Error::CampaignNotActive => "CampaignNotActive",