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
7 changes: 7 additions & 0 deletions src/base/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod Errors {
pub const NOT_TOKEN_OWNER: felt252 = 'Not Token Owner';
pub const TOKEN_DOES_NOT_EXIST: felt252 = 'Token Does Not Exist';
pub const EVENT_NOT_PAID: felt252 = 'Event is not paid';
pub const EVENT_NOT_CLOSED: felt252 = 'Event Is Not Closed';

pub const GROUP_ID_EXISTS: felt252 = 'Group ID Already In Use';
pub const INVALID_MAX_MEMBERS: felt252 = 'Maximum Member Must Be > 0';
Expand All @@ -29,4 +30,10 @@ pub mod Errors {
pub const GROUP_ACTIVE: felt252 = 'Group Is Already Active';
pub const GROUP_NOT_FULL: felt252 = 'Group Is Not Yet Full';
pub const GROUP_ROUNDS_COMPLETED: felt252 = 'Group RoundS Already Completed';

pub const INVALID_PAYMENT_TOKEN: felt252 = 'Payment Token Is Not Set';
pub const PAYMENT_FAILED: felt252 = 'Payment Transfer Failed';
pub const ALREADY_PAID: felt252 = 'Already Paid For This Event';
pub const NO_WITHDRAWAL: felt252 = 'No Funds To Withdraw';
pub const NOT_EVENT_REGISTERED: felt252 = 'Not Registered For Event';
}
133 changes: 126 additions & 7 deletions src/events/chainevents.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
/// @dev Implements Ownable and Upgradeable components from OpenZeppelin
pub mod ChainEvents {
use chainevents_contracts::base::errors::Errors::{
ALREADY_REGISTERED, ALREADY_RSVP, CLOSED_EVENT, EVENT_CLOSED, INVALID_EVENT, NOT_OWNER,
NOT_REGISTERED, ZERO_ADDRESS_CALLER,
ALREADY_PAID, ALREADY_REGISTERED, ALREADY_RSVP, CLOSED_EVENT, EVENT_CLOSED,
EVENT_NOT_CLOSED, EVENT_NOT_PAID, INVALID_EVENT, INVALID_PAYMENT_TOKEN,
NOT_EVENT_REGISTERED, NOT_OWNER, NOT_REGISTERED, NO_WITHDRAWAL, PAYMENT_FAILED,
ZERO_ADDRESS_CALLER,
};
use chainevents_contracts::base::types::{EventDetails, EventRegistration, EventType};
use chainevents_contracts::interfaces::IEvent::IEvent;
Expand All @@ -14,12 +16,21 @@ pub mod ChainEvents {
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePathEntry,
};
use core::starknet::syscalls::deploy_syscall;
use core::starknet::{ClassHash, ContractAddress, get_block_timestamp, get_caller_address};
use core::starknet::{
ClassHash, ContractAddress, get_block_timestamp, get_caller_address, get_contract_address,
};
use openzeppelin::access::ownable::OwnableComponent;
use openzeppelin::security::ReentrancyGuardComponent;
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use openzeppelin::upgrades::UpgradeableComponent;


component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);
// OpenZeppelin ReentrancyGuard component
component!(
path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent,
);

#[abi(embed_v0)]
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>;
Expand All @@ -28,6 +39,8 @@ pub mod ChainEvents {

impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;

impl ReentrancyGuardInternalImpl = ReentrancyGuardComponent::InternalImpl<ContractState>;

/// @notice Contract storage structure
/// @dev Contains mappings for event management and tracking
#[storage]
Expand All @@ -36,10 +49,13 @@ pub mod ChainEvents {
ownable: OwnableComponent::Storage,
#[substorage(v0)]
upgradeable: UpgradeableComponent::Storage,
#[substorage(v0)]
reentrancy_guard: ReentrancyGuardComponent::Storage,
event_owners: Map<u256, ContractAddress>, // map(event_id, eventOwnerAddress)
event_counts: u256,
event_details: Map<u256, EventDetails>, // map(event_id, EventDetailsParams)
event_registrations: Map<ContractAddress, u256>, // map<attendeeAddress, event_id>
event_token: IERC20Dispatcher, // token used for event payments
attendee_event_details: Map<
(u256, ContractAddress), EventRegistration,
>, // map <(event_id, attendeeAddress), EventRegistration>
Expand Down Expand Up @@ -70,6 +86,10 @@ pub mod ChainEvents {
#[flat]
UpgradeableEvent: UpgradeableComponent::Event,
UnregisteredEvent: UnregisteredEvent,
PaymentMade: PaymentMade,
PaymentWithdrawn: PaymentWithdrawn,
#[flat]
ReentrancyGuardEvent: ReentrancyGuardComponent::Event,
}

/// @notice Event emitted when a new event is created
Expand Down Expand Up @@ -126,12 +146,32 @@ pub mod ChainEvents {
pub user_address: ContractAddress,
}

/// @notice Event emitted when a payment is made for an event
#[derive(Drop, starknet::Event)]
pub struct PaymentMade {
pub event_id: u256,
pub attendee: ContractAddress,
pub amount: u256,
}

/// @notice Event emitted when payment is withdrawn by event owner
#[derive(Drop, starknet::Event)]
pub struct PaymentWithdrawn {
pub event_id: u256,
pub event_owner: ContractAddress,
pub amount: u256,
}


/// @notice Initializes the Events contract
/// @dev Sets the initial event count to 0
#[constructor]
fn constructor(ref self: ContractState, owner: ContractAddress) {
fn constructor(
ref self: ContractState, owner: ContractAddress, event_token_address: ContractAddress,
) {
self.event_counts.write(0);
self.ownable.initializer(owner);
self.event_token.write(IERC20Dispatcher { contract_address: event_token_address });
}

#[abi(embed_v0)]
Expand Down Expand Up @@ -284,15 +324,94 @@ pub mod ChainEvents {
self.attendee_event_registration_counts.read(event_id)
}

fn pay_for_event(ref self: ContractState, event_id: u256) {}
fn withdraw_paid_event_amount(ref self: ContractState, event_id: u256) {}
/// @notice Allows a user to pay for a paid event
/// @param event_id The ID of the event to pay for
/// @dev Reverts if event doesn't exist, isn't paid, or incorrect amount is sent
fn pay_for_event(ref self: ContractState, event_id: u256) {
self.reentrancy_guard.start();
let payment_token: IERC20Dispatcher = self.event_token.read();
// assert(payment_token.is_non_zero(), INVALID_PAYMENT_TOKEN);

let caller = get_caller_address();
let event = self.event_details.read(event_id);
let mut registration = self.attendee_event_details.read((event_id, caller));

// Validate event exists and is paid type
assert(event.event_id == event_id, INVALID_EVENT);
assert(event.event_type == EventType::Paid, EVENT_NOT_PAID);
assert(!event.is_closed, CLOSED_EVENT);

// Validate attendee is registered
assert(registration.attendee_address == caller, NOT_EVENT_REGISTERED);

// Check if user has already paid
let (paid_event_id, paid_amount) = self.paid_events.read(caller);
assert(paid_event_id != event_id, ALREADY_PAID);

// Make payment into event contract
let event_contract = get_contract_address();
let status = payment_token.transfer_from(caller, event_contract, event.paid_amount);

assert(status, PAYMENT_FAILED);

// Update payment tracking
self.paid_events.write(caller, (event_id, event.paid_amount));

// Update total amount collected for this event
let current_total = self.paid_events_amount.read(event_id);
self.paid_events_amount.write(event_id, current_total + event.paid_amount);

// Update tickets count
let current_count = self.paid_event_ticket_count.read(event_id);
self.paid_event_ticket_count.write(event_id, current_count + 1);

// Update attendee registration with payment info
registration.amount_paid = event.paid_amount;
self.attendee_event_details.write((event_id, caller), registration);

self.emit(PaymentMade { event_id, attendee: caller, amount: event.paid_amount });

self.reentrancy_guard.end();
}

/// @notice Allows event owner to withdraw collected payments
/// @param event_id The ID of the event to withdraw from
/// @dev Reverts if caller isn't event owner or no funds available
fn withdraw_paid_event_amount(ref self: ContractState, event_id: u256) {
let caller = get_caller_address();
let event_owner = self.event_owners.read(event_id);

// Validate event ownership and existence
assert(!event_owner.is_zero(), INVALID_EVENT);
assert(caller == event_owner, NOT_OWNER);

let event = self.event_details.read(event_id);
// assert(event.is_closed, EVENT_NOT_CLOSED);

let withdraw_amount = self.paid_events_amount.read(event_id);
assert(withdraw_amount > 0, NO_WITHDRAWAL);

// Reset the total amount before transferring to prevent reentrancy
self.paid_events_amount.write(event_id, 0);

// Transfer funds to event owner
let payment_token: IERC20Dispatcher = self.event_token.read();
let status = payment_token.transfer(event_owner, withdraw_amount);
assert(status, PAYMENT_FAILED);

self.emit(PaymentWithdrawn { event_id, event_owner, amount: withdraw_amount });
}

/// @notice Gets the payment details for the calling user
/// @return (event_id, amount_paid) tuple
fn fetch_user_paid_event(self: @ContractState) -> (u256, u256) {
(0, 0)
self.paid_events.read(get_caller_address())
}

fn paid_event_ticket_counts(self: @ContractState) -> u256 {
0
}

fn event_total_amount_paid(self: @ContractState) -> u256 {
0
}
Expand Down
7 changes: 3 additions & 4 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ pub mod base;
pub mod events;
pub mod group;
pub mod interfaces;
// pub mod mocks {
// pub mod erc20;
// }

pub mod mocks {
pub mod erc20;
}

2 changes: 1 addition & 1 deletion src/mocks/erc20.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[starknet::contract]
pub mod MyToken {
use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;
const FAUCET_AMOUNT: u256 = 1_000_000; // 1E6 * 1E18
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Expand Down
Loading