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
9 changes: 9 additions & 0 deletions contracts/raffle-instance/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,15 @@ pub struct SwapDeadlineUpdated {
pub timestamp: u64,
}

#[derive(Clone)]
#[contractevent]
pub struct EndTimeExtended {
pub old_end_time: u64,
pub new_end_time: u64,
pub extended_by: Address,
pub timestamp: u64,
}

#[derive(Clone)]
#[contractevent]
pub struct EmergencyWithdrawn {
Expand Down
48 changes: 45 additions & 3 deletions contracts/raffle-instance/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ pub enum DataKey {
CommitEntry(u32),
DrawingLock,
TicketBuyers,
/// Per-owner ticket ID index: owner Address → Vec<u32> of ticket IDs.
/// Appended to on every successful ticket purchase, allowing O(1) owner
/// lookups without scanning the full ticket space.
OwnerTickets(Address),
}

#[contracttype]
Expand Down Expand Up @@ -762,6 +766,19 @@ impl Contract {
ticket_ids.push_back(ticket_id);
}

// Maintain the per-owner ticket ID index so get_my_tickets is O(1).
let mut owner_tickets: Vec<u32> = env
.storage()
.persistent()
.get(&DataKey::OwnerTickets(buyer.clone()))
.unwrap_or_else(|| Vec::new(&env));
for i in 0..ticket_ids.len() {
owner_tickets.push_back(ticket_ids.get(i).unwrap());
}
env.storage()
.persistent()
.set(&DataKey::OwnerTickets(buyer.clone()), &owner_tickets);

// Update ticket count and raffle sold
env.storage().persistent().set(
&DataKey::TicketCount(buyer.clone()),
Expand Down Expand Up @@ -1568,6 +1585,18 @@ impl Contract {
})
}

/// Return all ticket IDs owned by `owner`.
///
/// Uses the `OwnerTickets` index maintained during `buy_tickets` for an
/// O(1) read. Falls back to an empty Vec when the address has never
/// purchased a ticket.
pub fn get_my_tickets(env: Env, owner: Address) -> Vec<u32> {
env.storage()
.persistent()
.get(&DataKey::OwnerTickets(owner))
.unwrap_or_else(|| Vec::new(&env))
}

pub fn wipe_storage(env: Env) -> Result<(), Error> {
let factory: Address = env
.storage()
Expand Down Expand Up @@ -1602,6 +1631,9 @@ impl Contract {
env.storage()
.persistent()
.remove(&DataKey::TicketCount(buyer.clone()));
env.storage()
.persistent()
.remove(&DataKey::OwnerTickets(buyer.clone()));
}
env.storage().persistent().remove(&DataKey::TicketBuyers);

Expand Down Expand Up @@ -1802,9 +1834,19 @@ impl Contract {
return Err(Error::NoActiveTickets);
}

let selector = OracleSeedWinnerSelection::new(seed);
let winning_ticket_ids =
selector.select_winner_indices(env, total_tickets, raffle.prizes.len());
let winning_ticket_ids = match raffle.randomness_source {
RandomnessSource::Internal | RandomnessSource::CommitReveal => {
PrngWinnerSelection::new(
env.current_contract_address(),
total_tickets,
)
.select_winner_indices(env, total_tickets, raffle.prizes.len())
}
RandomnessSource::External => {
OracleSeedWinnerSelection::new(seed)
.select_winner_indices(env, total_tickets, raffle.prizes.len())
}
};
let mut winners = Vec::new(env);

for i in 0..winning_ticket_ids.len() {
Expand Down
20 changes: 8 additions & 12 deletions contracts/raffle-instance/src/randomness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ use soroban_sdk::{xdr::ToXdr, Address, Bytes, BytesN, Env, Vec};
/// **For low-stakes raffles only.** See the module-level comment for a full
/// explanation of the limitations and the recommended alternative for
/// high-value draws.
#[allow(dead_code)]
pub fn build_internal_seed(env: &Env, raffle_id: &Address) -> BytesN<32> {
let timestamp = env.ledger().timestamp();
let sequence = env.ledger().sequence();
Expand All @@ -68,7 +67,6 @@ pub fn build_internal_seed(env: &Env, raffle_id: &Address) -> BytesN<32> {
/// In that case we expect the returned hash to be invalid rather than silently
/// falling back to a zeroed seed, which would make winner selection
/// deterministic and insecure.
#[allow(dead_code)]
fn hash_bytes32(env: &Env, input: &Bytes) -> BytesN<32> {
let hash: BytesN<32> = env.crypto().sha256(input).into();
if hash.to_array() == [0u8; 32] {
Expand All @@ -90,7 +88,6 @@ pub trait WinnerSelectionStrategy {
///
/// **For low-stakes raffles only** — see [`build_internal_seed`] for the full
/// security caveat.
#[allow(dead_code)]
pub struct PrngWinnerSelection {
_timestamp: u64,
_sequence: u32,
Expand All @@ -102,9 +99,8 @@ pub struct PrngWinnerSelection {
pub tickets_sold: u32,
}

#[allow(dead_code)]
impl PrngWinnerSelection {
pub fn new(timestamp: u64, sequence: u32, raffle_id: Address, tickets_sold: u32) -> Self {
pub fn new(raffle_id: Address, tickets_sold: u32) -> Self {
Self {
_timestamp: timestamp,
_sequence: sequence,
Expand Down Expand Up @@ -360,7 +356,7 @@ mod tests {
fn prng_selection_is_in_ticket_range() {
let env = Env::default();
let raffle_id = Address::generate(&env);
let strategy = PrngWinnerSelection::new(1_700_000_000, 99_001, raffle_id, 17);
let strategy = PrngWinnerSelection::new(raffle_id, 17);

let contract_id = env
.register_stellar_asset_contract_v2(Address::generate(&env))
Expand All @@ -384,11 +380,11 @@ mod tests {
.register_stellar_asset_contract_v2(Address::generate(&env))
.address();
let first = env.as_contract(&contract_id, || {
PrngWinnerSelection::new(1_700_000_000, 99_001, raffle_id.clone(), 17)
PrngWinnerSelection::new(raffle_id.clone(), 17)
.select_winner_indices(&env, 17, 8)
});
let second = env.as_contract(&contract_id, || {
PrngWinnerSelection::new(1_700_000_000, 99_001, raffle_id, 17)
PrngWinnerSelection::new(raffle_id, 17)
.select_winner_indices(&env, 17, 8)
});

Expand All @@ -409,8 +405,8 @@ mod tests {
.address();

let (fp_a, fp_b) = env.as_contract(&contract, || {
let s_a = PrngWinnerSelection::new(0, 0, id_a, 10);
let s_b = PrngWinnerSelection::new(0, 0, id_b, 10);
let s_a = PrngWinnerSelection::new(id_a, 10);
let s_b = PrngWinnerSelection::new(id_b, 10);
(s_a.seed_fingerprint(&env), s_b.seed_fingerprint(&env))
});

Expand All @@ -430,8 +426,8 @@ mod tests {
.address();

let (fp_a, fp_b) = env.as_contract(&contract, || {
let s_a = PrngWinnerSelection::new(0, 0, raffle_id.clone(), 10);
let s_b = PrngWinnerSelection::new(0, 0, raffle_id, 11);
let s_a = PrngWinnerSelection::new(raffle_id.clone(), 10);
let s_b = PrngWinnerSelection::new(raffle_id, 11);
(s_a.seed_fingerprint(&env), s_b.seed_fingerprint(&env))
});

Expand Down
Loading