Deposit
The deposit instruction performs three main tasks:
Deposit the
mint_xandmint_ytoken based on the amount of LP the user wants tomint.Calculate the amount to deposit and check that the amount isn't greater than
max_xandmax_ydesignated by the user.Mint the right amount of
mint_lpin the user ata.
Required Accounts
Below are the accounts required for this context:
user: The user that is depositing the token into the liquidity of the Amm. Must be asigner.mint_lp: The Mint account that will represent the pool’s liquidity. Must be passed asmutable.vault_x: The token account that holds all of token X deposited into the pool. Must be passed asmutable.vault_y: The token account that holds all of token Y deposited into the pool. Must be passed asmutable.user_x_ata: The user's associated token account for token X. This is the source account from which the user's token X will be transferred into the pool. Must be passed asmutable.user_y_ata: The user's associated token account for token Y. This is the source account from which the user's token Y will be transferred into the pool. Must be passed asmutable.user_lp_ata: The user's associated token account for LP tokens. This is the destination account where LP tokens will be minted. Must be passed asmutable.config: The configuration account for the AMM pool. Stores all relevant pool parameters and state.token program: The SPL Token program account. This is required to perform token operations such as transfers and minting. Must beexecutable.
Here, again, I’ll leave the implementation to you:
pub struct DepositAccounts<'a> {
pub user: &'a AccountInfo,
pub mint_lp: &'a AccountInfo,
pub vault_x: &'a AccountInfo,
pub vault_y: &'a AccountInfo,
pub user_x_ata: &'a AccountInfo,
pub user_y_ata: &'a AccountInfo,
pub user_lp_ata: &'a AccountInfo,
pub config: &'a AccountInfo,
pub token_program: &'a AccountInfo,
}
impl<'a> TryFrom<&'a [AccountInfo]> for DepositAccounts<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo]) -> Result<Self, Self::Error> {
//..
}
}Instruction Data
Here's the instruction data we need to pass in:
amount: The amount of LP token that the user wishes to receive. Must be a[u64]max_x: The max amount of token X that the user is willing to deposit. Must be a[u64]max_y: The max amount of token Y that the user is willing to deposit. Must be a[u64]expiration: The expiration of this order. Important to make sure that the transaction has to be played in a certain amount of time. Must be a[i64]
We're going to handle the implementation for the DepositInstructionData same as initialization. So I’ll leave the implementation to you:
pub struct DepositInstructionData {
pub amount: u64,
pub max_x: u64,
pub max_y: u64,
pub expiration: i64,
}
impl<'a> TryFrom<&'a [u8]> for DepositInstructionData {
type Error = ProgramError;
fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
//..
}
}Instruction Logic
We begin by deserializing both the instruction_data and the accounts.
We then need to:
Load the
Configaccount to grab all the data inside of it. We can do so using theConfig::load()helper.Verify that the
AmmStateis valid (so if it's equal toAmmState::Initialized)Check the derivation of
vault_xandvault_yto be Associated Token Accounts like this:
// Check if the vault_x is valid
let (vault_x, _) = find_program_address(
&[
self.accounts.config.key(),
self.accounts.token_program.key(),
config.mint_x(),
],
&pinocchio_associated_token_account::ID,
);
if vault_x.ne(self.accounts.vault_x.key()) {
return Err(ProgramError::InvalidAccountData);
}Deserialize all the token accounts involved and use the data inside of them to calculate the amount to deposits using the
constant-product-curvecrate and checking for slippage like this:
// Deserialize the token accounts
let mint_lp = unsafe { Mint::from_account_info_unchecked(self.accounts.mint_lp)? };
let vault_x = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_x)? };
let vault_y = unsafe { TokenAccount::from_account_info_unchecked(self.accounts.vault_y)? };
// Grab the amounts to deposit
let (x, y) = match mint_lp.supply() == 0 && vault_x.amount() == 0 && vault_y.amount() == 0 {
true => (self.instruction_data.max_x, self.instruction_data.max_y),
false => {
let amounts = ConstantProduct::xy_deposit_amounts_from_l(
vault_x.amount(),
vault_y.amount(),
mint_lp.supply(),
self.instruction_data.amount,
6,
)
.map_err(|_| ProgramError::InvalidArgument)?;
(amounts.x, amounts.y)
}
};
// Check for slippage
if !(x <= self.instruction_data.max_x && y <= self.instruction_data.max_y) {
return Err(ProgramError::InvalidArgument);
}Transfer the amounts from the token accounts of the user to the vaults and mint the appropriate amount of LP tokens to the user token account.
Thanks to the p-token upgrade, we can batch the token transfers and LP tokens minting in one CPI, instead of doing three separate CPI calls.
use pinocchio_token::instructions::{Batch, BatchState, MintTo, Transfer};
/// The number of accounts for the batch instruction.
const MAX_ACCOUNTS_LEN: usize =
Transfer::MAX_ACCOUNTS_LEN * 2
+ MintTo::MAX_ACCOUNTS_LEN;
/// The length of the instruction data for the batch instruction.
const MAX_DATA_LEN: usize = Batch::header_data_len(3)
+ Transfer::DATA_LEN * 2
+ MintTo::DATA_LEN;
// ...
let mut batch_state = BatchState::new(MAX_ACCOUNTS_LEN, MAX_DATA_LEN);
let mut batch = batch_state.as_batch()?;
Transfer::new(
self.accounts.user_x_ata,
self.accounts.vault_x,
self.accounts.user,
x,
)
.into_batch(&mut batch)?;
Transfer::new(
self.accounts.user_y_ata,
self.accounts.vault_y,
self.accounts.user,
y,
)
.into_batch(&mut batch)?;
MintTo::new(
self.accounts.mint_lp,
self.accounts.user_lp_ata,
self.accounts.config,
self.instruction_data.amount,
)
.into_batch(&mut batch)?;
batch.invoke_signed(&[signer])?;You should be proficient enough to do the rest on your own, so I'll leave the implementation to you:
pub struct Deposit<'a> {
pub accounts: DepositAccounts<'a>,
pub instruction_data: DepositInstructionData,
}
impl<'a> TryFrom<(&'a [u8], &'a [AccountInfo])> for Deposit<'a> {
type Error = ProgramError;
fn try_from((data, accounts): (&'a [u8], &'a [AccountInfo])) -> Result<Self, Self::Error> {
let accounts = DepositAccounts::try_from(accounts)?;
let instruction_data = DepositInstructionData::try_from(data)?;
// Return the initialized struct
Ok(Self {
accounts,
instruction_data,
})
}
}
impl<'a> Deposit<'a> {
pub const DISCRIMINATOR: &'a u8 = &1;
pub fn process(&mut self) -> ProgramResult {
//..
Ok(())
}
}