Skip to main content

percli_program/instructions/
deposit.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked};
3
4use crate::error::{from_risk_error, PercolatorError};
5use crate::instructions::events;
6use crate::state::{engine_from_account_data, header_from_account_data, MARKET_ACCOUNT_SIZE};
7
8#[derive(Accounts)]
9pub struct Deposit<'info> {
10    #[account(mut)]
11    pub user: Signer<'info>,
12
13    /// CHECK: Validated via owner, discriminator, and size.
14    #[account(
15        mut,
16        owner = crate::ID @ PercolatorError::AccountNotFound,
17        constraint = market.data_len() >= MARKET_ACCOUNT_SIZE @ PercolatorError::AccountNotFound,
18    )]
19    pub market: UncheckedAccount<'info>,
20
21    /// The collateral mint for this market.
22    pub mint: Account<'info, Mint>,
23
24    /// User's token account to transfer from.
25    #[account(
26        mut,
27        constraint = user_token_account.owner == user.key(),
28        constraint = user_token_account.mint == mint.key(),
29    )]
30    pub user_token_account: Account<'info, TokenAccount>,
31
32    /// Vault token account to transfer into.
33    #[account(
34        mut,
35        seeds = [b"vault", market.key().as_ref()],
36        bump,
37        constraint = vault.mint == mint.key(),
38    )]
39    pub vault: Account<'info, TokenAccount>,
40
41    pub token_program: Program<'info, Token>,
42}
43
44pub fn handler(ctx: Context<Deposit>, account_idx: u16, amount: u64) -> Result<()> {
45    require!(amount > 0, PercolatorError::InsufficientBalance);
46
47    // Transfer tokens from user to vault
48    transfer_checked(
49        CpiContext::new(
50            ctx.accounts.token_program.key(),
51            TransferChecked {
52                from: ctx.accounts.user_token_account.to_account_info(),
53                to: ctx.accounts.vault.to_account_info(),
54                authority: ctx.accounts.user.to_account_info(),
55                mint: ctx.accounts.mint.to_account_info(),
56            },
57        ),
58        amount,
59        ctx.accounts.mint.decimals,
60    )?;
61
62    // Update engine state
63    let market = &ctx.accounts.market;
64    let mut data = market.try_borrow_mut_data()?;
65
66    require!(
67        &data[0..8] == b"percmrkt",
68        PercolatorError::AccountNotFound
69    );
70
71    let header = header_from_account_data(&data)?;
72    require!(header.mint == ctx.accounts.mint.key(), PercolatorError::Unauthorized);
73
74    let engine = engine_from_account_data(&mut data);
75
76    // If account already exists, verify the signer is the owner
77    let user_key = ctx.accounts.user.key();
78    if engine.is_used(account_idx as usize) {
79        let existing_owner = engine.accounts[account_idx as usize].owner;
80        if existing_owner != [0u8; 32] {
81            require!(existing_owner == user_key.to_bytes(), PercolatorError::Unauthorized);
82        }
83    }
84
85    let oracle_price = engine.last_oracle_price;
86    let clock = Clock::get()?;
87
88    engine
89        .deposit(account_idx, amount as u128, oracle_price, clock.slot)
90        .map_err(from_risk_error)?;
91
92    // If this was a new account (owner is zero), claim it for the depositor
93    let _ = engine.set_owner(account_idx, user_key.to_bytes());
94
95    emit!(events::Deposited {
96        user: ctx.accounts.user.key(),
97        account_idx,
98        amount,
99    });
100
101    Ok(())
102}