From 5633721d1f954c7beec2257fafb26d9835ad4fa8 Mon Sep 17 00:00:00 2001 From: AJtheManager Date: Thu, 18 Jun 2026 08:29:54 +0100 Subject: [PATCH] implement Prize pool escrow + configurable top-N reward split --- contracts/creator-event-manager/src/event.rs | 106 +++++++- contracts/creator-event-manager/src/lib.rs | 41 +++ .../src/storage_types.rs | 25 +- contracts/creator-event-manager/src/views.rs | 18 ++ .../tests/data_structures_test.rs | 6 +- .../tests/event_tests.rs | 251 +++++++++++++++++- .../tests/fee_views_tests.rs | 6 +- .../tests/get_match_predictions_tests.rs | 4 +- .../tests/leaderboard_tests.rs | 2 + .../tests/match_tests.rs | 4 +- .../tests/oracle_tests.rs | 6 +- .../tests/prediction_tests.rs | 10 +- .../tests/storage_types_tests.rs | 10 +- .../submit_match_result_contract_tests.rs | 4 +- .../tests/views_tests.rs | 28 +- 15 files changed, 501 insertions(+), 20 deletions(-) diff --git a/contracts/creator-event-manager/src/event.rs b/contracts/creator-event-manager/src/event.rs index 3d206dbf..185312ae 100644 --- a/contracts/creator-event-manager/src/event.rs +++ b/contracts/creator-event-manager/src/event.rs @@ -4,7 +4,8 @@ use crate::admin; use crate::invite::{self, InviteError}; use crate::storage::{self, TTL_LEDGERS}; use crate::storage_types::{ - DataKey, Event, MAX_DESCRIPTION_LEN, MAX_EVENT_DURATION_SECONDS, MAX_TITLE_LEN, + DataKey, Event, MAX_DESCRIPTION_LEN, MAX_EVENT_DURATION_SECONDS, MAX_REWARD_RANKS, MAX_TITLE_LEN, + REWARD_PERCENT_TOTAL, }; // --------------------------------------------------------------------------- @@ -38,6 +39,12 @@ pub enum EventError { EventStartInPast = 11, /// (end_time - start_time) exceeds MAX_EVENT_DURATION_SECONDS EventDurationTooLong = 12, + /// prize_pool < 0 + InvalidPrizePool = 13, + /// reward_distribution is malformed (see `validate_reward_distribution`). + InvalidRewardDistribution = 14, + /// Creator's XLM balance is below the requested prize_pool. + InsufficientPrizePoolFunds = 15, } impl From for EventError { @@ -48,6 +55,53 @@ impl From for EventError { } } +// --------------------------------------------------------------------------- +// Prize pool validation +// --------------------------------------------------------------------------- + +/// Validate a prize pool and its reward distribution. +/// +/// Rules: +/// * `prize_pool` must be `>= 0` ([`EventError::InvalidPrizePool`]). +/// * When `prize_pool > 0`: +/// * `reward_distribution` must be non-empty, +/// * have at most [`MAX_REWARD_RANKS`] entries, +/// * every entry must be in `1..=REWARD_PERCENT_TOTAL`, +/// * and the entries must sum to exactly [`REWARD_PERCENT_TOTAL`]. +/// * When `prize_pool == 0` (a "fun event"), `reward_distribution` must be empty. +fn validate_prize_pool(prize_pool: i128, reward_distribution: &Vec) -> Result<(), EventError> { + if prize_pool < 0 { + return Err(EventError::InvalidPrizePool); + } + + if prize_pool == 0 { + // Fun event: no payouts, so no distribution may be specified. + if !reward_distribution.is_empty() { + return Err(EventError::InvalidRewardDistribution); + } + return Ok(()); + } + + // prize_pool > 0 from here on. + if reward_distribution.is_empty() || reward_distribution.len() > MAX_REWARD_RANKS { + return Err(EventError::InvalidRewardDistribution); + } + + let mut sum: u32 = 0; + for percent in reward_distribution.iter() { + if percent == 0 || percent > REWARD_PERCENT_TOTAL { + return Err(EventError::InvalidRewardDistribution); + } + sum += percent; + } + + if sum != REWARD_PERCENT_TOTAL { + return Err(EventError::InvalidRewardDistribution); + } + + Ok(()) +} + // --------------------------------------------------------------------------- // create_event (#794) // --------------------------------------------------------------------------- @@ -61,14 +115,18 @@ impl From for EventError { /// 4. Validate `max_participants > 0`. /// 5. Validate time range: `start_time < end_time`, `start_time >= current_time`, /// and duration `<= MAX_EVENT_DURATION_SECONDS`. -/// 6. Check creator has sufficient XLM balance for the creation fee. -/// 7. Transfer the fee from creator to treasury. -/// 8. Assign a new `event_id` via the global counter. -/// 9. Generate a unique 8-character invite code. -/// 10. Persist the `Event`, empty participant list, empty match list, and the +/// 6. Validate the prize pool and reward distribution. +/// 7. Check creator has sufficient XLM balance for the creation fee. +/// 8. Transfer the fee from creator to treasury. +/// 9. If `prize_pool > 0`, escrow the prize pool from creator into the contract +/// address (a separate transfer from the creation fee → treasury transfer). +/// 10. Assign a new `event_id` via the global counter. +/// 11. Generate a unique 8-character invite code. +/// 12. Persist the `Event`, empty participant list, empty match list, and the /// invite-code → event_id reverse index. -/// 11. Emit an `EventCreated` event. -/// 12. Return `(event_id, invite_code)`. +/// 13. Emit an `EventCreated` event, plus a `prize_pool_funded` event when the +/// event is funded. +/// 14. Return `(event_id, invite_code)`. pub fn create_event( env: &Env, creator: Address, @@ -77,6 +135,8 @@ pub fn create_event( max_participants: u32, start_time: u64, end_time: u64, + prize_pool: i128, + reward_distribution: Vec, ) -> Result<(u64, Symbol), EventError> { creator.require_auth(); @@ -114,6 +174,9 @@ pub fn create_event( return Err(EventError::EventDurationTooLong); } + // Validate the prize pool and its reward distribution. + validate_prize_pool(prize_pool, &reward_distribution)?; + let fee = admin::get_creation_fee(env).unwrap_or_else(|| panic!("not_initialized")); let treasury = admin::get_treasury(env).unwrap_or_else(|| panic!("not_initialized")); let xlm_token = admin::get_xlm_token(env).unwrap_or_else(|| panic!("not_initialized")); @@ -124,9 +187,21 @@ pub fn create_event( return Err(EventError::InsufficientFee); } - // Transfer creation fee from creator to treasury. + // The creator must be able to cover the prize pool on top of the creation + // fee. Check this before either transfer so we never move only the fee. + if prize_pool > 0 && token_client.balance(&creator) < fee + prize_pool { + return Err(EventError::InsufficientPrizePoolFunds); + } + + // Transfer creation fee from creator to treasury (platform anti-spam fee). token_client.transfer(&creator, &treasury, &fee); + // Escrow the prize pool from creator into the contract address. This is a + // distinct transfer from the creation-fee → treasury transfer above. + if prize_pool > 0 { + token_client.transfer(&creator, &env.current_contract_address(), &prize_pool); + } + let event_id = storage::next_event_id(env); let invite_code = invite::generate_invite_code(env).map_err(EventError::from)?; @@ -141,6 +216,8 @@ pub fn create_event( end_time, invite_code.clone(), max_participants, + prize_pool, + reward_distribution.clone(), ); storage::set_event(env, event_id, &event); @@ -174,6 +251,17 @@ pub fn create_event( (event_id, creator, invite_code.clone()), ); + // Announce the escrowed prize pool so off-chain indexers can track funding. + if prize_pool > 0 { + env.events().publish( + ( + Symbol::new(env, "event"), + Symbol::new(env, "prize_pool_funded"), + ), + (event_id, prize_pool, reward_distribution), + ); + } + Ok((event_id, invite_code)) } diff --git a/contracts/creator-event-manager/src/lib.rs b/contracts/creator-event-manager/src/lib.rs index baa5725c..8c363723 100644 --- a/contracts/creator-event-manager/src/lib.rs +++ b/contracts/creator-event-manager/src/lib.rs @@ -257,6 +257,12 @@ impl CreatorEventManagerContract { /// * `"event_duration_too_long"` — duration exceeds MAX_EVENT_DURATION_SECONDS. /// * `"insufficient_fee"` — creator's XLM balance is below the creation fee. /// * `"code_generation_failed"` — could not generate a unique invite code. + /// * `"invalid_prize_pool"` — `prize_pool` is negative. + /// * `"invalid_reward_distribution"` — distribution is malformed (empty when + /// funded, too many ranks, a zero/over-100 entry, a non-100 sum, or a + /// non-empty distribution on an unfunded event). + /// * `"insufficient_prize_pool_funds"` — creator cannot cover + /// `creation_fee + prize_pool`. pub fn create_event( env: Env, creator: Address, @@ -265,6 +271,8 @@ impl CreatorEventManagerContract { max_participants: u32, start_time: u64, end_time: u64, + prize_pool: i128, + reward_distribution: Vec, ) -> (u64, Symbol) { match event::create_event( &env, @@ -274,6 +282,8 @@ impl CreatorEventManagerContract { max_participants, start_time, end_time, + prize_pool, + reward_distribution, ) { Ok(result) => result, Err(EventError::Paused) => panic!("contract_paused"), @@ -286,6 +296,9 @@ impl CreatorEventManagerContract { Err(EventError::InsufficientFee) => panic!("insufficient_fee"), Err(EventError::TransferFailed) => panic!("transfer_failed"), Err(EventError::CodeGenerationFailed) => panic!("code_generation_failed"), + Err(EventError::InvalidPrizePool) => panic!("invalid_prize_pool"), + Err(EventError::InvalidRewardDistribution) => panic!("invalid_reward_distribution"), + Err(EventError::InsufficientPrizePoolFunds) => panic!("insufficient_prize_pool_funds"), Err(_) => panic!("unexpected_error"), } } @@ -318,6 +331,34 @@ impl CreatorEventManagerContract { } } + /// Return the escrowed prize pool (in stroops) for an event. + /// + /// Returns `0` for a "fun event" with no payouts. + /// + /// # Panics + /// * `"event_not_found"` — no event exists with the given ID. + pub fn get_event_prize_pool(env: Env, event_id: u64) -> i128 { + match views::get_event_prize_pool(&env, event_id) { + Ok(prize_pool) => prize_pool, + Err(EventError::EventNotFound) => panic!("event_not_found"), + Err(_) => panic!("unexpected_error"), + } + } + + /// Return the reward distribution percentages for an event. + /// + /// The vector is empty for a "fun event" with no payouts. + /// + /// # Panics + /// * `"event_not_found"` — no event exists with the given ID. + pub fn get_event_reward_distribution(env: Env, event_id: u64) -> Vec { + match views::get_event_reward_distribution(&env, event_id) { + Ok(distribution) => distribution, + Err(EventError::EventNotFound) => panic!("event_not_found"), + Err(_) => panic!("unexpected_error"), + } + } + /// Return all participant addresses for an event. /// /// Reads the `EventParticipants(event_id)` storage index after validating diff --git a/contracts/creator-event-manager/src/storage_types.rs b/contracts/creator-event-manager/src/storage_types.rs index 458156e6..55a12fee 100644 --- a/contracts/creator-event-manager/src/storage_types.rs +++ b/contracts/creator-event-manager/src/storage_types.rs @@ -1,9 +1,14 @@ -use soroban_sdk::{contracttype, Address, String, Symbol}; +use soroban_sdk::{contracttype, Address, String, Symbol, Vec}; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- +/// Maximum number of leaderboard ranks that can receive a prize pool payout. +pub const MAX_REWARD_RANKS: u32 = 5; +/// The reward distribution percentages must sum to exactly this value. +pub const REWARD_PERCENT_TOTAL: u32 = 100; + /// Maximum length for event title (characters) pub const MAX_TITLE_LEN: u32 = 200; /// Maximum length for event description (characters) @@ -224,6 +229,19 @@ pub struct Event { /// Number of matches that belong to this event pub match_count: u32, + + /// XLM prize pool (in stroops) escrowed in the contract for winners. + /// `0` means this is a "fun event" with no payouts. + pub prize_pool: i128, + + /// Percentage of the prize pool awarded to each leaderboard rank, in + /// 1-based rank order (index 0 → rank 1). Each entry is 1–100 and the + /// entries sum to `REWARD_PERCENT_TOTAL` when `prize_pool > 0`; the vector + /// is empty when `prize_pool == 0`. + pub reward_distribution: Vec, + + /// Whether the prize pool has been distributed / the event closed out. + pub is_finalized: bool, } impl Event { @@ -240,6 +258,8 @@ impl Event { end_time: u64, invite_code: Symbol, max_participants: u32, + prize_pool: i128, + reward_distribution: Vec, ) -> Self { Self { event_id, @@ -256,6 +276,9 @@ impl Event { max_participants, participant_count: 0, match_count: 0, + prize_pool, + reward_distribution, + is_finalized: false, } } diff --git a/contracts/creator-event-manager/src/views.rs b/contracts/creator-event-manager/src/views.rs index 77581beb..8b075f95 100644 --- a/contracts/creator-event-manager/src/views.rs +++ b/contracts/creator-event-manager/src/views.rs @@ -51,6 +51,24 @@ pub fn get_event_participants(env: &Env, event_id: u64) -> Result, Ok(storage::get_event_participants(env, event_id)) } +/// Return the escrowed prize pool (in stroops) for an existing event. +/// +/// Validates that `event_id` exists, then returns the stored `prize_pool`. +/// A "fun event" (no payouts) returns `0`. +pub fn get_event_prize_pool(env: &Env, event_id: u64) -> Result { + let event = event::get_event(env, event_id)?; + Ok(event.prize_pool) +} + +/// Return the reward distribution percentages for an existing event. +/// +/// Validates that `event_id` exists, then returns the stored +/// `reward_distribution`. The vector is empty for a "fun event". +pub fn get_event_reward_distribution(env: &Env, event_id: u64) -> Result, EventError> { + let event = event::get_event(env, event_id)?; + Ok(event.reward_distribution) +} + /// Build aggregate statistics for an existing event. /// /// The function first retrieves the event to validate that `event_id` exists, diff --git a/contracts/creator-event-manager/tests/data_structures_test.rs b/contracts/creator-event-manager/tests/data_structures_test.rs index 53075660..d8bc31fe 100644 --- a/contracts/creator-event-manager/tests/data_structures_test.rs +++ b/contracts/creator-event-manager/tests/data_structures_test.rs @@ -6,7 +6,7 @@ use creator_event_manager::storage_types::{ Event, Match, MatchResult, Prediction, MAX_TEAM_NAME_LEN, OUTCOME_DRAW, OUTCOME_TEAM_A, OUTCOME_TEAM_B, }; -use soroban_sdk::{testutils::Address as _, Address, Env, String, Symbol}; +use soroban_sdk::{testutils::Address as _, Address, Env, String, Symbol, Vec}; // ============================================================================= // Helpers @@ -24,6 +24,8 @@ fn make_event(env: &Env, event_id: u64) -> Event { 1_640_995_200u64 + 86400, // end_time (24 hours later) Symbol::new(env, "ABCD1234"), 100u32, + 0i128, + Vec::new(env), ) } @@ -131,6 +133,8 @@ fn test_event_add_participant_rejects_when_full() { 2000u64, // end_time Symbol::new(&env, "LIMIT1"), 1u32, + 0i128, + Vec::new(&env), ); assert!(event.add_participant().is_ok()); assert_eq!( diff --git a/contracts/creator-event-manager/tests/event_tests.rs b/contracts/creator-event-manager/tests/event_tests.rs index bcaa0bf8..708dbc5e 100644 --- a/contracts/creator-event-manager/tests/event_tests.rs +++ b/contracts/creator-event-manager/tests/event_tests.rs @@ -3,7 +3,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::testutils::Ledger; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String, Symbol}; +use soroban_sdk::{Address, Env, String, Symbol, Vec}; const FEE: i128 = 1_000_000; @@ -78,6 +78,8 @@ fn test_create_event_success() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); assert_eq!(event_id, 1); } @@ -98,6 +100,8 @@ fn test_create_event_stores_correct_fields() { &10u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event(&event_id); @@ -130,6 +134,8 @@ fn test_create_event_fee_transferred_to_treasury() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); assert_eq!(token.balance(&treasury) - before, FEE); } @@ -152,6 +158,8 @@ fn test_create_event_fails_when_paused() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -172,6 +180,8 @@ fn test_create_event_fails_empty_title() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -192,6 +202,8 @@ fn test_create_event_fails_empty_description() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -212,6 +224,8 @@ fn test_create_event_fails_zero_max_participants() { &0u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -232,6 +246,8 @@ fn test_create_event_fails_insufficient_balance() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -251,6 +267,8 @@ fn test_get_event_returns_correct_data() { &7u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event(&event_id); assert_eq!(event.event_id, event_id); @@ -282,6 +300,8 @@ fn test_get_event_by_code_returns_correct_event() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event_by_code(&invite_code); assert_eq!(event.event_id, event_id); @@ -315,6 +335,8 @@ fn test_create_event_with_valid_duration() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event(&event_id); @@ -340,6 +362,8 @@ fn test_create_event_end_before_start_rejected() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -361,6 +385,8 @@ fn test_create_event_end_equals_start_rejected() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -383,6 +409,8 @@ fn test_create_event_start_in_past_rejected() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -404,6 +432,8 @@ fn test_create_event_duration_too_long_rejected() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); } @@ -423,6 +453,8 @@ fn test_event_has_ended_helper() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event(&event_id); @@ -452,6 +484,8 @@ fn test_event_is_within_window_helper() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let event = client.get_event(&event_id); @@ -470,3 +504,218 @@ fn test_event_is_within_window_helper() { // After end_time assert!(!event.is_within_window(end_time + 1)); } + +// ============================================================================ +// Prize pool escrow + reward distribution tests +// ============================================================================ + +const PRIZE_POOL: i128 = 5_000_000; + +/// A valid top-5 distribution summing to 100. +fn distribution(env: &Env) -> soroban_sdk::Vec { + soroban_sdk::vec![env, 40u32, 30u32, 20u32, 5u32, 5u32] +} + +#[test] +fn test_create_event_with_prize_pool_escrows_funds() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + // Creator must cover both the creation fee and the prize pool. + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + let token = soroban_sdk::token::Client::new(&env, &xlm_token); + let contract_before = token.balance(&client.address); + let creator_before = token.balance(&creator); + + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &distribution(&env), + ); + + // Contract escrows exactly the prize pool (the creation fee goes to treasury). + assert_eq!(token.balance(&client.address) - contract_before, PRIZE_POOL); + // Creator pays the creation fee plus the prize pool. + assert_eq!(creator_before - token.balance(&creator), FEE + PRIZE_POOL); +} + +#[test] +#[should_panic(expected = "invalid_reward_distribution")] +fn test_create_event_reward_distribution_must_sum_to_100() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + // 50 + 30 + 10 = 90, not 100. + let dist = soroban_sdk::vec![&env, 50u32, 30u32, 10u32]; + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &dist, + ); +} + +#[test] +#[should_panic(expected = "invalid_reward_distribution")] +fn test_create_event_too_many_reward_ranks() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + // 6 entries exceeds MAX_REWARD_RANKS (5), even though they sum to 100. + let dist = soroban_sdk::vec![&env, 20u32, 20u32, 20u32, 20u32, 10u32, 10u32]; + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &dist, + ); +} + +#[test] +#[should_panic(expected = "invalid_reward_distribution")] +fn test_create_event_zero_entry_in_distribution_rejected() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + // Sums to 100 but contains a zero entry. + let dist = soroban_sdk::vec![&env, 100u32, 0u32]; + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &dist, + ); +} + +#[test] +#[should_panic(expected = "insufficient_prize_pool_funds")] +fn test_create_event_insufficient_balance_for_prize_pool() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + // Enough for the fee, but one stroop short of the fee + prize pool. + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL - 1); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &distribution(&env), + ); +} + +#[test] +#[should_panic(expected = "invalid_reward_distribution")] +fn test_create_event_zero_prize_pool_requires_empty_distribution() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + // Fun event (prize_pool == 0) must not specify a distribution. + let dist = soroban_sdk::vec![&env, 50u32, 50u32]; + client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &0i128, + &dist, + ); +} + +#[test] +fn test_get_event_prize_pool_and_distribution_views() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE + PRIZE_POOL); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + let (event_id, _) = client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &PRIZE_POOL, + &distribution(&env), + ); + + assert_eq!(client.get_event_prize_pool(&event_id), PRIZE_POOL); + assert_eq!(client.get_event_reward_distribution(&event_id), distribution(&env)); + + // Fields are persisted on the event itself, not finalized at creation. + let event = client.get_event(&event_id); + assert_eq!(event.prize_pool, PRIZE_POOL); + assert_eq!(event.reward_distribution, distribution(&env)); + assert!(!event.is_finalized); +} + +#[test] +fn test_get_event_prize_pool_zero_for_fun_event() { + let (env, client, _admin, _treasury, xlm_token) = setup(); + let creator = Address::generate(&env); + fund(&env, &xlm_token, &creator, FEE); + + let start_time = get_future_time(&env, 3600); + let end_time = get_future_time(&env, 7200); + + let (event_id, _) = client.create_event( + &creator, + &title(&env), + &desc(&env), + &5u32, + &start_time, + &end_time, + &0i128, + &Vec::new(&env), + ); + + assert_eq!(client.get_event_prize_pool(&event_id), 0); + assert!(client.get_event_reward_distribution(&event_id).is_empty()); +} diff --git a/contracts/creator-event-manager/tests/fee_views_tests.rs b/contracts/creator-event-manager/tests/fee_views_tests.rs index 2f1913e3..88c95f9b 100644 --- a/contracts/creator-event-manager/tests/fee_views_tests.rs +++ b/contracts/creator-event-manager/tests/fee_views_tests.rs @@ -2,7 +2,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::token::Client as TokenClient; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String}; +use soroban_sdk::{Address, Env, String, Vec}; const FEE: i128 = 1_000_000; @@ -85,6 +85,8 @@ fn test_treasury_balance_and_withdraw_success() { &2u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); // Treasury address should now have the fee @@ -138,6 +140,8 @@ fn test_withdraw_non_admin_rejected() { &2u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let non_admin = Address::generate(&env); diff --git a/contracts/creator-event-manager/tests/get_match_predictions_tests.rs b/contracts/creator-event-manager/tests/get_match_predictions_tests.rs index a40873d2..aa02d771 100644 --- a/contracts/creator-event-manager/tests/get_match_predictions_tests.rs +++ b/contracts/creator-event-manager/tests/get_match_predictions_tests.rs @@ -9,7 +9,7 @@ use creator_event_manager::storage; use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String, Symbol}; +use soroban_sdk::{Address, Env, String, Symbol, Vec}; const FEE: i128 = 1_000_000; @@ -75,6 +75,8 @@ fn create_event_with_match( &10u32, &start_time, &end_time, + &0i128, + &Vec::new(env), ); let match_id = env.as_contract(contract_id, || { diff --git a/contracts/creator-event-manager/tests/leaderboard_tests.rs b/contracts/creator-event-manager/tests/leaderboard_tests.rs index dbeb9956..92e1717e 100644 --- a/contracts/creator-event-manager/tests/leaderboard_tests.rs +++ b/contracts/creator-event-manager/tests/leaderboard_tests.rs @@ -75,6 +75,8 @@ fn create_event_with_matches( &100u32, &start_time, &end_time, + &0i128, + &soroban_sdk::Vec::new(env), ); let mut match_ids: Vec = Vec::new(); diff --git a/contracts/creator-event-manager/tests/match_tests.rs b/contracts/creator-event-manager/tests/match_tests.rs index 4bb64b4c..7360d85d 100644 --- a/contracts/creator-event-manager/tests/match_tests.rs +++ b/contracts/creator-event-manager/tests/match_tests.rs @@ -5,7 +5,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::testutils::Ledger as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String}; +use soroban_sdk::{Address, Env, String, Vec}; const FEE: i128 = 1_000_000; @@ -67,6 +67,8 @@ fn create_event_default( &max_participants, &start_time, &end_time, + &0i128, + &Vec::new(env), ) } diff --git a/contracts/creator-event-manager/tests/oracle_tests.rs b/contracts/creator-event-manager/tests/oracle_tests.rs index 46179b55..21ba8175 100644 --- a/contracts/creator-event-manager/tests/oracle_tests.rs +++ b/contracts/creator-event-manager/tests/oracle_tests.rs @@ -5,7 +5,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::testutils::Ledger as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String, Symbol}; +use soroban_sdk::{Address, Env, String, Symbol, Vec}; const FEE: i128 = 1_000_000; @@ -67,6 +67,8 @@ fn create_event_with_match( &10u32, &start_time, &end_time, + &0i128, + &Vec::new(env), ); let match_id = env.as_contract(contract_id, || { @@ -192,6 +194,8 @@ fn test_get_user_score_calculation_accurate() { &10u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let (match_id_1, match_id_2) = env.as_contract(&contract_id, || { diff --git a/contracts/creator-event-manager/tests/prediction_tests.rs b/contracts/creator-event-manager/tests/prediction_tests.rs index ce195763..c77a5bb6 100644 --- a/contracts/creator-event-manager/tests/prediction_tests.rs +++ b/contracts/creator-event-manager/tests/prediction_tests.rs @@ -4,7 +4,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::testutils::Ledger as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String, Symbol}; +use soroban_sdk::{Address, Env, String, Symbol, Vec}; const FEE: i128 = 1_000_000; @@ -71,6 +71,8 @@ fn create_event_and_match( &max_participants, &start_time, &end_time, + &0i128, + &Vec::new(env), ); let match_id = env.as_contract(contract_id, || { @@ -321,6 +323,8 @@ fn test_get_user_predictions_returns_all_for_event() { &2u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let (match_id_1, match_id_2) = env.as_contract(&contract_id, || { @@ -396,6 +400,8 @@ fn test_get_user_predictions_sorted_by_predicted_at() { &2u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let (match_id_1, match_id_2) = env.as_contract(&contract_id, || { @@ -562,6 +568,8 @@ fn test_get_prediction_distribution_multiple_matches_independent() { &2u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let (match_id_1, match_id_2) = env.as_contract(&contract_id, || { diff --git a/contracts/creator-event-manager/tests/storage_types_tests.rs b/contracts/creator-event-manager/tests/storage_types_tests.rs index ff9b345e..9d31e3d5 100644 --- a/contracts/creator-event-manager/tests/storage_types_tests.rs +++ b/contracts/creator-event-manager/tests/storage_types_tests.rs @@ -4,7 +4,7 @@ use creator_event_manager::storage_types::{ Event, Match, MatchResult, Prediction, OUTCOME_DRAW, OUTCOME_TEAM_A, OUTCOME_TEAM_B, }; -use soroban_sdk::{testutils::Address as _, Address, Env, String, Symbol}; +use soroban_sdk::{testutils::Address as _, Address, Env, String, Symbol, Vec}; // --------------------------------------------------------------------------- // MatchResult @@ -51,6 +51,8 @@ fn make_event(env: &Env, event_id: u64) -> Event { 1_641_081_600u64, Symbol::new(env, "ABCD1234"), 100u32, + 0i128, + Vec::new(env), ) } @@ -77,6 +79,8 @@ fn test_event_creation() { 1_641_081_600u64, invite_code.clone(), 50u32, + 0i128, + Vec::new(&env), ); assert_eq!(event.event_id, 1); @@ -144,6 +148,8 @@ fn test_event_max_participants() { 7_200u64, Symbol::new(&env, "CAPCODE1"), 2u32, + 0i128, + Vec::new(&env), ); assert!(event.add_participant().is_ok()); @@ -165,6 +171,8 @@ fn test_event_unlimited_participants() { 7_200u64, Symbol::new(&env, "OPENCODE"), 0u32, // 0 = unlimited + 0i128, + Vec::new(&env), ); for _ in 0..10 { diff --git a/contracts/creator-event-manager/tests/submit_match_result_contract_tests.rs b/contracts/creator-event-manager/tests/submit_match_result_contract_tests.rs index bcc3f1ac..9d41963e 100644 --- a/contracts/creator-event-manager/tests/submit_match_result_contract_tests.rs +++ b/contracts/creator-event-manager/tests/submit_match_result_contract_tests.rs @@ -13,7 +13,7 @@ use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::testutils::Ledger as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String, Symbol}; +use soroban_sdk::{Address, Env, String, Symbol, Vec}; const FEE: i128 = 1_000_000; @@ -81,6 +81,8 @@ fn create_event_with_match( &10u32, &start_time, &end_time, + &0i128, + &Vec::new(env), ); let match_id = env.as_contract(contract_id, || { diff --git a/contracts/creator-event-manager/tests/views_tests.rs b/contracts/creator-event-manager/tests/views_tests.rs index 23429000..cc6f0469 100644 --- a/contracts/creator-event-manager/tests/views_tests.rs +++ b/contracts/creator-event-manager/tests/views_tests.rs @@ -4,7 +4,7 @@ use creator_event_manager::storage_types::{Match, MatchResult, Prediction}; use creator_event_manager::CreatorEventManagerContractClient; use soroban_sdk::testutils::Address as _; use soroban_sdk::token::StellarAssetClient; -use soroban_sdk::{Address, Env, String}; +use soroban_sdk::{Address, Env, String, Vec}; const FEE: i128 = 1_000_000; @@ -115,6 +115,8 @@ fn test_get_event_participants_returns_all_participants() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); client.join_event(&user_one, &invite_code); client.join_event(&user_two, &invite_code); @@ -143,6 +145,8 @@ fn test_get_event_participants_empty_for_new_event() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let participants = client.get_event_participants(&event_id); @@ -166,6 +170,8 @@ fn test_get_event_participants_updates_as_participants_join() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let initial_participants = client.get_event_participants(&event_id); @@ -207,6 +213,8 @@ fn test_event_statistics_are_accurate() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); client.join_event(&user_one, &invite_code); client.join_event(&user_two, &invite_code); @@ -243,6 +251,8 @@ fn test_event_statistics_completion_status() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); env.as_contract(&contract_id, || { @@ -303,6 +313,8 @@ fn test_get_platform_statistics_all_statistics_accurate() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); client.join_event(&user1, &invite_code_1); client.join_event(&user2, &invite_code_1); @@ -324,6 +336,8 @@ fn test_get_platform_statistics_all_statistics_accurate() { &5u32, &start_time2, &end_time2, + &0i128, + &Vec::new(&env), ); client.join_event(&user1, &invite_code_2); @@ -363,6 +377,8 @@ fn test_get_platform_statistics_counters_increment_correctly() { &5u32, &start_time, &end_time, + &0i128, + &Vec::new(&env), ); let after_event = client.get_platform_statistics(); @@ -407,6 +423,8 @@ fn test_get_platform_statistics_unique_participants_calculated() { &5u32, &start_time1, &end_time1, + &0i128, + &Vec::new(&env), ); client.join_event(&user1, &invite_code_1); client.join_event(&user2, &invite_code_1); @@ -422,6 +440,8 @@ fn test_get_platform_statistics_unique_participants_calculated() { &5u32, &start_time2, &end_time2, + &0i128, + &Vec::new(&env), ); client.join_event(&user1, &invite_code_2); @@ -459,6 +479,8 @@ fn test_get_platform_statistics_fees_accumulated() { &5u32, &start_time1, &end_time1, + &0i128, + &Vec::new(&env), ); fund(&env, &xlm_token, &creator2, FEE); @@ -471,6 +493,8 @@ fn test_get_platform_statistics_fees_accumulated() { &5u32, &start_time2, &end_time2, + &0i128, + &Vec::new(&env), ); fund(&env, &xlm_token, &creator3, FEE); @@ -483,6 +507,8 @@ fn test_get_platform_statistics_fees_accumulated() { &5u32, &start_time3, &end_time3, + &0i128, + &Vec::new(&env), ); let stats = client.get_platform_statistics();