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
19 changes: 19 additions & 0 deletions contract/contract/src/base/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -260,6 +278,7 @@ pub enum StorageKey {
Contribution(BytesN<32>, Address),
PoolContribution(u64, Address),
PoolContributors(u64),
EventMetrics(u64),

NextPoolId,
IsPaused,
Expand Down
34 changes: 32 additions & 2 deletions contract/contract/src/crowdfunding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};
Expand Down Expand Up @@ -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<EventMetrics, CrowdfundingError> {
// 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()
Expand Down
8 changes: 6 additions & 2 deletions contract/contract/src/interfaces/crowdfunding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -201,6 +201,10 @@ pub trait CrowdfundingTrait {

fn get_platform_fee_bps(env: Env) -> Result<u32, CrowdfundingError>;

fn get_event_metrics(env: Env, pool_id: u64) -> Result<EventMetrics, CrowdfundingError>;

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`.
///
Expand Down
46 changes: 46 additions & 0 deletions contract/contract/test/buy_ticket_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}
Loading