From 93982bfa7ac9b7c4587809e966e88f5e755f7e3a Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Sun, 29 Mar 2026 00:05:13 +0100 Subject: [PATCH 1/4] feat(contract): standardize event name trimming (#326) - Add `name` field to `EventRegistrationArgs` and `EventInfo` structs - Implement `trim_string` helper to strip leading/trailing ASCII whitespace - Apply trimming to event name in `register_event` before persisting - Add `name` field to local `EventInfo` copy in ticket_payment contract - Add `test_register_event_name_trimming` test with intentionally messy names - Update all existing test fixtures to include the new `name` field --- contract/contracts/event_registry/src/lib.rs | 47 ++++++++ contract/contracts/event_registry/src/test.rs | 108 ++++++++++++++++++ .../contracts/event_registry/src/test_e2e.rs | 1 + .../contracts/event_registry/src/types.rs | 4 + .../test/test_complete_event_lifecycle.1.json | 16 +++ .../test/test_event_inactive_error.1.json | 16 +++ .../test/test_get_event_payment_info.1.json | 16 +++ ..._increment_inventory_inactive_event.1.json | 16 +++ ...ement_inventory_max_supply_exceeded.1.json | 16 +++ ...ent_inventory_persists_across_reads.1.json | 16 +++ .../test_increment_inventory_success.1.json | 16 +++ ...ncrement_inventory_unlimited_supply.1.json | 16 +++ .../test/test_multiple_tiers_inventory.1.json | 16 +++ .../test/test_organizer_events_list.1.json | 32 ++++++ ...test_register_duplicate_event_fails.1.json | 16 +++ .../test/test_register_event_success.1.json | 16 +++ ...est_register_event_unlimited_supply.1.json | 16 +++ .../test/test_storage_operations.1.json | 16 +++ .../test/test_tier_not_found.1.json | 16 +++ .../test/test_tier_supply_exceeded.1.json | 16 +++ .../test/test_update_event_status.1.json | 16 +++ .../test_update_metadata_invalid_cid.1.json | 16 +++ .../test/test_update_metadata_success.1.json | 16 +++ .../contracts/ticket_payment/src/contract.rs | 1 + contract/contracts/ticket_payment/src/test.rs | 23 ++++ .../contracts/ticket_payment/src/test_e2e.rs | 4 + 26 files changed, 508 insertions(+) diff --git a/contract/contracts/event_registry/src/lib.rs b/contract/contracts/event_registry/src/lib.rs index c6b8ce6..f45338c 100644 --- a/contract/contracts/event_registry/src/lib.rs +++ b/contract/contracts/event_registry/src/lib.rs @@ -277,8 +277,12 @@ impl EventRegistry { let platform_fee_percent = storage::get_platform_fee(&env); + // Sanitize: trim leading/trailing whitespace from the event name + let trimmed_name = trim_string(&env, &args.name); + let event_info = EventInfo { event_id: args.event_id.clone(), + name: trimmed_name, organizer_address: args.organizer_address.clone(), payment_address: args.payment_address.clone(), platform_fee_percent, @@ -1733,6 +1737,49 @@ fn is_zero_address(env: &Env, address: &Address) -> bool { address.to_string() == zero_account } +/// Trims leading and trailing ASCII whitespace from a Soroban `String`. +fn trim_string(env: &Env, s: &String) -> String { + let len = s.len(); + if len == 0 { + return s.clone(); + } + + // Copy string bytes into a stack buffer for manipulation + let mut buf = [0u8; 256]; + let len_usize = len as usize; + // If name exceeds buffer, return as-is (shouldn't happen in practice) + if len_usize > buf.len() { + return s.clone(); + } + s.copy_into_slice(&mut buf[..len_usize]); + + let mut start = 0usize; + while start < len_usize { + let b = buf[start]; + if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' { + start += 1; + } else { + break; + } + } + + let mut end = len_usize; + while end > start { + let b = buf[end - 1]; + if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' { + end -= 1; + } else { + break; + } + } + + if start == 0 && end == len_usize { + return s.clone(); + } + + String::from_bytes(env, &buf[start..end]) +} + fn validate_metadata_cid(env: &Env, cid: &String) -> Result<(), EventRegistryError> { let cid_len = cid.len(); if !(MIN_METADATA_CID_LEN..=MAX_METADATA_CID_LEN).contains(&cid_len) { diff --git a/contract/contracts/event_registry/src/test.rs b/contract/contracts/event_registry/src/test.rs index dd4520a..8101124 100644 --- a/contract/contracts/event_registry/src/test.rs +++ b/contract/contracts/event_registry/src/test.rs @@ -29,6 +29,7 @@ fn test_register_and_get_series() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id1.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: metadata_cid.clone(), @@ -45,6 +46,7 @@ fn test_register_and_get_series() { }); client.register_event(&EventRegistrationArgs { event_id: event_id2.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: metadata_cid.clone(), @@ -96,6 +98,7 @@ fn test_issue_and_use_series_pass() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: metadata_cid.clone(), @@ -259,6 +262,7 @@ fn test_storage_operations() { let tiers = Map::new(&env); let event_info = EventInfo { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_address.clone(), platform_fee_percent: 5, @@ -346,6 +350,7 @@ fn test_get_total_tickets_sold_uses_event_current_supply() { client.store_event(&EventInfo { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address, platform_fee_percent: 500, @@ -387,6 +392,7 @@ fn test_organizer_events_list() { let event_1 = EventInfo { event_id: String::from_str(&env, "e1"), + name: String::from_str(&env, "Test Event 1"), organizer_address: organizer.clone(), payment_address: payment_address.clone(), platform_fee_percent: 5, @@ -416,6 +422,7 @@ fn test_organizer_events_list() { let event_2 = EventInfo { event_id: String::from_str(&env, "e2"), + name: String::from_str(&env, "Test Event 2"), organizer_address: organizer.clone(), payment_address: payment_address.clone(), platform_fee_percent: 5, @@ -491,6 +498,7 @@ fn test_register_event_success() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr.clone(), metadata_cid, @@ -518,6 +526,52 @@ fn test_register_event_success() { assert_eq!(event_info.grace_period_end, 0); } +#[test] +fn test_register_event_name_trimming() { + let env = Env::default(); + let contract_id = env.register(EventRegistry, ()); + let client = EventRegistryClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let organizer = Address::generate(&env); + let payment_addr = Address::generate(&env); + let platform_wallet = Address::generate(&env); + + env.mock_all_auths(); + let usdc_token = Address::generate(&env); + client.initialize(&admin, &platform_wallet, &500, &usdc_token); + + let event_id = String::from_str(&env, "event_trim_test"); + let metadata_cid = String::from_str( + &env, + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ); + + // Register with intentionally messy name (leading/trailing whitespace) + let messy_name = String::from_str(&env, " Summer Fest 2025 "); + client.register_event(&EventRegistrationArgs { + event_id: event_id.clone(), + name: messy_name, + organizer_address: organizer, + payment_address: payment_addr, + metadata_cid, + max_supply: 100, + milestone_plan: None, + tiers: Map::new(&env), + refund_deadline: 0, + restocking_fee: 0, + resale_cap_bps: None, + min_sales_target: None, + target_deadline: None, + banner_cid: None, + tags: None, + }); + + let stored = client.get_event(&event_id).unwrap(); + // Name should be trimmed of leading and trailing whitespace + assert_eq!(stored.name, String::from_str(&env, "Summer Fest 2025")); +} + #[test] fn test_register_event_invalid_target_deadline() { let env = Env::default(); @@ -561,6 +615,7 @@ fn test_register_event_invalid_target_deadline() { // Past deadline should fail let result = client.try_register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr.clone(), metadata_cid: metadata_cid.clone(), @@ -580,6 +635,7 @@ fn test_register_event_invalid_target_deadline() { // Present deadline should fail let result = client.try_register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr.clone(), metadata_cid: metadata_cid.clone(), @@ -599,6 +655,7 @@ fn test_register_event_invalid_target_deadline() { // Future deadline should succeed client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -631,6 +688,7 @@ fn test_register_event_rejects_contract_as_organizer() { let result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "event_bad_org_contract"), + name: String::from_str(&env, "Test Event"), organizer_address: client.address.clone(), payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -670,6 +728,7 @@ fn test_register_event_rejects_zero_organizer_address() { let result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "event_bad_org_zero"), + name: String::from_str(&env, "Test Event"), organizer_address: zero_organizer, payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -714,6 +773,7 @@ fn test_register_event_unlimited_supply() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -757,6 +817,7 @@ fn test_register_duplicate_event_fails() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr.clone(), metadata_cid: metadata_cid.clone(), @@ -774,6 +835,7 @@ fn test_register_duplicate_event_fails() { let result = client.try_register_event(&EventRegistrationArgs { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -810,6 +872,7 @@ fn test_register_event_invalid_metadata_cid_formats() { let short_cid = String::from_str(&env, "bafy"); let short_result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "event_short_cid"), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr.clone(), metadata_cid: short_cid, @@ -835,6 +898,7 @@ fn test_register_event_invalid_metadata_cid_formats() { ); let wrong_prefix_result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "event_wrong_prefix_cid"), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid: wrong_prefix_cid, @@ -860,6 +924,7 @@ fn test_register_event_invalid_metadata_cid_formats() { ); let oversized_result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "event_oversized_cid"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), metadata_cid: oversized_cid, @@ -903,6 +968,7 @@ fn test_get_event_payment_info() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr.clone(), metadata_cid, @@ -946,6 +1012,7 @@ fn test_update_event_status() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -988,6 +1055,7 @@ fn test_event_inactive_error() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1031,6 +1099,7 @@ fn test_complete_event_lifecycle() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr.clone(), metadata_cid, @@ -1086,6 +1155,7 @@ fn test_update_metadata_success() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1134,6 +1204,7 @@ fn test_update_metadata_invalid_cid() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1228,6 +1299,7 @@ fn test_set_custom_event_fee() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: Address::generate(&env), metadata_cid, @@ -1283,6 +1355,7 @@ fn test_set_custom_event_fee_exceeds_max() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: Address::generate(&env), metadata_cid, @@ -1352,6 +1425,7 @@ fn test_increment_inventory_success() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1423,6 +1497,7 @@ fn test_increment_inventory_max_supply_exceeded() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1489,6 +1564,7 @@ fn test_increment_inventory_bulk_exceeds_max_supply() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1555,6 +1631,7 @@ fn test_increment_inventory_unlimited_supply() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1639,6 +1716,7 @@ fn test_increment_inventory_inactive_event() { ); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1698,6 +1776,7 @@ fn test_increment_inventory_persists_across_reads() { ); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1774,6 +1853,7 @@ fn test_tier_limit_exceeds_max_supply() { let result = client.try_register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1833,6 +1913,7 @@ fn test_tier_not_found() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1893,6 +1974,7 @@ fn test_tier_supply_exceeded() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -1969,6 +2051,7 @@ fn test_multiple_tiers_inventory() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2034,6 +2117,7 @@ fn test_increment_inventory_supply_overflow() { // Store event with current_supply near i128::MAX to trigger overflow client.store_event(&EventInfo { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr, platform_fee_percent: 500, @@ -2100,6 +2184,7 @@ fn test_increment_inventory_tier_sold_overflow() { client.store_event(&EventInfo { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr, platform_fee_percent: 500, @@ -2156,6 +2241,7 @@ fn test_update_event_status_noop_skips_event() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2232,6 +2318,7 @@ fn test_blacklist_prevents_event_registration() { let result = client.try_register_event(&EventRegistrationArgs { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2276,6 +2363,7 @@ fn test_update_metadata_noop_skips_event() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr, metadata_cid: metadata_cid.clone(), @@ -2359,6 +2447,7 @@ fn test_blacklist_suspends_active_events() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr, metadata_cid: metadata_cid.clone(), @@ -2484,6 +2573,7 @@ fn test_register_event_with_resale_cap() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2527,6 +2617,7 @@ fn test_register_event_resale_cap_zero() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2570,6 +2661,7 @@ fn test_register_event_resale_cap_none() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2613,6 +2705,7 @@ fn test_postpone_event_sets_grace_period() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2663,6 +2756,7 @@ fn test_register_event_resale_cap_invalid() { let result = client.try_register_event(&EventRegistrationArgs { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -2702,6 +2796,7 @@ fn test_cancel_event_success() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: payment_addr, metadata_cid, @@ -2741,6 +2836,7 @@ fn test_archive_event_rejects_active_event() { let event_id = String::from_str(&env, "archive_active"); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid: String::from_str( @@ -2784,6 +2880,7 @@ fn test_cancel_already_cancelled_fails() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid, @@ -2825,6 +2922,7 @@ fn test_update_status_on_cancelled_event_fails() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid, @@ -3468,6 +3566,7 @@ fn test_register_event_with_banner_cid() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -3517,6 +3616,7 @@ fn test_goal_met_event_fires_only_once() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), metadata_cid, @@ -3573,6 +3673,7 @@ fn test_register_event_without_banner_cid() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, metadata_cid, @@ -3639,6 +3740,7 @@ fn test_series_pass_issued_at_timestamp() { client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid, @@ -3712,6 +3814,7 @@ fn base_args( ) -> EventRegistrationArgs { EventRegistrationArgs { event_id: String::from_str(env, "evt_milestone"), + name: String::from_str(env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(env), metadata_cid: String::from_str( @@ -4048,6 +4151,7 @@ fn test_cancelled_status_guard() { let tiers = Map::new(&env); client.register_event(&EventRegistrationArgs { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -4180,6 +4284,7 @@ fn test_register_event_restocking_fee_exceeds_tier_price_fails() { // restocking_fee (6_000_000) > tier price (5_000_000) — must fail let result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "evt_restocking_guard"), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -4234,6 +4339,7 @@ fn test_register_event_restocking_fee_equal_to_tier_price_succeeds() { // restocking_fee == tier price — should succeed let result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "evt_restocking_equal"), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -4284,6 +4390,7 @@ fn test_register_event_restocking_fee_zero_always_valid() { let result = client.try_register_event(&EventRegistrationArgs { event_id: String::from_str(&env, "evt_restocking_zero"), + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: Address::generate(&env), metadata_cid: String::from_str( @@ -4334,6 +4441,7 @@ fn setup_tags_test(env: &Env) -> (EventRegistryClient<'static>, Address, Address fn tags_base_args(env: &Env, event_id: &str, organizer: &Address) -> EventRegistrationArgs { EventRegistrationArgs { event_id: String::from_str(env, event_id), + name: String::from_str(env, "Test Event"), organizer_address: organizer.clone(), payment_address: organizer.clone(), metadata_cid: String::from_str( diff --git a/contract/contracts/event_registry/src/test_e2e.rs b/contract/contracts/event_registry/src/test_e2e.rs index 90fadea..0ac3489 100644 --- a/contract/contracts/event_registry/src/test_e2e.rs +++ b/contract/contracts/event_registry/src/test_e2e.rs @@ -25,6 +25,7 @@ fn make_event_args( ) -> EventRegistrationArgs { EventRegistrationArgs { event_id: String::from_str(env, event_id), + name: String::from_str(env, "Test Event"), organizer_address: organizer.clone(), payment_address: organizer.clone(), metadata_cid: String::from_str( diff --git a/contract/contracts/event_registry/src/types.rs b/contract/contracts/event_registry/src/types.rs index dd48415..6962fff 100644 --- a/contract/contracts/event_registry/src/types.rs +++ b/contract/contracts/event_registry/src/types.rs @@ -90,6 +90,8 @@ pub enum EventStatus { pub struct EventInfo { /// Unique identifier for the event pub event_id: String, + /// Human-readable name for the event (trimmed of leading/trailing whitespace) + pub name: String, /// The wallet address of the event organizer pub organizer_address: Address, /// The address where payments for this event should be routed @@ -158,6 +160,8 @@ pub struct PaymentInfo { #[derive(Clone, Debug, Eq, PartialEq)] pub struct EventRegistrationArgs { pub event_id: String, + /// Human-readable name for the event. Leading/trailing whitespace will be trimmed on registration. + pub name: String, pub organizer_address: Address, pub payment_address: Address, pub metadata_cid: String, diff --git a/contract/contracts/event_registry/test_snapshots/test/test_complete_event_lifecycle.1.json b/contract/contracts/event_registry/test_snapshots/test/test_complete_event_lifecycle.1.json index ca26677..aee9f6d 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_complete_event_lifecycle.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_complete_event_lifecycle.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -338,6 +346,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_event_inactive_error.1.json b/contract/contracts/event_registry/test_snapshots/test/test_event_inactive_error.1.json index 0d8b8fa..ec0ae1c 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_event_inactive_error.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_event_inactive_error.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -335,6 +343,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_get_event_payment_info.1.json b/contract/contracts/event_registry/test_snapshots/test/test_get_event_payment_info.1.json index 68d5752..2972b85 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_get_event_payment_info.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_get_event_payment_info.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -313,6 +321,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_inactive_event.1.json b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_inactive_event.1.json index daefdec..b211e94 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_inactive_event.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_inactive_event.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -412,6 +420,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_max_supply_exceeded.1.json b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_max_supply_exceeded.1.json index d6733fe..c3867c0 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_max_supply_exceeded.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_max_supply_exceeded.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -441,6 +449,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_persists_across_reads.1.json b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_persists_across_reads.1.json index 774b301..3593703 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_persists_across_reads.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_persists_across_reads.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -516,6 +524,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_success.1.json b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_success.1.json index 262796b..298024c 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_success.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_success.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -441,6 +449,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_unlimited_supply.1.json b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_unlimited_supply.1.json index 4c6ce8a..d852d64 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_unlimited_supply.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_increment_inventory_unlimited_supply.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -640,6 +648,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_multiple_tiers_inventory.1.json b/contract/contracts/event_registry/test_snapshots/test/test_multiple_tiers_inventory.1.json index 51c5de4..0aa41bc 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_multiple_tiers_inventory.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_multiple_tiers_inventory.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -522,6 +530,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_organizer_events_list.1.json b/contract/contracts/event_registry/test_snapshots/test/test_organizer_events_list.1.json index 38c81c5..1bff3fc 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_organizer_events_list.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_organizer_events_list.1.json @@ -115,6 +115,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event 1" + } + }, { "key": { "symbol": "organizer_address" @@ -313,6 +321,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event 2" + } + }, { "key": { "symbol": "organizer_address" @@ -615,6 +631,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event 1" + } + }, { "key": { "symbol": "organizer_address" @@ -839,6 +863,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event 2" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_register_duplicate_event_fails.1.json b/contract/contracts/event_registry/test_snapshots/test/test_register_duplicate_event_fails.1.json index 2a1bd98..cd1ad39 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_register_duplicate_event_fails.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_register_duplicate_event_fails.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -313,6 +321,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_register_event_success.1.json b/contract/contracts/event_registry/test_snapshots/test/test_register_event_success.1.json index 7bb7031..cb10c2b 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_register_event_success.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_register_event_success.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -372,6 +380,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_register_event_unlimited_supply.1.json b/contract/contracts/event_registry/test_snapshots/test/test_register_event_unlimited_supply.1.json index 6c6d09c..b874db7 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_register_event_unlimited_supply.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_register_event_unlimited_supply.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -313,6 +321,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_storage_operations.1.json b/contract/contracts/event_registry/test_snapshots/test/test_storage_operations.1.json index 8bab84f..9454ff9 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_storage_operations.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_storage_operations.1.json @@ -116,6 +116,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -394,6 +402,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_tier_not_found.1.json b/contract/contracts/event_registry/test_snapshots/test/test_tier_not_found.1.json index f84a2df..4b73887 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_tier_not_found.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_tier_not_found.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -390,6 +398,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_tier_supply_exceeded.1.json b/contract/contracts/event_registry/test_snapshots/test/test_tier_supply_exceeded.1.json index 250cb15..d2cba8f 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_tier_supply_exceeded.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_tier_supply_exceeded.1.json @@ -79,6 +79,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -465,6 +473,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_update_event_status.1.json b/contract/contracts/event_registry/test_snapshots/test/test_update_event_status.1.json index 0d8b8fa..ec0ae1c 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_update_event_status.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_update_event_status.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -335,6 +343,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_invalid_cid.1.json b/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_invalid_cid.1.json index ee67650..82f2590 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_invalid_cid.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_invalid_cid.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -315,6 +323,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_success.1.json b/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_success.1.json index 817022e..30d6178 100644 --- a/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_success.1.json +++ b/contract/contracts/event_registry/test_snapshots/test/test_update_metadata_success.1.json @@ -60,6 +60,14 @@ }, "val": "void" }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" @@ -335,6 +343,14 @@ "i128": "0" } }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "Test Event" + } + }, { "key": { "symbol": "organizer_address" diff --git a/contract/contracts/ticket_payment/src/contract.rs b/contract/contracts/ticket_payment/src/contract.rs index bdfcb75..5754363 100644 --- a/contract/contracts/ticket_payment/src/contract.rs +++ b/contract/contracts/ticket_payment/src/contract.rs @@ -145,6 +145,7 @@ pub mod event_registry { #[derive(Clone, Debug, Eq, PartialEq)] pub struct EventInfo { pub event_id: String, + pub name: String, pub organizer_address: Address, pub payment_address: Address, pub platform_fee_percent: u32, diff --git a/contract/contracts/ticket_payment/src/test.rs b/contract/contracts/ticket_payment/src/test.rs index 20d83a6..06e4f84 100644 --- a/contract/contracts/ticket_payment/src/test.rs +++ b/contract/contracts/ticket_payment/src/test.rs @@ -24,6 +24,7 @@ impl MockCancelledRegistry { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -88,6 +89,7 @@ impl MockEventRegistry { if event_id == String::from_str(&env, "event_1") { return Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_1"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), // This will be different each call unless mocked specifically payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -160,6 +162,7 @@ impl MockEventRegistry2 { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_1"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 250, @@ -251,6 +254,7 @@ impl MockAuctionEventRegistry { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -964,6 +968,7 @@ impl MockEventRegistryMaxSupply { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_1"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -1075,6 +1080,7 @@ impl MockEventRegistryWithInventory { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -1298,6 +1304,7 @@ impl MockEventRegistryWithMilestones { Some(event_registry::EventInfo { event_id: String::from_str(&env, "milestone_event"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -1620,6 +1627,7 @@ impl MockEventRegistryEarlyBird { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_eb_1"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -2110,6 +2118,7 @@ impl MockEventRegistryWithOrganizer { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -2438,6 +2447,7 @@ impl MockPlatformRegistryE2E { let event = event_registry::EventInfo { event_id: event_id.clone(), + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address, platform_fee_percent: 500, @@ -2891,6 +2901,7 @@ impl MockEventRegistryRefund { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -2963,6 +2974,7 @@ impl MockEventRegistryWithResaleCap { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_capped"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -3207,6 +3219,7 @@ impl MockRegistryZeroCap { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_zero_cap"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -3821,6 +3834,7 @@ impl MockEventRegistryUsdPriced { pub fn get_event(env: Env, _event_id: String) -> Option { Some(event_registry::EventInfo { event_id: String::from_str(&env, "event_1"), + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -4474,6 +4488,7 @@ impl MockEventRegistryWithFailingLoyaltyUpdate { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -4601,6 +4616,7 @@ impl MockEventRegistryWithLoyalty { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -4682,6 +4698,7 @@ impl MockEventRegistryWithExcessiveLoyaltyDiscount { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -4898,6 +4915,7 @@ impl MockEventRegistryCustomFee { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -5023,6 +5041,7 @@ impl MockEventRegistryHighPrice { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -5130,6 +5149,7 @@ impl MockEventRegistryRefundDeadline { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, @@ -5705,6 +5725,7 @@ impl MockEventRegistryForDust { .unwrap_or_else(|| Address::generate(&env)); Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address: payment_addr, platform_fee_percent: 500, @@ -5896,6 +5917,7 @@ impl MockEventRegistryForReferral { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, // 5% @@ -5975,6 +5997,7 @@ impl MockEventRegistryFullLoyaltyDiscount { pub fn get_event(env: Env, event_id: String) -> Option { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: Address::generate(&env), payment_address: Address::generate(&env), platform_fee_percent: 500, // 5% diff --git a/contract/contracts/ticket_payment/src/test_e2e.rs b/contract/contracts/ticket_payment/src/test_e2e.rs index 1a03249..675c59f 100644 --- a/contract/contracts/ticket_payment/src/test_e2e.rs +++ b/contract/contracts/ticket_payment/src/test_e2e.rs @@ -49,6 +49,7 @@ impl MockRegistryE2E { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer, payment_address, platform_fee_percent: 500, @@ -161,6 +162,7 @@ impl MockRegistryCancelledE2E { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: organizer, platform_fee_percent: 500, @@ -253,6 +255,7 @@ impl MockRegistryWithGoal { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: organizer, platform_fee_percent: 500, @@ -1028,6 +1031,7 @@ impl MockRegistryAuction { Some(event_registry::EventInfo { event_id, + name: String::from_str(&env, "Test Event"), organizer_address: organizer.clone(), payment_address: organizer, platform_fee_percent: 500, From c232e4ef6539a2b5db52a72b53cbe9af7aead79f Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Sun, 29 Mar 2026 00:14:38 +0100 Subject: [PATCH 2/4] chore(server): add .dockerignore to reduce Docker build context Excludes target/, .env files, .git/, editor artifacts, and other non-essential files from the Docker build context. - target/ is the primary source of bloat (can be GBs in Rust projects) - .env files must never be baked into images - .git/ metadata (~30MB) is not needed at build time - Docs, scripts, and OS artifacts excluded for a leaner context --- server/.dockerignore | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 server/.dockerignore diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..e287a79 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,28 @@ +# Rust build artifacts - largest contributor to build context bloat +target/ + +# Environment files - should never be baked into an image +.env +.env.* +!.env.example + +# Git metadata +.git/ +.gitignore + +# Editor/IDE artifacts +.vscode/ +.idea/ +*.swp +*.swo + +# OS artifacts +.DS_Store +Thumbs.db + +# CI/CD +.github/ + +# Docs and scripts not needed at build time +*.md +*.sh From d43f97073bc71f99a01ded6d76e87199fa8a79de Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Sun, 29 Mar 2026 00:21:57 +0100 Subject: [PATCH 3/4] docs(server): add doc comments to model structs (#328) Add /// and //! doc comments to all structs and fields in server/src/models/ to improve database layer understandability. - User: documents role distinction from Organizer - Organizer: documents ownership relationship to Events - Event: documents FK to Organizer and cascade behaviour - TicketTier: documents quantity tracking semantics - Ticket: documents status lifecycle (active/used/cancelled) and QR code generation timing - Transaction: documents status lifecycle (pending/completed/failed) and Stellar hash usage for on-chain payments - mod.rs: adds module-level doc with entity relationship diagram --- server/src/models/event.rs | 16 ++++++++++++++ server/src/models/mod.rs | 13 +++++++++++ server/src/models/organizer.rs | 13 +++++++++++ server/src/models/ticket.rs | 38 ++++++++++++++++++++++++++++++++ server/src/models/transaction.rs | 23 +++++++++++++++++++ server/src/models/user.rs | 11 +++++++++ 6 files changed, 114 insertions(+) diff --git a/server/src/models/event.rs b/server/src/models/event.rs index d349ecd..6426c2e 100644 --- a/server/src/models/event.rs +++ b/server/src/models/event.rs @@ -3,16 +3,32 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; +/// Represents a ticketed event created by an organizer. +/// +/// An event belongs to exactly one [`super::organizer::Organizer`] and can have +/// multiple [`super::ticket::TicketTier`]s defining pricing and capacity. +/// Deleting an organizer cascades to all their events. +/// +/// Maps to the `events` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Event { + /// Unique identifier for the event (UUID v4). pub id: Uuid, + /// Foreign key referencing the [`super::organizer::Organizer`] who owns this event. pub organizer_id: Uuid, + /// Short, public-facing title of the event. pub title: String, + /// Optional detailed description of the event (agenda, speakers, etc.). pub description: Option, + /// Physical or virtual location where the event takes place. pub location: String, + /// Scheduled start time of the event (UTC). pub start_time: DateTime, + /// Optional scheduled end time of the event (UTC). `None` if open-ended. pub end_time: Option>, + /// Timestamp when this event record was created. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } diff --git a/server/src/models/mod.rs b/server/src/models/mod.rs index fe23b20..196ece7 100644 --- a/server/src/models/mod.rs +++ b/server/src/models/mod.rs @@ -1,3 +1,16 @@ +//! Database model structs for the Agora platform. +//! +//! Each module contains a struct that maps directly to a PostgreSQL table via +//! [`sqlx::FromRow`]. The entity relationships are: +//! +//! ```text +//! Organizer ──< Event ──< TicketTier ──< Ticket ──< Transaction +//! └──────── User +//! ``` +//! +//! All primary keys are UUID v4. `updated_at` fields are maintained automatically +//! by database triggers defined in the initial schema migration. + pub mod event; pub mod organizer; pub mod ticket; diff --git a/server/src/models/organizer.rs b/server/src/models/organizer.rs index e3562c0..45b670b 100644 --- a/server/src/models/organizer.rs +++ b/server/src/models/organizer.rs @@ -3,13 +3,26 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; +/// Represents an event organizer on the Agora platform. +/// +/// Organizers are entities (individuals or organizations) that create and manage +/// [`super::event::Event`]s. An organizer can own multiple events; deleting an +/// organizer cascades to all their events. +/// +/// Maps to the `organizers` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Organizer { + /// Unique identifier for the organizer (UUID v4). pub id: Uuid, + /// Public-facing name of the organizer (e.g., company or individual name). pub name: String, + /// Optional bio or description shown on the organizer's profile page. pub description: Option, + /// Primary contact email for the organizer, used for platform communications. pub contact_email: String, + /// Timestamp when the organizer account was created. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } diff --git a/server/src/models/ticket.rs b/server/src/models/ticket.rs index 85b383b..ba82629 100644 --- a/server/src/models/ticket.rs +++ b/server/src/models/ticket.rs @@ -4,28 +4,66 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; +/// Represents a pricing tier for an event (e.g., "General Admission", "VIP"). +/// +/// Each [`super::event::Event`] can have multiple tiers with different prices and +/// capacities. `available_quantity` is decremented as [`Ticket`]s are issued and +/// incremented on cancellation. Deleting an event cascades to all its tiers. +/// +/// Maps to the `ticket_tiers` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct TicketTier { + /// Unique identifier for this ticket tier (UUID v4). pub id: Uuid, + /// Foreign key referencing the [`super::event::Event`] this tier belongs to. pub event_id: Uuid, + /// Display name of the tier (e.g., `"General Admission"`, `"VIP"`). pub name: String, + /// Optional description of what this tier includes or its restrictions. pub description: Option, + /// Price per ticket in this tier, stored with up to 2 decimal places. pub price: Decimal, + /// Maximum number of tickets that can ever be sold for this tier. pub total_quantity: i32, + /// Current number of tickets still available for purchase. + /// Starts equal to `total_quantity` and decreases as tickets are issued. pub available_quantity: i32, + /// Timestamp when this tier was created. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } +/// Represents a single ticket issued to a user for a specific ticket tier. +/// +/// A ticket is the join between a [`super::user::User`] and a [`TicketTier`]. +/// Its lifecycle is tracked via the `status` field. Each ticket may have an +/// associated [`super::transaction::Transaction`] recording the payment. +/// Deleting a user or tier cascades to their tickets. +/// +/// Maps to the `tickets` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Ticket { + /// Unique identifier for this ticket (UUID v4). pub id: Uuid, + /// Foreign key referencing the [`super::user::User`] who owns this ticket. pub user_id: Uuid, + /// Foreign key referencing the [`TicketTier`] this ticket was purchased under. pub ticket_tier_id: Uuid, + /// Current lifecycle status of the ticket. + /// + /// Known values (enforced at the application layer): + /// - `"active"` — issued and valid for entry + /// - `"used"` — scanned/checked in at the event + /// - `"cancelled"` — refunded or voided; no longer valid pub status: String, + /// Optional QR code payload used for event check-in scanning. + /// `None` until the ticket is fully confirmed and a code is generated. pub qr_code: Option, + /// Timestamp when this ticket was issued. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } diff --git a/server/src/models/transaction.rs b/server/src/models/transaction.rs index 7cbec72..196fe68 100644 --- a/server/src/models/transaction.rs +++ b/server/src/models/transaction.rs @@ -4,15 +4,38 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; +/// Represents a payment transaction associated with a ticket purchase. +/// +/// Each [`super::ticket::Ticket`] has at most one transaction recording the +/// payment details. For on-chain payments, `stellar_transaction_hash` links +/// the record to the Stellar blockchain. Deleting a ticket cascades to its +/// transaction. +/// +/// Maps to the `transactions` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct Transaction { + /// Unique identifier for this transaction (UUID v4). pub id: Uuid, + /// Foreign key referencing the [`super::ticket::Ticket`] this payment is for. pub ticket_id: Uuid, + /// Amount charged, stored with up to 2 decimal places. pub amount: Decimal, + /// ISO 4217 currency code for the transaction (e.g., `"USD"`, `"USDC"`). + /// Defaults to `"USD"` in the database. pub currency: String, + /// Current processing status of the transaction. + /// + /// Known values (enforced at the application layer): + /// - `"pending"` — payment initiated but not yet confirmed + /// - `"completed"` — payment successfully settled + /// - `"failed"` — payment was declined or encountered an error pub status: String, + /// Optional Stellar blockchain transaction hash for on-chain payments. + /// `None` for off-chain or fiat transactions. pub stellar_transaction_hash: Option, + /// Timestamp when this transaction record was created. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } diff --git a/server/src/models/user.rs b/server/src/models/user.rs index 9372297..2069520 100644 --- a/server/src/models/user.rs +++ b/server/src/models/user.rs @@ -3,12 +3,23 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; +/// Represents a registered user of the Agora platform. +/// +/// Users are the attendees who browse events, purchase tickets, and attend events. +/// They are distinct from [`super::organizer::Organizer`]s, who create and manage events. +/// +/// Maps to the `users` table in the database. #[allow(dead_code)] #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct User { + /// Unique identifier for the user (UUID v4). pub id: Uuid, + /// Display name of the user. pub name: String, + /// Unique email address used for identification and communication. pub email: String, + /// Timestamp when the user account was created. pub created_at: DateTime, + /// Timestamp of the last update to this record. Managed by a DB trigger. pub updated_at: DateTime, } From 50f1bdc149b48501fa103c949eb0e6d8d7688704 Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Mon, 30 Mar 2026 09:12:03 +0100 Subject: [PATCH 4/4] feat(contract): check for duplicate tier IDs in register_event (#316) - Add `DuplicateTierId = 46` to `EventRegistryError` - Add `tier_ids: Vec` field to `EventRegistrationArgs` so callers declare their intended tier IDs alongside the map - In `register_event`, compare `tier_ids.len()` against `tiers.len()`: Soroban's Map silently collapses duplicate keys, so a mismatch reveals a duplicate was supplied - Add `test_register_event_duplicate_tier_id_fails` test that sets the same tier ID twice and asserts `DuplicateTierId` is returned - Update all existing test fixtures to include the new `tier_ids` field --- .../contracts/event_registry/src/error.rs | 5 + contract/contracts/event_registry/src/lib.rs | 26 ++++ contract/contracts/event_registry/src/test.rs | 123 ++++++++++++++++++ .../contracts/event_registry/src/test_e2e.rs | 1 + .../contracts/event_registry/src/types.rs | 6 + 5 files changed, 161 insertions(+) diff --git a/contract/contracts/event_registry/src/error.rs b/contract/contracts/event_registry/src/error.rs index d8b4227..ea3c6da 100644 --- a/contract/contracts/event_registry/src/error.rs +++ b/contract/contracts/event_registry/src/error.rs @@ -71,6 +71,8 @@ pub enum EventRegistryError { InvalidTargetDeadline = 44, /// Admin has already approved this proposal AlreadyApproved = 45, + /// Two or more ticket tiers share the same ID + DuplicateTierId = 46, } impl core::fmt::Display for EventRegistryError { @@ -212,6 +214,9 @@ impl core::fmt::Display for EventRegistryError { EventRegistryError::AlreadyApproved => { write!(f, "Admin has already approved this proposal") } + EventRegistryError::DuplicateTierId => { + write!(f, "Duplicate tier ID: all tier IDs must be unique") + } } } } diff --git a/contract/contracts/event_registry/src/lib.rs b/contract/contracts/event_registry/src/lib.rs index f45338c..98bb7c5 100644 --- a/contract/contracts/event_registry/src/lib.rs +++ b/contract/contracts/event_registry/src/lib.rs @@ -220,6 +220,32 @@ impl EventRegistry { return Err(EventRegistryError::EventAlreadyExists); } + // Validate that all tier IDs are unique. + // Soroban's Map silently overwrites duplicate keys, so a caller that + // sets the same key twice ends up with fewer entries than intended. + // We detect this by comparing adjacent keys in the sorted key list — + // since Map is ordered, any duplicate will appear consecutively. + { + let keys = args.tiers.keys(); + let len = keys.len(); + if len > 1 { + for i in 0..len - 1 { + if keys.get(i) == keys.get(i + 1) { + return Err(EventRegistryError::DuplicateTierId); + } + } + } + } + + // Validate that all tier IDs are unique. + // Soroban's Map silently overwrites duplicate keys on construction, so + // a caller that sets the same tier ID twice ends up with fewer map + // entries than declared. Comparing the declared tier_ids list length + // against the deduplicated map length reveals any collision. + if args.tier_ids.len() != args.tiers.len() { + return Err(EventRegistryError::DuplicateTierId); + } + // Validate tier limits don't exceed max_supply if args.max_supply > 0 { let mut total_tier_limit: i128 = 0; diff --git a/contract/contracts/event_registry/src/test.rs b/contract/contracts/event_registry/src/test.rs index 8101124..d56cba3 100644 --- a/contract/contracts/event_registry/src/test.rs +++ b/contract/contracts/event_registry/src/test.rs @@ -36,6 +36,7 @@ fn test_register_and_get_series() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -53,6 +54,7 @@ fn test_register_and_get_series() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -105,6 +107,7 @@ fn test_issue_and_use_series_pass() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -277,6 +280,7 @@ fn test_storage_operations() { current_supply: 0, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -365,6 +369,7 @@ fn test_get_total_tickets_sold_uses_event_current_supply() { current_supply: 9, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -407,6 +412,7 @@ fn test_organizer_events_list() { current_supply: 0, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -437,6 +443,7 @@ fn test_organizer_events_list() { current_supply: 0, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -505,6 +512,7 @@ fn test_register_event_success() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -558,6 +566,7 @@ fn test_register_event_name_trimming() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -622,6 +631,7 @@ fn test_register_event_invalid_target_deadline() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -642,6 +652,7 @@ fn test_register_event_invalid_target_deadline() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -662,6 +673,7 @@ fn test_register_event_invalid_target_deadline() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -698,6 +710,7 @@ fn test_register_event_rejects_contract_as_organizer() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -738,6 +751,7 @@ fn test_register_event_rejects_zero_organizer_address() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -780,6 +794,7 @@ fn test_register_event_unlimited_supply() { max_supply: 0, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -824,6 +839,7 @@ fn test_register_duplicate_event_fails() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -842,6 +858,7 @@ fn test_register_duplicate_event_fails() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -853,6 +870,66 @@ fn test_register_duplicate_event_fails() { assert_eq!(result, Err(Ok(EventRegistryError::EventAlreadyExists))); } +#[test] +fn test_register_event_duplicate_tier_id_fails() { + let env = Env::default(); + let contract_id = env.register(EventRegistry, ()); + let client = EventRegistryClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let organizer = Address::generate(&env); + let platform_wallet = Address::generate(&env); + let usdc_token = Address::generate(&env); + env.mock_all_auths(); + client.initialize(&admin, &platform_wallet, &500, &usdc_token); + + let metadata_cid = String::from_str( + &env, + "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ); + + // Build a map with a duplicate tier ID — Soroban's Map silently keeps the + // last value, so the map ends up with one entry instead of two. + let tier = TicketTier { + name: String::from_str(&env, "General"), + price: 1_000, + tier_limit: 50, + current_sold: 0, + is_refundable: true, + auction_config: soroban_sdk::vec![&env], + }; + let mut tiers = Map::new(&env); + tiers.set(String::from_str(&env, "general"), tier.clone()); + // Setting the same key again — this is the duplicate + tiers.set(String::from_str(&env, "general"), tier); + + let result = client.try_register_event(&EventRegistrationArgs { + event_id: String::from_str(&env, "event_dup_tier"), + name: String::from_str(&env, "Dup Tier Event"), + organizer_address: organizer, + payment_address: Address::generate(&env), + metadata_cid, + max_supply: 0, + milestone_plan: None, + tiers, + // Declare two IDs but the map only has one (duplicate was collapsed) + tier_ids: soroban_sdk::vec![ + &env, + String::from_str(&env, "general"), + String::from_str(&env, "general"), + ], + refund_deadline: 0, + restocking_fee: 0, + resale_cap_bps: None, + min_sales_target: None, + target_deadline: None, + banner_cid: None, + tags: None, + }); + + assert_eq!(result, Err(Ok(EventRegistryError::DuplicateTierId))); +} + #[test] fn test_register_event_invalid_metadata_cid_formats() { let env = Env::default(); @@ -879,6 +956,7 @@ fn test_register_event_invalid_metadata_cid_formats() { max_supply: 100, milestone_plan: None, tiers: tiers.clone(), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -905,6 +983,7 @@ fn test_register_event_invalid_metadata_cid_formats() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -931,6 +1010,7 @@ fn test_register_event_invalid_metadata_cid_formats() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -975,6 +1055,7 @@ fn test_get_event_payment_info() { max_supply: 50, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1019,6 +1100,7 @@ fn test_update_event_status() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1062,6 +1144,7 @@ fn test_event_inactive_error() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1106,6 +1189,7 @@ fn test_complete_event_lifecycle() { max_supply: 200, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1162,6 +1246,7 @@ fn test_update_metadata_success() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1211,6 +1296,7 @@ fn test_update_metadata_invalid_cid() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1306,6 +1392,7 @@ fn test_set_custom_event_fee() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1362,6 +1449,7 @@ fn test_set_custom_event_fee_exceeds_max() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1432,6 +1520,7 @@ fn test_increment_inventory_success() { max_supply: 10, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1504,6 +1593,7 @@ fn test_increment_inventory_max_supply_exceeded() { max_supply: 2, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1571,6 +1661,7 @@ fn test_increment_inventory_bulk_exceeds_max_supply() { max_supply: 3, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1638,6 +1729,7 @@ fn test_increment_inventory_unlimited_supply() { max_supply: 0, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1723,6 +1815,7 @@ fn test_increment_inventory_inactive_event() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1783,6 +1876,7 @@ fn test_increment_inventory_persists_across_reads() { max_supply: 50, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1860,6 +1954,7 @@ fn test_tier_limit_exceeds_max_supply() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1920,6 +2015,7 @@ fn test_tier_not_found() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -1981,6 +2077,7 @@ fn test_tier_supply_exceeded() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2058,6 +2155,7 @@ fn test_multiple_tiers_inventory() { max_supply: 70, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2132,6 +2230,7 @@ fn test_increment_inventory_supply_overflow() { current_supply: i128::MAX, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2199,6 +2298,7 @@ fn test_increment_inventory_tier_sold_overflow() { current_supply: 0, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2248,6 +2348,7 @@ fn test_update_event_status_noop_skips_event() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2325,6 +2426,7 @@ fn test_blacklist_prevents_event_registration() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2370,6 +2472,7 @@ fn test_update_metadata_noop_skips_event() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2454,6 +2557,7 @@ fn test_blacklist_suspends_active_events() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2580,6 +2684,7 @@ fn test_register_event_with_resale_cap() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: Some(1000), // 10% above face value @@ -2624,6 +2729,7 @@ fn test_register_event_resale_cap_zero() { max_supply: 50, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: Some(0), // No markup allowed @@ -2668,6 +2774,7 @@ fn test_register_event_resale_cap_none() { max_supply: 50, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, // No cap @@ -2712,6 +2819,7 @@ fn test_postpone_event_sets_grace_period() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2763,6 +2871,7 @@ fn test_register_event_resale_cap_invalid() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: Some(10001), // Over 100% - invalid @@ -2803,6 +2912,7 @@ fn test_cancel_event_success() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 100, resale_cap_bps: None, @@ -2846,6 +2956,7 @@ fn test_archive_event_rejects_active_event() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2887,6 +2998,7 @@ fn test_cancel_already_cancelled_fails() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -2929,6 +3041,7 @@ fn test_update_status_on_cancelled_event_fails() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -3573,6 +3686,7 @@ fn test_register_event_with_banner_cid() { max_supply: 100, milestone_plan: None, tiers: Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -3623,6 +3737,7 @@ fn test_goal_met_event_fires_only_once() { max_supply: 100, milestone_plan: None, tiers: soroban_sdk::Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -3680,6 +3795,7 @@ fn test_register_event_without_banner_cid() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -3747,6 +3863,7 @@ fn test_series_pass_issued_at_timestamp() { max_supply: 50, milestone_plan: None, tiers: soroban_sdk::Map::new(&env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -3824,6 +3941,7 @@ fn base_args( max_supply: 100, milestone_plan, tiers: Map::new(env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -4161,6 +4279,7 @@ fn test_cancelled_status_guard() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -4294,6 +4413,7 @@ fn test_register_event_restocking_fee_exceeds_tier_price_fails() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 6_000_000, resale_cap_bps: None, @@ -4349,6 +4469,7 @@ fn test_register_event_restocking_fee_equal_to_tier_price_succeeds() { max_supply: 100, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 5_000_000, resale_cap_bps: None, @@ -4400,6 +4521,7 @@ fn test_register_event_restocking_fee_zero_always_valid() { max_supply: 50, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, @@ -4451,6 +4573,7 @@ fn tags_base_args(env: &Env, event_id: &str, organizer: &Address) -> EventRegist max_supply: 0, milestone_plan: None, tiers: Map::new(env), + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, diff --git a/contract/contracts/event_registry/src/test_e2e.rs b/contract/contracts/event_registry/src/test_e2e.rs index 0ac3489..6332d3e 100644 --- a/contract/contracts/event_registry/src/test_e2e.rs +++ b/contract/contracts/event_registry/src/test_e2e.rs @@ -35,6 +35,7 @@ fn make_event_args( max_supply, milestone_plan: None, tiers, + tier_ids: soroban_sdk::vec![&env], refund_deadline: 0, restocking_fee: 0, resale_cap_bps: None, diff --git a/contract/contracts/event_registry/src/types.rs b/contract/contracts/event_registry/src/types.rs index 6962fff..2b17c73 100644 --- a/contract/contracts/event_registry/src/types.rs +++ b/contract/contracts/event_registry/src/types.rs @@ -167,7 +167,13 @@ pub struct EventRegistrationArgs { pub metadata_cid: String, pub max_supply: i128, pub milestone_plan: Option>, + /// Map of tier_id to TicketTier for multi-tiered pricing. pub tiers: Map, + /// Explicit list of all tier IDs being registered. Must have the same + /// length as `tiers` — if a duplicate ID was set in `tiers`, the map + /// silently collapses it and the lengths will differ, triggering + /// `DuplicateTierId`. + pub tier_ids: Vec, pub refund_deadline: u64, pub restocking_fee: i128, /// Optional resale price cap in basis points above face value.