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
330 changes: 330 additions & 0 deletions soroban-contract/contracts/event_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,100 @@ impl EventManager {
.publish((Symbol::new(&env, "event_canceled"),), event_id);
}

/// Update event details. Only the organizer can update. Cannot update a canceled event.
/// Cannot reduce total_tickets below tickets_sold. Cannot set dates in the past.
pub fn update_event(
env: Env,
event_id: u32,
theme: Option<String>,
ticket_price: Option<i128>,
total_tickets: Option<u128>,
start_date: Option<u64>,
end_date: Option<u64>,
) {
let mut event: Event = env
.storage()
.persistent()
.get(&DataKey::Event(event_id))
.unwrap_or_else(|| panic!("Event not found"));

// Only organizer can update
event.organizer.require_auth();

// Cannot update a canceled event
if event.is_canceled {
panic!("Cannot update a canceled event");
}

let current_time = env.ledger().timestamp();

// Apply theme if provided
if let Some(t) = theme {
event.theme = t;
}

// Apply ticket_price if provided (must be non-negative)
if let Some(p) = ticket_price {
if p < 0 {
panic!("Ticket price cannot be negative");
}
event.ticket_price = p;
}

// Apply total_tickets if provided (cannot be below tickets_sold)
if let Some(t) = total_tickets {
if t == 0 {
panic!("Total tickets must be greater than 0");
}
if t < event.tickets_sold {
panic!("Cannot reduce total_tickets below tickets_sold");
}
event.total_tickets = t;
}

// Effective end for start validation (new end if provided in this call, else current)
let effective_end = end_date.unwrap_or(event.end_date);
// Apply start_date if provided
if let Some(s) = start_date {
if s < current_time {
panic!("Start date cannot be in the past");
}
if s >= effective_end {
panic!("Start date must be before end date");
}
event.start_date = s;
}

// Effective start for end validation (new start if provided in this call, else current)
let effective_start = start_date.unwrap_or(event.start_date);
// Apply end_date if provided
if let Some(e) = end_date {
if e < current_time {
panic!("End date cannot be in the past");
}
if e <= effective_start {
panic!("End date must be after start date");
}
event.end_date = e;
}

// Update storage
env.storage()
.persistent()
.set(&DataKey::Event(event_id), &event);

// Extend TTL
env.storage().persistent().extend_ttl(
&DataKey::Event(event_id),
30 * 24 * 60 * 60 / 5,
100 * 24 * 60 * 60 / 5,
);

// Emit event_updated event
env.events()
.publish((Symbol::new(&env, "event_updated"),), (event_id, event.organizer.clone()));
}

/// Update tickets sold (called by ticket purchase logic)
pub fn update_tickets_sold(env: Env, event_id: u32, amount: u128) {
let mut event: Event = env
Expand Down Expand Up @@ -323,4 +417,240 @@ impl EventManager {
}
}

#[cfg(test)]
mod update_event_tests {
use super::*;
use crate::test::MockContract;
use soroban_sdk::{testutils::Address as _, testutils::Ledger};

fn setup_event_for_update(env: &Env) -> (EventManagerClient<'_>, Address, u32) {
let contract_id = env.register(EventManager, ());
let client = EventManagerClient::new(env, &contract_id);
let mock_addr = env.register(MockContract, ());
let organizer = Address::generate(env);
env.mock_all_auths();
client.initialize(&mock_addr);

let start_date = env.ledger().timestamp() + 86400;
let end_date = start_date + 86400;
let event_id = client.create_event(
&organizer,
&String::from_str(env, "Original Theme"),
&String::from_str(env, "Conference"),
&start_date,
&end_date,
&1000_0000000,
&100,
&Address::generate(env),
);
(client, organizer, event_id)
}

#[test]
fn test_update_event_theme() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);

client.update_event(
&event_id,
&Option::Some(String::from_str(&env, "Updated Theme")),
&Option::None,
&Option::None,
&Option::None,
&Option::None,
);

let event = client.get_event(&event_id);
assert_eq!(event.theme, String::from_str(&env, "Updated Theme"));
}

#[test]
fn test_update_event_ticket_price() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);
let new_price = 2000_0000000i128;

client.update_event(
&event_id,
&Option::None,
&Option::Some(new_price),
&Option::None,
&Option::None,
&Option::None,
);

let event = client.get_event(&event_id);
assert_eq!(event.ticket_price, new_price);
}

#[test]
fn test_update_event_total_tickets() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);

client.update_event(
&event_id,
&Option::None,
&Option::None,
&Option::Some(200u128),
&Option::None,
&Option::None,
);

let event = client.get_event(&event_id);
assert_eq!(event.total_tickets, 200);
}

#[test]
fn test_update_event_dates() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);
let new_start = env.ledger().timestamp() + 172800;
let new_end = new_start + 86400;

client.update_event(
&event_id,
&Option::None,
&Option::None,
&Option::None,
&Option::Some(new_start),
&Option::Some(new_end),
);

let event = client.get_event(&event_id);
assert_eq!(event.start_date, new_start);
assert_eq!(event.end_date, new_end);
}

#[test]
fn test_update_event_emits_event() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);

client.update_event(
&event_id,
&Option::Some(String::from_str(&env, "Emit Test")),
&Option::None,
&Option::None,
&Option::None,
&Option::None,
);

// Update completed successfully; event_updated is emitted in the same code path
let event = client.get_event(&event_id);
assert_eq!(event.theme, String::from_str(&env, "Emit Test"));
}

#[test]
#[should_panic(expected = "Cannot update a canceled event")]
fn test_update_event_canceled_fails() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);
client.cancel_event(&event_id);

client.update_event(
&event_id,
&Option::Some(String::from_str(&env, "Should fail")),
&Option::None,
&Option::None,
&Option::None,
&Option::None,
);
}

#[test]
#[should_panic(expected = "Cannot reduce total_tickets below tickets_sold")]
fn test_update_event_total_tickets_below_sold_fails() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register(EventManager, ());
let client = EventManagerClient::new(&env, &contract_id);
let mock_addr = env.register(MockContract, ());
let organizer = Address::generate(&env);
let buyer = Address::generate(&env);
client.initialize(&mock_addr);

let start_date = env.ledger().timestamp() + 86400;
let end_date = start_date + 86400;
let event_id = client.create_event(
&organizer,
&String::from_str(&env, "Event"),
&String::from_str(&env, "Type"),
&start_date,
&end_date,
&100i128,
&10u128,
&mock_addr,
);
client.purchase_ticket(&buyer, &event_id);
client.purchase_ticket(&Address::generate(&env), &event_id);

client.update_event(
&event_id,
&Option::None,
&Option::None,
&Option::Some(1u128),
&Option::None,
&Option::None,
);
}

#[test]
#[should_panic(expected = "Start date cannot be in the past")]
fn test_update_event_start_date_past_fails() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);
env.ledger()
.set_timestamp(env.ledger().timestamp() + 86400 * 2);
let past_start = env.ledger().timestamp() - 3600;

client.update_event(
&event_id,
&Option::None,
&Option::None,
&Option::None,
&Option::Some(past_start),
&Option::None,
);
}

#[test]
#[should_panic(expected = "Start date must be before end date")]
fn test_update_event_end_before_start_fails() {
let env = Env::default();
let (client, _organizer, event_id) = setup_event_for_update(&env);
let start_date = env.ledger().timestamp() + 86400;
let end_before_start = start_date - 3600;

client.update_event(
&event_id,
&Option::None,
&Option::None,
&Option::None,
&Option::Some(start_date),
&Option::Some(end_before_start),
);
}

#[test]
#[should_panic(expected = "Event not found")]
fn test_update_event_not_found_fails() {
let env = Env::default();
let contract_id = env.register(EventManager, ());
let client = EventManagerClient::new(&env, &contract_id);
let mock_addr = env.register(MockContract, ());
env.mock_all_auths();
client.initialize(&mock_addr);

client.update_event(
&999u32,
&Option::None,
&Option::None,
&Option::None,
&Option::None,
&Option::None,
);
}
}

mod test;
2 changes: 1 addition & 1 deletion soroban-contract/contracts/event_manager/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![cfg(test)]

use super::*;
use soroban_sdk::{testutils::Address as _, testutils::Ledger, Address, BytesN, Env, Symbol};
use soroban_sdk::{testutils::Address as _, testutils::Ledger, Address, BytesN, Env};

#[contract]
pub struct MockContract;
Expand Down
Loading
Loading