Skip to content
Open
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
75 changes: 75 additions & 0 deletions soroban-contract/contracts/event_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub enum DataKey {
Event(u32),
EventCounter,
TicketFactory,
RefundClaimed(u32, Address), // (event_id, buyer_address)
EventBuyers(u32), // event_id -> Vec<Address> of ticket buyers
}

// Event structure
Expand Down Expand Up @@ -171,6 +173,68 @@ impl EventManager {
.publish((Symbol::new(&env, "event_canceled"),), event_id);
}

/// Claim refund for a canceled event (pull model)
/// Only works for canceled events, prevents double-refund claims
pub fn claim_refund(env: Env, claimer: Address, event_id: u32) {
claimer.require_auth();

let event: Event = env
.storage()
.persistent()
.get(&DataKey::Event(event_id))
.unwrap_or_else(|| panic!("Event not found"));

// Event must be canceled
if !event.is_canceled {
panic!("Event is not canceled");
}

// Check if this claimer already claimed refund
if env
.storage()
.persistent()
.has(&DataKey::RefundClaimed(event_id, claimer.clone()))
{
panic!("Refund already claimed");
}

// Verify claimer is in the buyers list
let buyers: Vec<Address> = env
.storage()
.persistent()
.get(&DataKey::EventBuyers(event_id))
.unwrap_or_else(|| Vec::new(&env));

let mut found = false;
for buyer in buyers.iter() {
if buyer == claimer {
found = true;
break;
}
}

if !found {
panic!("Claimer did not purchase a ticket for this event");
}

// Mark refund as claimed (prevent double-refund)
env.storage()
.persistent()
.set(&DataKey::RefundClaimed(event_id, claimer.clone()), &true);

// Transfer refund amount back to claimer
if event.ticket_price > 0 {
let token_client = soroban_sdk::token::Client::new(&env, &event.payment_token);
token_client.transfer(&event.organizer, &claimer, &event.ticket_price);
}

// Emit refund claimed event
env.events().publish(
(Symbol::new(&env, "refund_claimed"),),
(event_id, claimer, event.ticket_price),
);
}

/// 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(
Expand Down Expand Up @@ -324,6 +388,17 @@ impl EventManager {
soroban_sdk::vec![&env, buyer.into_val(&env)],
);

// Track buyer for refund purposes
let mut buyers: Vec<Address> = env
.storage()
.persistent()
.get(&DataKey::EventBuyers(event_id))
.unwrap_or_else(|| Vec::new(&env));
buyers.push_back(buyer.clone());
env.storage()
.persistent()
.set(&DataKey::EventBuyers(event_id), &buyers);

// Update tickets sold
event.tickets_sold += 1;

Expand Down
237 changes: 237 additions & 0 deletions soroban-contract/contracts/event_manager/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,240 @@ fn test_purchase_ticket() {
let event = client.get_event(&event_id);
assert_eq!(event.tickets_sold, 1);
}

// ========== REFUND TESTS ==========

#[test]
fn test_claim_refund_successful() {
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);

// Create event with ticket price
let event_id = client.create_event(
&organizer,
&String::from_str(&env, "Test Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&100i128,
&10u128,
&mock_addr,
);

// Purchase ticket
client.purchase_ticket(&buyer, &event_id);
let event = client.get_event(&event_id);
assert_eq!(event.tickets_sold, 1);

// Cancel event
client.cancel_event(&event_id);

// Claim refund
client.claim_refund(&buyer, &event_id);
}

#[test]
#[should_panic(expected = "Event is not canceled")]
fn test_claim_refund_event_not_canceled() {
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 event_id = client.create_event(
&organizer,
&String::from_str(&env, "Test Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&100i128,
&10u128,
&mock_addr,
);

client.purchase_ticket(&buyer, &event_id);

// Try to claim refund without canceling event
client.claim_refund(&buyer, &event_id);
}

#[test]
#[should_panic(expected = "Refund already claimed")]
fn test_claim_refund_double_claim() {
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 event_id = client.create_event(
&organizer,
&String::from_str(&env, "Test Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&100i128,
&10u128,
&mock_addr,
);

client.purchase_ticket(&buyer, &event_id);
client.cancel_event(&event_id);

// Claim refund first time
client.claim_refund(&buyer, &event_id);

// Try to claim again (should fail)
client.claim_refund(&buyer, &event_id);
}

#[test]
#[should_panic(expected = "Claimer did not purchase a ticket for this event")]
fn test_claim_refund_no_ticket_purchased() {
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);
let non_buyer = Address::generate(&env);

client.initialize(&mock_addr);

let event_id = client.create_event(
&organizer,
&String::from_str(&env, "Test Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&100i128,
&10u128,
&mock_addr,
);

client.purchase_ticket(&buyer, &event_id);
client.cancel_event(&event_id);

// Try to claim refund without purchasing
client.claim_refund(&non_buyer, &event_id);
}

#[test]
fn test_claim_refund_free_ticket() {
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);

// Create event with zero ticket price
let event_id = client.create_event(
&organizer,
&String::from_str(&env, "Free Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&0i128, // Free ticket
&10u128,
&mock_addr,
);

client.purchase_ticket(&buyer, &event_id);
client.cancel_event(&event_id);

// Should succeed even with zero price (no token transfer)
client.claim_refund(&buyer, &event_id);
}

#[test]
fn test_multiple_refund_claims() {
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 buyer1 = Address::generate(&env);
let buyer2 = Address::generate(&env);
let buyer3 = Address::generate(&env);

client.initialize(&mock_addr);

let event_id = client.create_event(
&organizer,
&String::from_str(&env, "Multi-buyer Event"),
&String::from_str(&env, "Conference"),
&(env.ledger().timestamp() + 86400),
&(env.ledger().timestamp() + 172800),
&100i128,
&10u128,
&mock_addr,
);

// Multiple buyers purchase tickets
client.purchase_ticket(&buyer1, &event_id);
client.purchase_ticket(&buyer2, &event_id);
client.purchase_ticket(&buyer3, &event_id);

let event = client.get_event(&event_id);
assert_eq!(event.tickets_sold, 3);

// Cancel event
client.cancel_event(&event_id);

// All three buyers claim refunds
client.claim_refund(&buyer1, &event_id);
client.claim_refund(&buyer2, &event_id);
client.claim_refund(&buyer3, &event_id);
}

#[test]
#[should_panic(expected = "Event not found")]
fn test_claim_refund_nonexistent_event() {
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, ());
client.initialize(&mock_addr);

let buyer = Address::generate(&env);

// Try to claim refund for non-existent event
client.claim_refund(&buyer, &999u32);
}
Loading
Loading