Untitled

 avatar
unknown
plain_text
a year ago
27 kB
8
Indexable
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};

// Declare the program ID
declare_id!("GTwvccoDjKRUB936wqrF3jvAdpwEJGZWZLShEy4PAPxs");

#[program]
mod multi_token {
    use super::*;

    /// Initializes the escrow account with zero balances for all tokens and sets the authority and mint addresses.
    pub fn initialize_escrow(
        ctx: Context<InitializeEscrow>,
        usdc_mint: Pubkey,
        bonk_mint: Pubkey,
        send_mint: Pubkey,
    ) -> Result<()> {
        let escrow_account = &mut ctx.accounts.escrow_account;
        // Initialize balances to 0
        escrow_account.sol_balance = 0;
        escrow_account.usdc_balance = 0;
        escrow_account.bonk_balance = 0;
        escrow_account.send_balance = 0;
        // Set the authority and mint addresses
        escrow_account.authority = ctx.accounts.owner.key();
        escrow_account.usdc_mint = usdc_mint;
        escrow_account.bonk_mint = bonk_mint;
        escrow_account.send_mint = send_mint;
        Ok(())
    }

    /// Allows a user to participate in a challenge by transferring the specified token amount to the escrow account.
    pub fn participate<'info>(
        ctx: Context<'_, '_, '_, 'info, Participate<'info>>,
        token: String,
        amount: u64,
        challenge_id: u64,
        player_id: Option<u64>,
        participation_type: ParticipationType,
    ) -> Result<()> {
        // Determine the number of decimals for the token and adjust the amount accordingly
        let adjusted_amount = match token.as_str() {
            "SOL" => amount, // Use the amount directly for SOL as it is already in lamports
            "USDC" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust USDC amount
            "BONK" => amount
                .checked_mul(10_u64.pow(5))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust BONK amount
            "SEND" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust SEND amount
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        };

        match token.as_str() {
            "SOL" => {
                // Transfer SOL to the escrow account
                let sol_transfer = anchor_lang::solana_program::system_instruction::transfer(
                    &ctx.accounts.user.key(),
                    &ctx.accounts.escrow_account.key(),
                    adjusted_amount,
                );
                anchor_lang::solana_program::program::invoke(
                    &sol_transfer,
                    &[
                        ctx.accounts.user.to_account_info().clone(),
                        ctx.accounts.escrow_account.to_account_info().clone(),
                        ctx.accounts.system_program.to_account_info().clone(),
                    ],
                )?;
                // Update the SOL balance in the escrow account
                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.sol_balance += adjusted_amount;
            }
            "USDC" => {
                // Transfer USDC to the escrow account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.user_token_account.to_account_info(),
                    to: ctx.accounts.escrow_token_account.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;
                // Update the USDC balance in the escrow account
                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.usdc_balance += adjusted_amount;
            }
            "BONK" => {
                // Transfer BONK to the escrow account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.user_token_account.to_account_info(),
                    to: ctx.accounts.escrow_token_account.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;
                // Update the BONK balance in the escrow account
                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.bonk_balance += adjusted_amount;
            }
            "SEND" => {
                // Transfer SEND to the escrow account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.user_token_account.to_account_info(),
                    to: ctx.accounts.escrow_token_account.to_account_info(),
                    authority: ctx.accounts.user.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;
                // Update the SEND balance in the escrow account
                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.send_balance += adjusted_amount;
            }
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        }

        // Emit an event for the participation
        emit!(ParticipateEvent {
            user: ctx.accounts.user.key(),
            token: token.clone(),
            amount,
            challenge_id,
            player_id,
            participation_type: participation_type.clone(),
        });

        Ok(())
    }

    /// Handles false settlements by transferring the specified token amount from the escrow account to the user account.
    pub fn false_settlement(
        ctx: Context<FalseSettlement>,
        token: String,
        amount: u64,
        txn_id: String,
    ) -> Result<()> {
        // Adjust the amount based on the token's decimals
        let adjusted_amount = match token.as_str() {
            "SOL" => amount, // Use the amount directly for SOL as it is already in lamports
            "USDC" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust USDC amount
            "BONK" => amount
                .checked_mul(10_u64.pow(5))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust BONK amount
            "SEND" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust SEND amount
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        };

        // Ensure the authority is valid
        require!(
            ctx.accounts.escrow_account.authority == *ctx.accounts.authority.key,
            ErrorCode::Unauthorized
        );

        // Get the rent exemption amount for an account with 0 bytes
        let rent_exemption_amount = Rent::get()?.minimum_balance(0);

        match token.as_str() {
            "SOL" => {
                let escrow_balance = **ctx
                    .accounts
                    .escrow_account
                    .to_account_info()
                    .lamports
                    .borrow();

                msg!(
                    "Escrow account SOL balance: {} adjustedAmount {} rentExemption {}",
                    escrow_balance,
                    adjusted_amount,
                    rent_exemption_amount
                );

                // Check if there are enough SOL funds in the escrow account including rent exemption
                if escrow_balance < adjusted_amount + rent_exemption_amount {
                    return Err(ErrorCode::InsufficientFunds.into());
                }

                // Perform SOL transfer from escrow to user account
                **ctx
                    .accounts
                    .escrow_account
                    .to_account_info()
                    .try_borrow_mut_lamports()? -= adjusted_amount;
                **ctx
                    .accounts
                    .user_account
                    .to_account_info()
                    .try_borrow_mut_lamports()? += adjusted_amount;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.sol_balance -= adjusted_amount;

                // Emit an event for the false settlement
                emit!(FalseSettlementEvent {
                    user: ctx.accounts.user_account.key(),
                    token: "SOL".to_string(),
                    amount,
                    txn_id
                });
            }
            "USDC" => {
                // Perform USDC transfer from escrow to user account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.user_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.usdc_balance -= adjusted_amount;

                // Emit an event for the false settlement
                emit!(FalseSettlementEvent {
                    user: ctx.accounts.user_account.key(),
                    token: token.clone(),
                    amount,
                    txn_id
                });
            }
            "BONK" => {
                // Perform BONK transfer from escrow to user account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.user_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.bonk_balance -= adjusted_amount;

                // Emit an event for the false settlement
                emit!(FalseSettlementEvent {
                    user: ctx.accounts.user_account.key(),
                    token: token.clone(),
                    amount,
                    txn_id
                });
            }
            "SEND" => {
                // Perform SEND transfer from escrow to user account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.user_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.send_balance -= adjusted_amount;

                // Emit an event for the false settlement
                emit!(FalseSettlementEvent {
                    user: ctx.accounts.user_account.key(),
                    token: token.clone(),
                    amount,
                    txn_id
                });
            }
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        }

        Ok(())
    }

    /// Sends tokens from the escrow account to the admin account.
    pub fn send(ctx: Context<Send>, token: String, amount: u64) -> Result<()> {
        let adjusted_amount = match token.as_str() {
            "SOL" => amount, // Use the amount directly for SOL as it is already in lamports
            "USDC" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust USDC amount
            "BONK" => amount
                .checked_mul(10_u64.pow(5))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust BONK amount
            "SEND" => amount
                .checked_mul(10_u64.pow(6))
                .ok_or(ErrorCode::InvalidAmount)?, // Adjust SEND amount
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        };

        // Ensure the authority is valid
        require!(
            ctx.accounts.escrow_account.authority == *ctx.accounts.authority.key,
            ErrorCode::Unauthorized
        );

        match token.as_str() {
            "SOL" => {
                // Check if there are enough SOL funds in the escrow account
                if **ctx
                    .accounts
                    .escrow_account
                    .to_account_info()
                    .lamports
                    .borrow()
                    < adjusted_amount
                {
                    return Err(ErrorCode::InsufficientFunds.into());
                }

                // Perform SOL transfer from escrow to admin account
                **ctx
                    .accounts
                    .escrow_account
                    .to_account_info()
                    .try_borrow_mut_lamports()? -= adjusted_amount;
                **ctx
                    .accounts
                    .admin_account
                    .to_account_info()
                    .try_borrow_mut_lamports()? += adjusted_amount;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.sol_balance -= adjusted_amount;

                // Emit an event for the send operation
                emit!(SendEvent {
                    to: ctx.accounts.escrow_account.authority,
                    token: "SOL".to_string(),
                    amount,
                });
            }
            "USDC" => {
                // Perform USDC transfer from escrow to admin account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.admin_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.usdc_balance -= adjusted_amount;

                // Emit an event for the send operation
                emit!(SendEvent {
                    to: ctx.accounts.escrow_account.authority,
                    token: token.clone(),
                    amount,
                });
            }
            "BONK" => {
                // Perform BONK transfer from escrow to admin account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.admin_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.bonk_balance -= adjusted_amount;

                // Emit an event for the send operation
                emit!(SendEvent {
                    to: ctx.accounts.escrow_account.authority,
                    token: token.clone(),
                    amount,
                });
            }
            "SEND" => {
                // Perform SEND transfer from escrow to admin account using CPI
                let cpi_accounts = Transfer {
                    from: ctx.accounts.escrow_token_account.to_account_info(),
                    to: ctx.accounts.admin_token_account.to_account_info(),
                    authority: ctx.accounts.escrow_account.to_account_info(),
                };
                let cpi_program = ctx.accounts.token_program.to_account_info();
                let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                token::transfer(cpi_ctx, adjusted_amount)?;

                let escrow_account = &mut ctx.accounts.escrow_account;
                escrow_account.send_balance -= adjusted_amount;

                // Emit an event for the send operation
                emit!(SendEvent {
                    to: ctx.accounts.escrow_account.authority,
                    token: token.clone(),
                    amount,
                });
            }
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        }

        Ok(())
    }

    /// Settles a challenge by transferring the specified token amounts from the escrow account to the winners.
    pub fn settle_challenge<'a, 'b>(
        ctx: Context<'_, '_, '_, 'b, SettleChallenge<'b>>,
        token: String,
        amounts: Vec<u64>,
        challenge_id: u64,
    ) -> Result<()> {
        // Determine the number of decimals for the token
        let decimals = match token.as_str() {
            "SOL" => 9,
            "USDC" => 6,
            "BONK" => 5,
            "SEND" => 6,
            _ => return Err(ErrorCode::UnsupportedCurrency.into()),
        };

        // Ensure the authority is valid
        require!(
            ctx.accounts.escrow_account.authority == *ctx.accounts.authority.key,
            ErrorCode::Unauthorized
        );

        // Iterate through each winner and transfer the respective amount
        for (winner, &amount) in ctx.remaining_accounts.iter().zip(amounts.iter()) {
            let adjusted_amount = if token.as_str() == "SOL" {
                amount
            } else {
                amount
                    .checked_mul(10_u64.pow(decimals as u32))
                    .ok_or(ErrorCode::InvalidAmount)?
            };

            match token.as_str() {
                "SOL" => {
                    // Check if there are enough SOL funds in the escrow account
                    if **ctx
                        .accounts
                        .escrow_account
                        .to_account_info()
                        .lamports
                        .borrow()
                        < adjusted_amount
                    {
                        return Err(ErrorCode::InsufficientFunds.into());
                    }

                    // Perform SOL transfer from escrow to winner
                    **ctx
                        .accounts
                        .escrow_account
                        .to_account_info()
                        .try_borrow_mut_lamports()? -= adjusted_amount;
                    **winner.try_borrow_mut_lamports()? += adjusted_amount;

                    let escrow_account = &mut ctx.accounts.escrow_account;
                    escrow_account.sol_balance -= adjusted_amount;
                }
                "USDC" => {
                    // Perform USDC transfer from escrow to winner using CPI
                    let cpi_accounts = Transfer {
                        from: ctx.accounts.escrow_token_account.to_account_info().clone(),
                        to: winner.clone(),
                        authority: ctx.accounts.escrow_account.to_account_info().clone(),
                    };
                    let cpi_program = ctx.accounts.token_program.to_account_info();
                    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                    token::transfer(cpi_ctx, adjusted_amount)?;

                    let escrow_account = &mut ctx.accounts.escrow_account;
                    escrow_account.usdc_balance -= adjusted_amount;
                }
                "BONK" => {
                    // Perform BONK transfer from escrow to winner using CPI
                    let cpi_accounts = Transfer {
                        from: ctx.accounts.escrow_token_account.to_account_info().clone(),
                        to: winner.clone(),
                        authority: ctx.accounts.escrow_account.to_account_info().clone(),
                    };
                    let cpi_program = ctx.accounts.token_program.to_account_info();
                    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                    token::transfer(cpi_ctx, adjusted_amount)?;

                    let escrow_account = &mut ctx.accounts.escrow_account;
                    escrow_account.bonk_balance -= adjusted_amount;
                }
                "SEND" => {
                    // Perform SEND transfer from escrow to winner using CPI
                    let cpi_accounts = Transfer {
                        from: ctx.accounts.escrow_token_account.to_account_info().clone(),
                        to: winner.clone(),
                        authority: ctx.accounts.escrow_account.to_account_info().clone(),
                    };
                    let cpi_program = ctx.accounts.token_program.to_account_info();
                    let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
                    token::transfer(cpi_ctx, adjusted_amount)?;

                    let escrow_account = &mut ctx.accounts.escrow_account;
                    escrow_account.send_balance -= adjusted_amount;
                }
                _ => return Err(ErrorCode::UnsupportedCurrency.into()),
            }
        }

        // Emit an event for the challenge settlement
        emit!(SettleChallengeEvent {
            token,
            amounts,
            challenge_id,
        });

        Ok(())
    }
}

// Define the account structures and constraints
#[derive(Accounts)]
pub struct InitializeEscrow<'info> {
    #[account(init, payer = owner, space = 8 + 8 + 8 + 8 + 8 + 32 + 32 + 32 + 32)]
    pub escrow_account: Account<'info, EscrowAccount>,
    #[account(mut)]
    pub owner: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Participate<'info> {
    #[account(mut)]
    pub user: Signer<'info>,
    #[account(mut)]
    pub user_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub escrow_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub escrow_account: Account<'info, EscrowAccount>,
    pub system_program: Program<'info, System>,
    pub token_program: Program<'info, Token>,
}

#[derive(Accounts)]
pub struct FalseSettlement<'info> {
    #[account(mut, signer)]
    pub escrow_account: Account<'info, EscrowAccount>,
    #[account(mut)]
    pub escrow_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub user_account: AccountInfo<'info>,
    #[account(mut)]
    pub user_token_account: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

#[derive(Accounts)]
pub struct Send<'info> {
    #[account(mut, signer)]
    pub escrow_account: Account<'info, EscrowAccount>,
    #[account(mut)]
    pub escrow_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub admin_account: AccountInfo<'info>,
    #[account(mut)]
    pub admin_token_account: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

#[derive(Accounts)]
pub struct SettleChallenge<'info> {
    #[account(mut, signer)]
    pub escrow_account: Account<'info, EscrowAccount>,
    #[account(mut)]
    pub escrow_token_account: Account<'info, TokenAccount>,
    #[account(mut)]
    pub authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

// Define the escrow account structure
#[account]
pub struct EscrowAccount {
    pub sol_balance: u64,
    pub usdc_balance: u64,
    pub bonk_balance: u64,
    pub send_balance: u64,
    pub authority: Pubkey,
    pub usdc_mint: Pubkey,
    pub bonk_mint: Pubkey,
    pub send_mint: Pubkey,
}

// Define participation types
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum ParticipationType {
    SideBet,
    JoinChallenge,
}

// Define events for logging
#[event]
pub struct ParticipateEvent {
    pub user: Pubkey,
    pub token: String,
    pub amount: u64,
    pub challenge_id: u64,
    pub player_id: Option<u64>,
    pub participation_type: ParticipationType,
}

#[event]
pub struct SettleChallengeEvent {
    pub token: String,
    pub amounts: Vec<u64>,
    pub challenge_id: u64,
}

#[event]
pub struct FalseSettlementEvent {
    pub user: Pubkey,
    pub token: String,
    pub amount: u64,
    pub txn_id: String,
}

#[event]
pub struct SendEvent {
    pub to: Pubkey,
    pub token: String,
    pub amount: u64,
}

// Define custom error codes
#[error_code]
pub enum ErrorCode {
    #[msg("The requested operation is not authorized.")]
    Unauthorized,

    #[msg("Insufficient funds to complete the operation.")]
    InsufficientFunds,

    #[msg("The specified currency is not supported.")]
    UnsupportedCurrency,

    #[msg("Failed to perform the transfer.")]
    TransferFailed,

    #[msg("Invalid input provided.")]
    InvalidInput,

    #[msg("Invalid amount provided.")]
    InvalidAmount,

    #[msg("Failed to get current time.")]
    TimeError,

    #[msg("Invalid admin wallet public key.")]
    InvalidAdminWallet,

    #[msg("Invalid mint address provided.")]
    InvalidMint,
}
Editor is loading...
Leave a Comment