diff --git a/contract/contract/src/base/types.rs b/contract/contract/src/base/types.rs index 6a163f2..ae4947c 100644 --- a/contract/contract/src/base/types.rs +++ b/contract/contract/src/base/types.rs @@ -248,6 +248,24 @@ pub struct PoolContribution { pub asset: Address, } +#[contracttype] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EventMetrics { + pub tickets_sold: u32, +} + +impl Default for EventMetrics { + fn default() -> Self { + Self::new() + } +} + +impl EventMetrics { + pub fn new() -> Self { + Self { tickets_sold: 0 } + } +} + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum StorageKey { @@ -260,6 +278,7 @@ pub enum StorageKey { Contribution(BytesN<32>, Address), PoolContribution(u64, Address), PoolContributors(u64), + EventMetrics(u64), NextPoolId, IsPaused, diff --git a/contract/contract/src/crowdfunding.rs b/contract/contract/src/crowdfunding.rs index ac13dc0..f713003 100644 --- a/contract/contract/src/crowdfunding.rs +++ b/contract/contract/src/crowdfunding.rs @@ -9,8 +9,8 @@ use crate::base::{ }, types::{ CampaignDetails, CampaignLifecycleStatus, CampaignMetrics, Contribution, - EmergencyWithdrawal, MultiSigConfig, PoolConfig, PoolContribution, PoolMetadata, - PoolMetrics, PoolState, StorageKey, MAX_DESCRIPTION_LENGTH, MAX_HASH_LENGTH, + EmergencyWithdrawal, EventMetrics, MultiSigConfig, PoolConfig, PoolContribution, + PoolMetadata, PoolMetrics, PoolState, StorageKey, MAX_DESCRIPTION_LENGTH, MAX_HASH_LENGTH, MAX_STRING_LENGTH, MAX_URL_LENGTH, }, }; @@ -336,11 +336,41 @@ impl CrowdfundingTrait for CrowdfundingContract { let user_ticket_key = StorageKey::UserTicket(pool_id, buyer.clone()); env.storage().instance().set(&user_ticket_key, &true); + // Update event metrics + let metrics_key = StorageKey::EventMetrics(pool_id); + let mut metrics: EventMetrics = env + .storage() + .instance() + .get(&metrics_key) + .unwrap_or(EventMetrics::new()); + metrics.tickets_sold += 1; + env.storage().instance().set(&metrics_key, &metrics); + events::ticket_sold(&env, pool_id, buyer, price, event_amount, fee_amount); Ok((event_amount, fee_amount)) } + fn get_event_metrics(env: Env, pool_id: u64) -> Result { + // Ensure pool exists + let pool_key = StorageKey::Pool(pool_id); + if !env.storage().instance().has(&pool_key) { + return Err(CrowdfundingError::PoolNotFound); + } + + let metrics_key = StorageKey::EventMetrics(pool_id); + Ok(env + .storage() + .instance() + .get(&metrics_key) + .unwrap_or_else(EventMetrics::new)) + } + + fn is_ticket_buyer(env: Env, pool_id: u64, buyer: Address) -> bool { + let user_ticket_key = StorageKey::UserTicket(pool_id, buyer); + env.storage().instance().get(&user_ticket_key).unwrap_or(false) + } + fn get_global_raised_total(env: Env) -> i128 { env.storage() .instance() diff --git a/contract/contract/src/interfaces/crowdfunding.rs b/contract/contract/src/interfaces/crowdfunding.rs index 5d21f8d..4aecac1 100644 --- a/contract/contract/src/interfaces/crowdfunding.rs +++ b/contract/contract/src/interfaces/crowdfunding.rs @@ -3,8 +3,8 @@ use soroban_sdk::{Address, BytesN, Env, String, Vec}; use crate::base::{ errors::CrowdfundingError, types::{ - CampaignDetails, CampaignLifecycleStatus, PoolConfig, PoolContribution, PoolMetadata, - PoolState, + CampaignDetails, CampaignLifecycleStatus, EventMetrics, PoolConfig, PoolContribution, + PoolMetadata, PoolState, }, }; @@ -201,6 +201,10 @@ pub trait CrowdfundingTrait { fn get_platform_fee_bps(env: Env) -> Result; + fn get_event_metrics(env: Env, pool_id: u64) -> Result; + + fn is_ticket_buyer(env: Env, pool_id: u64, buyer: Address) -> bool; + /// Purchase a ticket for a pool, splitting the payment between the event /// pool and the platform fee pool using the current `PlatformFeeBps`. /// diff --git a/contract/contract/test/buy_ticket_test.rs b/contract/contract/test/buy_ticket_test.rs index 13b1827..b0883c9 100644 --- a/contract/contract/test/buy_ticket_test.rs +++ b/contract/contract/test/buy_ticket_test.rs @@ -239,3 +239,49 @@ fn test_buy_ticket_requires_buyer_auth() { "buyer auth must be recorded" ); } + +#[test] +fn test_buy_ticket_updates_metrics() { + let env = Env::default(); + let (client, _, token) = setup(&env); + let pool_id = create_pool(&client, &env, &token); + + let price = 10_000i128; + + // Initial metrics + let initial_metrics = client.get_event_metrics(&pool_id); + assert_eq!(initial_metrics.tickets_sold, 0); + + // Buy first ticket + mint_and_buy(&env, &client, &token, pool_id, price); + let metrics = client.get_event_metrics(&pool_id); + assert_eq!(metrics.tickets_sold, 1); + + // Buy second ticket + mint_and_buy(&env, &client, &token, pool_id, price); + let metrics = client.get_event_metrics(&pool_id); + assert_eq!(metrics.tickets_sold, 2); +} + +#[test] +fn test_buy_ticket_records_user_ticket() { + let env = Env::default(); + let (client, _, token) = setup(&env); + let pool_id = create_pool(&client, &env, &token); + + let price = 10_000i128; + let (buyer, _) = mint_and_buy(&env, &client, &token, pool_id, price); + + // Verify it's recorded + assert!( + client.is_ticket_buyer(&pool_id, &buyer), + "buyer must be recorded as having a ticket" + ); + + // Verify another random user is NOT recorded + let other = Address::generate(&env); + assert!( + !client.is_ticket_buyer(&pool_id, &other), + "other user must not be recorded as having a ticket" + ); +}