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
44 changes: 44 additions & 0 deletions contracts/creator-event-manager/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub enum EventError {
EventCancelled = 19,
/// entry_fee < 0
InvalidEntryFee = 20,
/// Caller is not the event creator.
Unauthorized = 21,
}

impl From<InviteError> for EventError {
Expand Down Expand Up @@ -282,6 +284,48 @@ pub fn create_event(
Ok((event_id, invite_code))
}

// ---------------------------------------------------------------------------
// cancel_event (#1019)
// ---------------------------------------------------------------------------

/// Cancel an event. Only the event creator may call this, and only before
/// the event has been finalized.
///
/// Sets `is_cancelled = true` and `is_active = false` on the stored event and
/// emits a `("event", "cancelled")` contract event.
///
/// Returns [`EventError::EventNotFound`] when the ID is unknown,
/// [`EventError::Unauthorized`] when `caller` is not the creator,
/// and [`EventError::AlreadyFinalized`] when the event is already finalized.
/// Returns [`EventError::EventCancelled`] if the event was already cancelled.
pub fn cancel_event(env: &Env, caller: Address, event_id: u64) -> Result<(), EventError> {
caller.require_auth();

let mut event = storage::get_event(env, event_id).map_err(|_| EventError::EventNotFound)?;

if event.creator != caller {
return Err(EventError::Unauthorized);
}

if event.is_finalized {
return Err(EventError::AlreadyFinalized);
}

if event.is_cancelled {
return Err(EventError::EventCancelled);
}

event.cancel();
storage::set_event(env, event_id, &event);

env.events().publish(
(Symbol::new(env, "event"), Symbol::new(env, "cancelled")),
event_id,
);

Ok(())
}

// ---------------------------------------------------------------------------
// get_event (#796)
// ---------------------------------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions contracts/creator-event-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,27 @@ impl CreatorEventManagerContract {
}
}

/// Cancel an event. Only the event creator may call this before finalization.
///
/// Marks the event as cancelled and inactive, and emits a
/// `("event", "cancelled")` contract event. Cancellation is irreversible.
///
/// # Panics
/// * `"event_not_found"` — no event exists with the given ID.
/// * `"unauthorized"` — caller is not the event creator.
/// * `"already_finalized"` — the event has already been finalized.
/// * `"event_cancelled"` — the event is already cancelled.
pub fn cancel_event(env: Env, caller: Address, event_id: u64) {
match event::cancel_event(&env, caller, event_id) {
Ok(()) => {}
Err(EventError::EventNotFound) => panic!("event_not_found"),
Err(EventError::Unauthorized) => panic!("unauthorized"),
Err(EventError::AlreadyFinalized) => panic!("already_finalized"),
Err(EventError::EventCancelled) => panic!("event_cancelled"),
Err(_) => panic!("unexpected_error"),
}
}

/// Return the escrowed prize pool (in stroops) for an event.
///
/// Returns `0` for a "fun event" with no payouts.
Expand Down
164 changes: 164 additions & 0 deletions contracts/creator-event-manager/tests/cancel_event_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/// Integration tests for cancel_event (#1019).
use creator_event_manager::CreatorEventManagerContractClient;
use soroban_sdk::testutils::Address as _;
use soroban_sdk::testutils::Ledger;
use soroban_sdk::token::StellarAssetClient;
use soroban_sdk::{Address, Env, String, Vec};

const FEE: i128 = 1_000_000;

fn setup() -> (
Env,
CreatorEventManagerContractClient<'static>,
Address, // admin
Address, // xlm_token
) {
let env = Env::default();
env.mock_all_auths();
env.ledger().with_mut(|li| {
li.timestamp = 1_700_000_000;
});

let contract_id = env.register(creator_event_manager::CreatorEventManagerContract, ());
let client = CreatorEventManagerContractClient::new(&env, &contract_id);
let client: CreatorEventManagerContractClient<'static> =
unsafe { core::mem::transmute(client) };

let admin = Address::generate(&env);
let ai_agent = Address::generate(&env);
let treasury = Address::generate(&env);
let token_admin = Address::generate(&env);
let xlm_token = env
.register_stellar_asset_contract_v2(token_admin)
.address();

client.initialize(&admin, &ai_agent, &treasury, &xlm_token, &FEE);
(env, client, admin, xlm_token)
}

fn fund(env: &Env, token: &Address, user: &Address, amount: i128) {
StellarAssetClient::new(env, token).mint(user, &amount);
}

/// Create a basic event and return its id.
fn create_event(
env: &Env,
client: &CreatorEventManagerContractClient,
creator: &Address,
xlm_token: &Address,
) -> u64 {
fund(env, xlm_token, creator, FEE);
let now = env.ledger().timestamp();
let (event_id, _) = client.create_event(
creator,
&String::from_str(env, "Test Event"),
&String::from_str(env, "A test event"),
&10u32,
&(now + 3600),
&(now + 7200),
&0i128,
&Vec::new(env),
&0i128,
);
event_id
}

// -----------------------------------------------------------------------
// Happy path
// -----------------------------------------------------------------------

#[test]
fn test_cancel_event_success() {
let (env, client, _admin, xlm_token) = setup();
let creator = Address::generate(&env);
let event_id = create_event(&env, &client, &creator, &xlm_token);

client.cancel_event(&creator, &event_id);

let event = client.get_event(&event_id);
assert!(event.is_cancelled);
assert!(!event.is_active);
}

// -----------------------------------------------------------------------
// Guard: non-creator cannot cancel
// -----------------------------------------------------------------------

#[test]
#[should_panic(expected = "unauthorized")]
fn test_cancel_event_non_creator_rejected() {
let (env, client, _admin, xlm_token) = setup();
let creator = Address::generate(&env);
let stranger = Address::generate(&env);
let event_id = create_event(&env, &client, &creator, &xlm_token);

client.cancel_event(&stranger, &event_id);
}

// -----------------------------------------------------------------------
// Guard: already-cancelled event is rejected
// -----------------------------------------------------------------------

#[test]
#[should_panic(expected = "event_cancelled")]
fn test_cancel_event_already_cancelled() {
let (env, client, _admin, xlm_token) = setup();
let creator = Address::generate(&env);
let event_id = create_event(&env, &client, &creator, &xlm_token);

client.cancel_event(&creator, &event_id);
client.cancel_event(&creator, &event_id); // second call must panic
}

// -----------------------------------------------------------------------
// Guard: cancelled event blocks join_event
// -----------------------------------------------------------------------

#[test]
#[should_panic(expected = "event_cancelled")]
fn test_join_cancelled_event_rejected() {
let (env, client, _admin, xlm_token) = setup();
let creator = Address::generate(&env);
let participant = Address::generate(&env);
let event_id = create_event(&env, &client, &creator, &xlm_token);

let event = client.get_event(&event_id);
client.cancel_event(&creator, &event_id);
client.join_event(&participant, &event.invite_code);
}

// -----------------------------------------------------------------------
// Guard: cancelled event blocks create_match
// -----------------------------------------------------------------------

#[test]
#[should_panic(expected = "event_cancelled")]
fn test_create_match_on_cancelled_event_rejected() {
let (env, client, _admin, xlm_token) = setup();
let creator = Address::generate(&env);
let event_id = create_event(&env, &client, &creator, &xlm_token);

client.cancel_event(&creator, &event_id);

let now = env.ledger().timestamp();
client.create_match(
&creator,
&event_id,
&String::from_str(&env, "Team A"),
&String::from_str(&env, "Team B"),
&(now + 3600),
&1u32,
);
}

// -----------------------------------------------------------------------
// Guard: non-existent event returns event_not_found
// -----------------------------------------------------------------------

#[test]
#[should_panic(expected = "event_not_found")]
fn test_cancel_event_not_found() {
let (env, client, _admin, _xlm_token) = setup();
let caller = Address::generate(&env);
client.cancel_event(&caller, &9999u64);
}
Loading