diff --git a/contracts/boundless/src/datatypes.rs b/contracts/boundless/src/datatypes.rs index 43a0593..942c16d 100644 --- a/contracts/boundless/src/datatypes.rs +++ b/contracts/boundless/src/datatypes.rs @@ -181,6 +181,10 @@ pub enum BoundlessError { EscrowAlreadyLinked = 14, /// Escrow contract not found EscrowNotFound = 15, + /// Campaign not found + CampaignAlreadyCompleted = 16, + /// Operation not allowed in current campaign state + InvalidCampaignState = 17, } // Events #[contractevent] @@ -202,3 +206,10 @@ pub struct CampaignStatusUpdated { pub status: Status, pub admin: Address, } + +#[contractevent] +pub struct CampaignCompleted { + pub campaign_id: u64, + pub completed_by: Address, + pub completed_at: u64, +} diff --git a/contracts/boundless/src/helper.rs b/contracts/boundless/src/helper.rs new file mode 100644 index 0000000..68656ce --- /dev/null +++ b/contracts/boundless/src/helper.rs @@ -0,0 +1,24 @@ +use crate::datatypes::{BoundlessError, CampaignCompleted, DataKey}; +use soroban_sdk::{Env, Address}; + +// Helper function to check platform admin +pub fn is_platform_admin(env: &Env, address: &Address) -> Result { + let admin: Address = env + .storage() + .persistent() + .get(&DataKey::Admin) + .ok_or(BoundlessError::Unauthorized)?; + + Ok(&admin == address) +} + +// Helper function to emit event +pub fn emit_campaign_completed_event(env: &Env, campaign_id: u64, completed_by: Address) { + let event_data = CampaignCompleted { + campaign_id, + completed_by, + completed_at: env.ledger().timestamp(), + }; + + event_data.publish(env); +} diff --git a/contracts/boundless/src/lib.rs b/contracts/boundless/src/lib.rs index 962352d..6dc1102 100644 --- a/contracts/boundless/src/lib.rs +++ b/contracts/boundless/src/lib.rs @@ -5,6 +5,7 @@ use soroban_sdk::contract; mod datatypes; mod interface; mod logic; +mod helper; pub use logic::*; diff --git a/contracts/boundless/src/logic/campaign.rs b/contracts/boundless/src/logic/campaign.rs index 67b52c6..de8d6a2 100644 --- a/contracts/boundless/src/logic/campaign.rs +++ b/contracts/boundless/src/logic/campaign.rs @@ -1,7 +1,8 @@ use crate::datatypes::{ Backer, BoundlessError, Campaign, CampaignCancelled, CampaignFunded, CampaignStatusUpdated, - Milestone, Status, + Milestone, Status, DataKey }; +use crate::helper::{emit_campaign_completed_event, is_platform_admin}; use crate::interface::{CampaignManagement, ContractManagement}; use crate::{BoundlessContract, BoundlessContractArgs, BoundlessContractClient}; use soroban_sdk::{contractimpl, Address, Env, Symbol, Vec}; @@ -80,12 +81,41 @@ impl CampaignManagement for BoundlessContract { } fn complete_campaign(env: Env, campaign_id: u64, admin: Address) -> Result<(), BoundlessError> { - // TODO: complete campaign logic - // - Verify admin authorization - // - Get campaign from storage - // - Update status to Completed - // - Store updated campaign - // - Emit completion event + // Verify admin authorization + admin.require_auth(); + + // Get campaign from storage + let mut campaign: Campaign = env + .storage() + .persistent() + .get(&DataKey::Campaign(campaign_id)) + .ok_or(BoundlessError::CampaignNotFound)?; + + // Check campaign status + match campaign.status { + Status::Completed => return Err(BoundlessError::CampaignAlreadyCompleted), + Status::Pending | Status::Active => { + // Proceed for active campaigns + }, + _ => return Err(BoundlessError::InvalidCampaignState), + } + + // Verify admin has permission + if campaign.owner != admin && !is_platform_admin(&env, &admin)? { + return Err(BoundlessError::Unauthorized); + } + + // Update status to Completed + campaign.status = Status::Completed; + + // Store updated campaign + env.storage() + .persistent() + .set(&DataKey::Campaign(campaign_id), &campaign); + + // Emit completion event + emit_campaign_completed_event(&env, campaign_id, admin); + Ok(()) }