Viral Vault
  • Welcome
  • Getting Started
    • Requirements
    • Quick Start Guide
  • Technical Overview
    • Architecture
    • Smart Contract
  • Raffle System
    • How Raffles Work
    • Entry Process
    • Threshold System
    • Winner Selection
    • Pricing Transparency
  • Rewards
    • Referral Program
    • Free Giveaways & Community Raffles
  • Roadmap
    • Roadmap: Community Voting System
    • Roadmap: User-Created Raffles
  • Prizes & Delivery
    • Prize Claims
    • Shipping Information
    • International Winners (Outside of US)
  • Refund System
    • Refund Eligibility
    • Claiming Refunds
  • Support & FAQ
    • Contact Information
    • Frequently Asked Questions
  • Terms & Conditions
  • Official Links
    • Website & Main Platform
    • X / Twitter
    • GitHub
    • SOLSCAN (Smart Contract Address)
    • SolanaFM (Smart Contract Address)
Powered by GitBook
On this page
  • Overview
  • Core Components
  • 1. Raffle Creation and Management
  • 2. Ticket System
  • 3. Treasury Management
  • Winner Selection System
  • 1. Randomness Implementation
  • 2. Winner Determination
  • Security Features
  • 1. Input Validation
  • 2. Access Control
  • 3. Financial Security
  • 4. Enhanced Randomness
  • Refund System
  • State Management
  • Raffle States
  • Event System
  • Error Handling System
  • Error Definitions
  1. Technical Overview

Smart Contract

Rust-based Solana programs handle ticket sales and winner selection. Open-source code ensures complete transparency and security.

Please check official GitHub for most recent updates. Official Links

Overview

This smart contract implements a secure and transparent raffle system on the Solana blockchain. It enables us to create raffles where participants can purchase tickets for chances to win prizes. The system prioritizes security, fairness, and transparency throughout the entire process.

Core Components

1. Raffle Creation and Management

The system allows authorized users to create and manage raffles with customizable parameters.

For Users:

  • Set ticket prices (0.1 to 100 SOL)

  • Define minimum required ticket sales

  • Set optional maximum ticket limit

  • Set raffle duration (1 hour to 30 days)

  • Provide prize information via metadata URI

Technical Implementation:

#[account]
pub struct Raffle {
    pub authority: Pubkey, // Raffle creator/admin
    pub treasury: Pubkey, // Treasury PDA holding funds
    pub metadata_uri: String, // Prize metadata
    pub ticket_price: u64, // Price per ticket in lamports
    pub current_tickets: u64, // Total tickets sold
    pub min_tickets: u64, // Minimum required sales
    pub max_tickets: Option<u64>, // Optional maximum tickets limit
    pub current_entries: u64, // Number of purchase transactions
    pub creation_time: i64, // Unix timestamp of creation
    pub end_time: i64, // Unix timestamp of end
    pub raffle_state: RaffleState, // Current state
    pub winner_address: Option<Pubkey>, // Winner if drawn
    pub winning_ticket: Option<u64>, // The winning ticket that was drafted
}

pub fn create_raffle(
    ctx: Context<CreateRaffle>,
    metadata_uri: String,
    ticket_price: u64,
    end_time: i64,
    min_tickets: u64,
    max_tickets: Option<u64>,
) -> Result<()> {
    let current_time = Clock::get()?.unix_timestamp;

    // Validate inputs
    // URI format check - must start with one of the valid prefixes
    require!(
        VALID_URI_PREFIXES
            .iter()
            .any(|prefix| metadata_uri.starts_with(prefix)),
        RaffleError::InvalidMetadataUri
    );
    require!(metadata_uri.len() <= 256, RaffleError::MetadataUriTooLong);

    // Price checks
    require!(
        ticket_price >= MIN_TICKET_PRICE,
        RaffleError::TicketPriceTooLow
    );
    require!(
        ticket_price <= MAX_TICKET_PRICE,
        RaffleError::TicketPriceTooHigh
    );

    // Ticket count checks
    require!(min_tickets > 0, RaffleError::MinTicketsTooLow);
    require!(
        min_tickets <= MAX_MIN_TICKETS,
        RaffleError::MinTicketsTooHigh
    );

    // Check that max tickets is greater than or equal to min tickets
    if let Some(max_tickets) = max_tickets {
        require!(max_tickets >= min_tickets, RaffleError::MaxTicketsTooLow);
    }

    // Time checks
    require!(
        end_time > current_time.checked_add(MIN_DURATION).unwrap(),
        RaffleError::EndTimeTooClose
    );
    require!(
        end_time <= current_time.checked_add(MAX_DURATION).unwrap(),
        RaffleError::DurationTooLong
    );

    // Set inputs from transaction data
    ctx.accounts.raffle.metadata_uri = metadata_uri;
    ctx.accounts.raffle.ticket_price = ticket_price;
    ctx.accounts.raffle.min_tickets = min_tickets;
    ctx.accounts.raffle.end_time = end_time;
    ctx.accounts.raffle.treasury = ctx.accounts.treasury.key();
    ctx.accounts.treasury.bump = ctx.bumps.treasury;
    ctx.accounts.treasury.raffle = ctx.accounts.raffle.key();
    ctx.accounts.raffle.max_tickets = max_tickets;

    // Set default values
    ctx.accounts.raffle.current_tickets = 0;
    ctx.accounts.raffle.current_entries = 0;
    ctx.accounts.raffle.creation_time = current_time;
    ctx.accounts.raffle.raffle_state = RaffleState::Open;
    ctx.accounts.raffle.winner_address = None;
    ctx.accounts.raffle.winning_ticket = None;

    // Increment the raffle counter
    ctx.accounts.config.raffle_counter = ctx
        .accounts
        .config
        .raffle_counter
        .checked_add(1)
        .ok_or(RaffleError::Overflow)?;

    // Emit the raffle created event
    emit!(RaffleCreated {
        raffle: ctx.accounts.raffle.key(),
        metadata_uri: ctx.accounts.raffle.metadata_uri.clone(),
        ticket_price,
        min_tickets,
        end_time,
        creation_time: current_time,
    });

    Ok(())
}

2. Ticket System

The ticket system manages purchases, ownership tracking, and ticket distribution.

For Users:

  • Purchase multiple tickets in a single transaction

  • View ticket ownership and balance

  • Track all transactions on-chain

  • Secure entry tracking with randomized seeds

Technical Implementation:

pub fn buy_tickets(ctx: Context<BuyTickets>, ticket_count: u64, entry_seed: [u8; 8]) -> Result<()> {
    // Validate ticket count
    require!(ticket_count > 0, RaffleError::InvalidTicketCount);
    
    // Check if still allowed to buy tickets
    if let Some(max_tickets) = ctx.accounts.raffle.max_tickets {
        require!(
            ctx.accounts.raffle.current_tickets < max_tickets, 
            RaffleError::MaximumTicketsSold
        );

        require!(
            ctx.accounts.raffle.max_tickets >= ctx.accounts.raffle.current_tickets.checked_add(ticket_count), 
            RaffleError::PurchaseExceedsThreshold
        );
    }
    
    // Calculate payment amount with overflow protection
    let payment_amount = ticket_count
        .checked_mul(ctx.accounts.raffle.ticket_price)
        .ok_or(RaffleError::Overflow)?;
    
    // Validate buyer has sufficient funds using checked comparison
    require!(
        ctx.accounts.signer.lamports()
            .checked_sub(payment_amount)
            .ok_or(RaffleError::InsufficientFunds)? > 0,
        RaffleError::InsufficientFunds,
    );

    // Ensure treasury account matches the one stored in raffle
    require!(
        ctx.accounts.treasury.key() == ctx.accounts.raffle.treasury.key(),
        RaffleError::InvalidTreasury,
    );

    // Verify ticket balance account is initialized
    require!(
        ctx.accounts.ticket_balance.owner == ctx.accounts.signer.key(),
        RaffleError::TicketBalanceNotInitialized,
    );

    // Initialize entry data in the PDA
    // Each entry represents a single purchase transaction
    let entry = &mut ctx.accounts.entry;
    entry.raffle = ctx.accounts.raffle.key();
    entry.owner = ctx.accounts.signer.key();
    entry.ticket_count = ticket_count;
    entry.ticket_start_index = ctx.accounts.raffle.current_tickets;
    entry.seed = entry_seed;

    // Update raffle state with new ticket count using checked arithmetic
    ctx.accounts.raffle.current_tickets = ctx.accounts.raffle.current_tickets
        .checked_add(ticket_count)
        .ok_or(RaffleError::Overflow)?;

    // Update user's total ticket balance with overflow protection
    let ticket_balance = &mut ctx.accounts.ticket_balance;
    ticket_balance.ticket_count = ticket_balance.ticket_count
        .checked_add(ticket_count)
        .ok_or(RaffleError::Overflow)?;

    // Store pre-transfer balance for verification
    let pre_transfer_balance = ctx.accounts.treasury.to_account_info().lamports();

    // Transfer lamports from the buyer to the raffle treasury
    anchor_lang::solana_program::program::invoke(
        &anchor_lang::solana_program::system_instruction::transfer(
            &ctx.accounts.signer.key(),
            &ctx.accounts.treasury.key(),
            payment_amount,
        ),
        &[
            ctx.accounts.signer.to_account_info(),
            ctx.accounts.system_program.to_account_info(),
            ctx.accounts.treasury.to_account_info(),
        ],
    )?;

    // Verify the transfer was successful by checking treasury balance
    let post_transfer_balance = ctx.accounts.treasury.to_account_info().lamports();
    require!(
        post_transfer_balance == pre_transfer_balance.checked_add(payment_amount).ok_or(RaffleError::Overflow)?,
        RaffleError::TransferFailed
    );

    // Emit the tickets purchased event
    emit!(TicketsPurchased {
        raffle: ctx.accounts.raffle.key(),
        buyer: ctx.accounts.signer.key(),
        ticket_count,
        payment_amount,
        ticket_start_index: entry.ticket_start_index,
        entry_seed,
    });

    Ok(())
}

3. Treasury Management

Secure handling of funds throughout the raffle lifecycle.

For Users:

  • Automatic fund collection in treasury

  • Secure withdrawal process

  • Automatic refunds for expired raffles

  • Protected balance tracking

  • Treasury can only be managed via program instructions (No associated private key)

Technical Implementation:

#[account]
pub struct Treasury {
    pub raffle: Pubkey,
    pub bump: u8,
}

#[account(
    init,
    payer = management_authority,
    space = TREASURY_ACCOUNT_SIZE,
    seeds = [
        b"treasury",
        raffle.key().as_ref(),
    ],
    bump,
)]
pub treasury: Account<'info, Treasury>,

pub fn withdraw_from_treasury(ctx: Context<WithdrawFromTreasury>) -> Result<()> {
    // Verify that the threshold has been met
    require!(
        ctx.accounts.raffle.current_tickets >= ctx.accounts.raffle.min_tickets,
        RaffleError::ThresholdNotMet,
    );
    // Verify treasury account matches the one stored in raffle
    require!(
        ctx.accounts.treasury.key() == ctx.accounts.raffle.treasury,
        RaffleError::InvalidTreasury
    );
    let treasury_account = ctx.accounts.treasury.to_account_info();
    let payout_authority = ctx.accounts.payout_authority.to_account_info();

    // Get total balance including rent
    let treasury_balance = treasury_account.lamports();
    require!(treasury_balance > 0, RaffleError::InsufficientFunds);

    // Get rent exempt balance to make sure we don't deduct ALL lamports, as the raffle might still be open
    let rent_lamports = (Rent::get()?).minimum_balance(TREASURY_ACCOUNT_SIZE);
    let lamports_to_withdraw = treasury_balance - rent_lamports;

    // Transfer lamports by directly deducting from treasury and adding to payout_authority.
    // This only works because the treasury is a PDA owned by our program.
    treasury_account.sub_lamports(lamports_to_withdraw)?;
    payout_authority.add_lamports(lamports_to_withdraw)?;

    // Emit the treasury withdrawn event
    emit!(TreasuryWithdrawn {
        raffle: ctx.accounts.raffle.key(),
        amount: lamports_to_withdraw,
    });

    Ok(())
}

Winner Selection System

1. Randomness Implementation

The system uses a sophisticated multi-step process to ensure fair and verifiable winner selection.

For Users:

  • Transparent randomness source

  • Verifiable drawing process

  • Equal chance for every ticket

  • Resistant to manipulation

Technical Implementation:

pub fn draw_winning_ticket(ctx: Context<DrawWinningTicket>) -> Result<()> {
    // Manually validate the recent_slothashes account
    let pubkey_matches = Pubkey::from_str("SysvarS1otHashes111111111111111111111111111")
        .or(Err(RaffleError::InvalidSlotHashesAccount))?
        .eq(&ctx.accounts.recent_slothashes.key());
    require!(pubkey_matches, RaffleError::InvalidSlotHashesAccount);

    let recent_slothashes = &ctx.accounts.recent_slothashes;
    let data = recent_slothashes.data.borrow();

    // Extract entropy from SlotHashes data
    let chunk1 = array_ref![data, 12, 8];
    let chunk2 = if data.len() >= 28 {
        // Get second 8-byte block if available
        array_ref![data, 20, 8]
    } else {
        // Otherwise use the first block again
        chunk1
    };

    let hash_value1 = u64::from_le_bytes(*chunk1);
    let hash_value2 = u64::from_le_bytes(*chunk2);
    let clock = Clock::get()?;
    let timestamp = clock.unix_timestamp as u64;

    // Combine entropy sources through cryptographic mixing
    let mut mixed_value = mix(hash_value1, timestamp);
    mixed_value = mix(mixed_value, hash_value2);

    // Map the random value to a ticket number without statistical bias
    let winning_ticket = unbiased_range(mixed_value, ctx.accounts.raffle.current_tickets)?;

    // Store winning ticket and update state
    ctx.accounts.raffle.winning_ticket = Some(winning_ticket);
    ctx.accounts.raffle.raffle_state = RaffleState::Drawing;

    Ok(())
}

/// Cryptographic mixing function with strong avalanche properties
/// Each bit in the output has a ~50% chance of flipping when any input bit changes.
/// Based on splitmix64 algorithm used in high-quality PRNGs.
fn mix(a: u64, b: u64) -> u64 {
    let mut z = a.wrapping_add(b);

    z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
    z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
    z = z ^ (z >> 31);

    z
}

/// Maps a random number to a range without introducing statistical bias
/// Standard modulo operations can bias results when the range isn't a power of 2.
/// This function uses specialized techniques based on range size to ensure fairness.
fn unbiased_range(x: u64, range: u64) -> Result<u64> {
    if range == 0 {
        return Err(RaffleError::Overflow.into());
    }

    // If range is a power of 2, we can use a simple mask which is unbiased
    if range.is_power_of_two() {
        return Ok(x & (range - 1));
    }

    // For small ranges, simple modulo is fine as bias is minimal
    if range <= 256 {
        return Ok(x % range);
    }

    // Find threshold value to ensure unbiased selection
    let threshold = u64::MAX - (u64::MAX % range);

    // Use rejection sampling with a limit on computational cost
    let mut value = x;
    const MAX_ATTEMPTS: u8 = 3;

    for i in 0..MAX_ATTEMPTS {
        // If value is below threshold, we can use modulo safely
        if value < threshold {
            return Ok(value % range);
        }

        // Try a new value with additional mixing
        value = mix(value, value.wrapping_add(i as u64 + 1));
    }

    // Fallback case - the bias is minimal after the mixing operations
    Ok(value % range)
}

2. Winner Determination

Once a winning ticket is drawn, the system identifies the owner and updates the raffle state.

Technical Implementation:

pub fn set_winner(ctx: Context<SetWinner>, _entry_seed: [u8; 8]) -> Result<()> {
    // Get the winning ticket number
    let winning_ticket = ctx
        .accounts
        .raffle
        .winning_ticket
        .ok_or(RaffleError::NoWinningTicket)?;

    // Verify the entry contains the winning ticket
    let entry = &ctx.accounts.entry;
    require!(
        winning_ticket >= entry.ticket_start_index
            && winning_ticket
                < entry
                    .ticket_start_index
                    .checked_add(entry.ticket_count)
                    .ok_or(RaffleError::Overflow)?,
        RaffleError::InvalidWinningEntry
    );

    // Set the winner and update state
    ctx.accounts.raffle.winner_address = Some(entry.owner);
    ctx.accounts.raffle.raffle_state = RaffleState::Drawn;

    // Emit winner set event
    emit!(WinnerSet {
        raffle: ctx.accounts.raffle.key(),
        winner: entry.owner,
        winning_ticket,
    });

    Ok(())
}

Security Features

1. Input Validation

The system implements strict validation for all inputs to prevent exploitation.

For Users:

  • Protected against invalid parameters

  • Secure price and ticket limits

  • Time constraint validation

  • URI format verification

Technical Implementation:

// Constants for validation
const MAX_TICKET_PRICE: u64 = 100_000_000_000; // 100 SOL
const MIN_TICKET_PRICE: u64 = 100_000_000; // 0.1 SOL
const MAX_MIN_TICKETS: u64 = 1_000_000; // 1 million tickets
const MAX_DURATION: i64 = 30 * 24 * 60 * 60; // 30 days
const MIN_DURATION: i64 = 1 * 60 * 60; // 1 hour

// URI validation
const VALID_URI_PREFIXES: [&str; 3] = [
    "https://",
    "ipfs://",
    "ipfs://ipfs/",
];

// Validation checks in create_raffle
require!(
    VALID_URI_PREFIXES
        .iter()
        .any(|prefix| metadata_uri.starts_with(prefix)),
    RaffleError::InvalidMetadataUri
);
require!(metadata_uri.len() <= 256, RaffleError::MetadataUriTooLong);

// Price checks
require!(
    ticket_price >= MIN_TICKET_PRICE,
    RaffleError::TicketPriceTooLow
);
require!(
    ticket_price <= MAX_TICKET_PRICE,
    RaffleError::TicketPriceTooHigh
);

// Ticket count checks
require!(min_tickets > 0, RaffleError::MinTicketsTooLow);
require!(
    min_tickets <= MAX_MIN_TICKETS,
    RaffleError::MinTicketsTooHigh
);

// Max ticket validation (if set)
if let Some(max_tickets) = max_tickets {
    require!(max_tickets >= min_tickets, RaffleError::MaxTicketsTooLow);
}

// Time checks
require!(
    end_time > current_time.checked_add(MIN_DURATION).unwrap(),
    RaffleError::EndTimeTooClose
);
require!(
    end_time <= current_time.checked_add(MAX_DURATION).unwrap(),
    RaffleError::DurationTooLong
);

2. Access Control

Robust permission system ensuring only authorized operations.

For Users:

  • Secure account initialization

  • Protected administrative functions

  • PDAs only allow data modification through program instructions

  • PDAs have no private keys, so funds can only be moved via program instructions

Technical Implementation:

#[derive(Accounts)]
pub struct CreateRaffle<'info> {
    #[account(
        init,
        payer = management_authority,
        space = RAFFLE_ACCOUNT_SIZE,
        seeds = [
            b"raffle",
            config.raffle_counter.to_le_bytes().as_ref(),
        ],
        bump
    )]
    pub raffle: Account<'info, Raffle>,

    #[account(mut)]
    pub management_authority: Signer<'info>,

    #[account(
        init,
        payer = management_authority,
        space = TREASURY_ACCOUNT_SIZE,
        seeds = [
            b"treasury",
            raffle.key().as_ref(),
        ],
        bump,
    )]
    pub treasury: Account<'info, Treasury>,

    /// The config account storing upgrade, management and payout authorities, and raffle counter
    #[account(
        mut,
        seeds = [b"config"],
        bump = config.bump,
        has_one = management_authority @ RaffleError::NotProgramManagementAuthority,
    )]
    pub config: Account<'info, Config>,

    pub system_program: Program<'info, System>,
}

3. Financial Security

Multiple layers of protection for financial operations.

For Users:

  • Protected fund transfers

  • Balance verification

  • Overflow protection

  • Automated treasury management

Technical Implementation:

// Safe arithmetic operations
let payment_amount = ticket_count
    .checked_mul(ctx.accounts.raffle.ticket_price)
    .ok_or(RaffleError::Overflow)?;

// Prevent withdrawal of rent to ensure account remains valid
let rent_lamports = (Rent::get()?).minimum_balance(TREASURY_ACCOUNT_SIZE);
let lamports_to_withdraw = treasury_balance - rent_lamports;

// Transfer verification
let pre_transfer_balance = ctx.accounts.treasury.to_account_info().lamports();
// ... perform transfer ...
let post_transfer_balance = ctx.accounts.treasury.to_account_info().lamports();
require!(
    post_transfer_balance == pre_transfer_balance
        .checked_add(payment_amount)
        .ok_or(RaffleError::Overflow)?,
    RaffleError::TransferFailed
);

4. Enhanced Randomness

The system uses a sophisticated randomness implementation to ensure fair and unpredictable selection.

For Users:

  • Multiple entropy sources

  • Cryptographic mixing

  • Unbiased selection

  • Statistical fairness

Technical Implementation:

// Extract multiple entropy sources
let chunk1 = array_ref![data, 12, 8];
let chunk2 = if data.len() >= 28 {
    array_ref![data, 20, 8]
} else {
    chunk1
};

let hash_value1 = u64::from_le_bytes(*chunk1);
let hash_value2 = u64::from_le_bytes(*chunk2);
let timestamp = clock.unix_timestamp as u64;

// Apply cryptographic mixing
let mut mixed_value = mix(hash_value1, timestamp);
mixed_value = mix(mixed_value, hash_value2);

// Generate unbiased random number
let winning_ticket = unbiased_range(mixed_value, ctx.accounts.raffle.current_tickets)?;

Refund System

The contract includes functionality for users to reclaim funds from expired raffles.

For Users:

  • Automatic eligibility for refunds if raffle expires

  • Full refund of ticket purchase amount

  • Secure transfer process

Technical Implementation:

pub fn reclaim_expired_tickets(ctx: Context<ReclaimExpiredTickets>) -> Result<()> {
    require!(
        ctx.accounts.raffle.raffle_state == RaffleState::Expired, 
        RaffleError::RaffleNotExpired
    );
    require!(
        ctx.accounts.signer.key() == ctx.accounts.ticket_balance.owner,
        RaffleError::OwnerMismatch
    );
    require!(
        ctx.accounts.raffle.treasury.key() == ctx.accounts.treasury.key(),
        RaffleError::InvalidTreasury
    );
    require!(
        ctx.accounts.ticket_balance.ticket_count > 0,
        RaffleError::NoTicketsOwned
    );

    let from_pubkey = ctx.accounts.treasury.to_account_info();
    let to_pubkey = ctx.accounts.signer.to_account_info();

    // Transfer lamports by directly deducting from treasury and adding to signer. 
    // This only works because the treasury is a PDA owned by our program.
    let total_lamports_to_transfer = ctx.accounts.ticket_balance.ticket_count * ctx.accounts.raffle.ticket_price;
    from_pubkey.sub_lamports(total_lamports_to_transfer)?;
    to_pubkey.add_lamports(total_lamports_to_transfer)?;

    Ok(())
}

State Management

Raffle States

The system maintains clear state transitions throughout the raffle lifecycle.

For Users:

  • Open: Active and accepting tickets

  • Drawing: Winning ticket drawn, looking for entry with ticket

  • Drawn: Winner selected

  • Expired: Failed to meet minimum tickets

  • Claimed: Prize claimed by winner

Technical Implementation:

#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
pub enum RaffleState {
    Open = 0,
    Drawing = 1,
    Drawn = 2,
    Expired = 3,
    Claimed = 4,
}

Event System

The contract emits events for important actions to provide transparency and auditability.

Technical Implementation:

/// Event emitted when a raffle is created
#[event]
pub struct RaffleCreated {
    /// The pubkey of the created raffle
    pub raffle: Pubkey,
    /// The metadata URI for the raffle
    pub metadata_uri: String,
    /// Price per ticket in lamports
    pub ticket_price: u64,
    /// Minimum number of tickets required
    pub min_tickets: u64,
    /// When the raffle ends
    pub end_time: i64,
    /// When the raffle was created
    pub creation_time: i64,
}

/// Event emitted when tickets are purchased
#[event]
pub struct TicketsPurchased {
    /// The pubkey of the raffle
    pub raffle: Pubkey,
    /// The buyer's address
    pub buyer: Pubkey,
    /// Number of tickets purchased
    pub ticket_count: u64,
    /// Total amount paid in lamports
    pub payment_amount: u64,
    /// Starting ticket index for this purchase
    pub ticket_start_index: u64,
    /// The seed that was used to create the entry
    pub entry_seed: [u8; 8],
}

/// Event emitted when a winner is set for a raffle
#[event]
pub struct WinnerSet {
    /// The pubkey of the raffle
    pub raffle: Pubkey,
    /// The winner's address
    pub winner: Pubkey,
    /// The winning ticket number
    pub winning_ticket: u64,
}

/// Event emitted when a winner submits their encrypted data
#[event]
pub struct WinnerDataSubmitted {
    /// The pubkey of the raffle
    pub raffle: Pubkey,
}

/// Event emitted when treasury funds are withdrawn
#[event]
pub struct TreasuryWithdrawn {
    /// The pubkey of the raffle
    pub raffle: Pubkey,
    /// Amount withdrawn in lamports
    pub amount: u64,
}

/// Event emitted when a raffle is expired
#[event]
pub struct RaffleExpired {
    /// The pubkey of the expired raffle
    pub raffle: Pubkey,
    /// The timestamp when the raffle was expired
    pub expired_at: i64,
    /// The final number of tickets sold
    pub final_ticket_count: u64,
}

Error Handling System

Error Definitions

The contract implements comprehensive error handling for all operations.

For Users:

  • Clear error messages

  • Specific error types

  • Input validation errors

  • State transition errors

  • Financial operation errors

Technical Implementation:

#[error_code]
pub enum RaffleError {
    #[msg("Raffle is not in Open state")]
    RaffleNotOpen,
    #[msg("Raffle is not in Drawing state")]
    RaffleNotDrawing,
    #[msg("Raffle is not in Drawn state")]
    RaffleNotDrawn,
    #[msg("Raffle is not in Expired state")]
    RaffleNotExpired,
    #[msg("Raffle has not ended yet")]
    RaffleNotEnded,
    #[msg("Raffle has already ended")]
    RaffleEnded,
    #[msg("Ticket price is too low")]
    TicketPriceTooLow,
    #[msg("Ticket price is too high")]
    TicketPriceTooHigh,
    #[msg("Minimum tickets threshold is too low")]
    MinTicketsTooLow,
    #[msg("Minimum tickets threshold is too high")]
    MinTicketsTooHigh,
    #[msg("Maximum tickets is too low")]
    MaxTicketsTooLow,
    #[msg("Maximum tickets have been sold")]
    MaximumTicketsSold,
    #[msg("Purchase exceeds maximum ticket threshold")]
    PurchaseExceedsThreshold,
    #[msg("End time is too close to current time")]
    EndTimeTooClose,
    #[msg("Raffle duration is too long")]
    DurationTooLong,
    #[msg("Invalid metadata URI")]
    InvalidMetadataUri,
    #[msg("Metadata URI is too long")]
    MetadataUriTooLong,
    #[msg("Invalid ticket count")]
    InvalidTicketCount,
    #[msg("Insufficient funds")]
    InsufficientFunds,
    #[msg("Invalid treasury account")]
    InvalidTreasury,
    #[msg("Transfer failed")]
    TransferFailed,
    #[msg("Arithmetic overflow")]
    Overflow,
    #[msg("Minimum ticket threshold has not been met")]
    InsufficientTickets,
    #[msg("Minimum ticket threshold has not been met")]
    ThresholdNotMet,
    #[msg("Minimum ticket threshold has been met")]
    ThresholdIsMet,
    #[msg("Invalid SlotHashes account")]
    InvalidSlotHashesAccount,
    #[msg("No winning ticket has been drawn")]
    NoWinningTicket,
    #[msg("Invalid winning entry")]
    InvalidWinningEntry,
    #[msg("Owner mismatch")]
    OwnerMismatch,
    #[msg("No tickets owned")]
    NoTicketsOwned,
    #[msg("Ticket balance not initialized")]
    TicketBalanceNotInitialized,
    #[msg("Config not initialized")]
    ConfigNotInitialized,
    #[msg("Not program management authority")]
    NotProgramManagementAuthority,
    #[msg("Not program payout authority")]
    NotPayoutAuthority,
    #[msg("Not the winner")]
    NotWinner,
    #[msg("Invalid data length")]
    InvalidDataLength,
}
PreviousArchitectureNextRaffle System

Last updated 2 months ago