Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 79 additions & 10 deletions soroban-contract/contracts/event_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,26 @@ impl EventManager {
pub fn create_event(env: Env, params: CreateEventParams) -> Result<u32, Error> {
params.organizer.require_auth();

// Validate basic params
if params.start_date <= env.ledger().timestamp() {
return Err(Error::InvalidStartDate);
}
if params.end_date <= params.start_date {
return Err(Error::InvalidEndDate);
Self::validate_create_schedule(&env, params.start_date, params.end_date)?;
Self::validate_bounded_string(&params.theme, Self::MAX_STRING_BYTES)?;
Self::validate_bounded_string(&params.event_type, Self::MAX_STRING_BYTES)?;
Self::validate_ticket_price(params.ticket_price)?;

if !params.tiers.is_empty() {
if params.tiers.len() > Self::MAX_TICKET_TIERS {
return Err(Error::TooManyTicketTiers);
}
}

Self::enforce_organizer_limits_and_rate(&env, &params.organizer)?;

let resolved_tiers = if params.tiers.is_empty() {
if params.total_tickets == 0 {
return Err(Error::InvalidTicketCount);
}
if params.total_tickets > Self::MAX_TICKETS_PER_EVENT {
return Err(Error::InvalidTicketCount);
}
let mut v = Vec::new(&env);
v.push_back(TicketTier {
name: String::from_str(&env, "General"),
Expand All @@ -165,12 +176,17 @@ impl EventManager {
} else {
let mut v = Vec::new(&env);
for cfg in params.tiers.iter() {
Self::validate_bounded_string(&cfg.name, Self::MAX_STRING_BYTES)?;
if cfg.price < 0 {
return Err(Error::NegativeTicketPrice);
}
Self::validate_ticket_price(cfg.price)?;
if cfg.total_quantity == 0 {
return Err(Error::InvalidTierConfig);
}
if cfg.total_quantity > Self::MAX_TICKETS_PER_EVENT {
return Err(Error::InvalidTierConfig);
}
v.push_back(TicketTier {
name: cfg.name.clone(),
price: cfg.price,
Expand All @@ -182,6 +198,9 @@ impl EventManager {
};

let agg_total: u128 = resolved_tiers.iter().map(|t| t.total_quantity).sum();
if agg_total == 0 || agg_total > Self::MAX_TICKETS_PER_EVENT {
return Err(Error::InvalidTicketCount);
}
let agg_price = resolved_tiers
.first()
.map(|t| t.price)
Expand Down Expand Up @@ -221,9 +240,11 @@ impl EventManager {

env.events().publish(
(Symbol::new(&env, "event_created"),),
(event_id, params.organizer, ticket_nft_addr),
(event_id, params.organizer.clone(), ticket_nft_addr),
);

Self::commit_organizer_create(&env, &params.organizer);

Ok(event_id)
}

Expand Down Expand Up @@ -302,6 +323,8 @@ impl EventManager {
);
}

Self::decrement_organizer_open_events(&env, &event.organizer);

Ok(())
}

Expand Down Expand Up @@ -385,6 +408,9 @@ impl EventManager {
if quantity == 0 {
return Err(Error::InvalidTicketCount);
}
if quantity > Self::MAX_PURCHASE_QUANTITY {
return Err(Error::PurchaseQuantityTooLarge);
}

let mut event: Event = env
.storage()
Expand Down Expand Up @@ -529,20 +555,25 @@ impl EventManager {
let current_time = env.ledger().timestamp();

if let Some(t) = theme {
Self::validate_bounded_string(&t, Self::MAX_STRING_BYTES)?;
event.theme = t;
}

if let Some(p) = ticket_price {
if p < 0 {
return Err(Error::NegativeTicketPrice);
}
Self::validate_ticket_price(p)?;
event.ticket_price = p;
}

if let Some(t) = total_tickets {
if t == 0 {
return Err(Error::InvalidTicketCount);
}
if t > Self::MAX_TICKETS_PER_EVENT {
return Err(Error::InvalidTicketCount);
}
if t < event.tickets_sold {
return Err(Error::TicketsBelowSold);
}
Expand All @@ -551,23 +582,31 @@ impl EventManager {

let effective_end = end_date.unwrap_or(event.end_date);
if let Some(s) = start_date {
if s < current_time {
if s <= current_time {
return Err(Error::InvalidStartDate);
}
if s >= effective_end {
return Err(Error::InvalidEndDate);
}
Self::validate_event_span(s, effective_end)?;
Self::validate_start_not_too_far(s, current_time)?;
event.start_date = s;
}

let effective_start = start_date.unwrap_or(event.start_date);
if let Some(e) = end_date {
if e < current_time {
if e <= current_time {
return Err(Error::InvalidEndDate);
}
if e <= effective_start {
return Err(Error::InvalidEndDate);
}
// Max duration / far-future start only apply when the event start is still ahead;
// otherwise lengthening `end_date` for an in-progress event would falsely fail.
if effective_start > current_time {
Self::validate_event_span(effective_start, e)?;
Self::validate_start_not_too_far(effective_start, current_time)?;
}
event.end_date = e;
}

Expand Down Expand Up @@ -635,7 +674,7 @@ impl EventManager {

env.events().publish(
(Symbol::new(&env, "funds_withdrawn"),),
(event_id, event.organizer, balance),
(event_id, event.organizer.clone(), balance),
);

Ok(())
Expand Down Expand Up @@ -684,6 +723,36 @@ impl EventManager {
// Placeholder: waitlist promotion is handled externally via join_waitlist / return_ticket
}

fn commit_organizer_create(env: &Env, organizer: &Address) {
let count_key = DataKey::OrganizerOpenEventCount(organizer.clone());
let open_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0);
env.storage()
.persistent()
.set(&count_key, &(open_count.saturating_add(1)));
Self::extend_persistent_ttl(env, &count_key);

let ts_key = DataKey::OrganizerLastCreateTs(organizer.clone());
env.storage()
.instance()
.set(&ts_key, &env.ledger().timestamp());
env.storage()
.instance()
.extend_ttl(Self::ttl_threshold(), Self::ttl_extend_to());
}

fn decrement_organizer_open_events(env: &Env, organizer: &Address) {
let count_key = DataKey::OrganizerOpenEventCount(organizer.clone());
let open_count: u32 = env.storage().persistent().get(&count_key).unwrap_or(0);
env.storage()
.persistent()
.set(&count_key, &open_count.saturating_sub(1));
Self::extend_persistent_ttl(env, &count_key);
}

fn try_promote_from_waitlist(_env: &Env, _event_id: u32) {
// Waitlist promotion hooks live alongside `join_waitlist` when enabled.
}

fn get_and_increment_counter(env: &Env) -> Result<u32, Error> {
let current: u32 = env
.storage()
Expand Down
16 changes: 7 additions & 9 deletions soroban-contract/contracts/event_manager/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn make_event(
tiers: Vec<TierConfig>,
) -> (Address, u32) {
let (organizer, params) = make_params(env, mock_addr, tiers);
let event_id = client.create_event(&params);
let event_id = client.create_event_with_tiers(&params);
(organizer, event_id)
}

Expand All @@ -72,7 +72,7 @@ fn test_create_event() {
let organizer = Address::generate(&env);
let start_date = env.ledger().timestamp() + 86_400;

let event_id = client.create_event(&CreateEventParams {
let event_id = client.create_event_with_tiers(&CreateEventParams {
organizer: organizer.clone(),
theme: String::from_str(&env, "Rust Conference 2026"),
event_type: String::from_str(&env, "Conference"),
Expand Down Expand Up @@ -101,7 +101,7 @@ fn test_create_event_past_start_date_fails() {
let organizer = Address::generate(&env);
env.ledger().set_timestamp(1_000);

let result = client.try_create_event(&CreateEventParams {
let result = client.try_create_event_with_tiers(&CreateEventParams {
organizer,
theme: String::from_str(&env, "Past Event"),
event_type: String::from_str(&env, "Conference"),
Expand Down Expand Up @@ -220,7 +220,7 @@ fn test_claim_refund_free_ticket() {
let buyer = Address::generate(&env);
let start = env.ledger().timestamp() + 86_400;

let event_id = client.create_event(&CreateEventParams {
let event_id = client.create_event_with_tiers(&CreateEventParams {
organizer,
theme: String::from_str(&env, "Free Event"),
event_type: String::from_str(&env, "Conference"),
Expand Down Expand Up @@ -436,7 +436,7 @@ fn test_batch_purchase_refund_uses_total_paid() {
let organizer = Address::generate(&env);
let start = env.ledger().timestamp() + 86_400;

let event_id = client.create_event(&CreateEventParams {
let event_id = client.create_event_with_tiers(&CreateEventParams {
organizer,
theme: String::from_str(&env, "Meetup"),
event_type: String::from_str(&env, "Conference"),
Expand Down Expand Up @@ -517,10 +517,8 @@ fn test_withdraw_funds_double_withdrawal() {
let event = client.get_event(&event_id);
env.ledger().set_timestamp(event.end_date + 1);

// First withdrawal succeeds
client.withdraw_funds(&event_id);

// Second withdrawal must fail
let result = client.try_withdraw_funds(&event_id);
assert!(result.is_err());
}
Expand All @@ -541,7 +539,7 @@ fn test_withdraw_funds_zero_balance() {

let organizer = Address::generate(&env);
let start = env.ledger().timestamp() + 86_400;
let event_id = client.create_event(&CreateEventParams {
let event_id = client.create_event_with_tiers(&CreateEventParams {
organizer,
theme: String::from_str(&env, "Free Event"),
event_type: String::from_str(&env, "Conference"),
Expand Down Expand Up @@ -569,7 +567,7 @@ fn test_withdraw_funds_after_partial_refunds() {
let organizer = Address::generate(&env);
let start = env.ledger().timestamp() + 86_400;

let event_id = client.create_event(&CreateEventParams {
let event_id = client.create_event_with_tiers(&CreateEventParams {
organizer,
theme: String::from_str(&env, "Hybrid Event"),
event_type: String::from_str(&env, "Conference"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"function": {
"contract_fn": {
"contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"function_name": "create_event",
"function_name": "create_event_with_tiers",
"args": [
{
"map": [
Expand Down Expand Up @@ -359,6 +359,51 @@
1728000
]
],
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": {
"vec": [
{
"symbol": "OrganizerOpenEventCount"
},
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
]
},
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": {
"vec": [
{
"symbol": "OrganizerOpenEventCount"
},
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
]
},
"durability": "persistent",
"val": {
"u32": 1
}
}
},
"ext": "v0"
},
1728000
]
],
[
{
"contract_data": {
Expand Down Expand Up @@ -394,6 +439,21 @@
"u32": 1
}
},
{
"key": {
"vec": [
{
"symbol": "OrganizerLastCreateTs"
},
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
]
},
"val": {
"u64": 0
}
},
{
"key": {
"vec": [
Expand Down
Loading
Loading