Raydium AMM

Oveview

Entrypoint

Raydium AMM is implemented in native solana program way not using anchor.
Runtime passes program_id , accounts and instruction_data into entrypoint which calls Processor::process to execute specific instruction.
In Processor::process, it calls AmmInstruction::unpack to deserialize input and get typed instruction data, then matches it to corresponding instruction handler to execute.
/// --- program/src/entrypoint.rs --- entrypoint!(process_instruction); fn process_instruction<'a>( program_id: &Pubkey, accounts: &'a [AccountInfo<'a>], instruction_data: &[u8], ) -> ProgramResult { if let Err(error) = Processor::process(program_id, accounts, instruction_data) { // catch the error so we can print it error.print::<AmmError>(); return Err(error); } Ok(()) } /// Program state handler. pub struct Processor {} impl Processor { /// ... /// Processes an [Instruction](enum.Instruction.html). pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult { let instruction = AmmInstruction::unpack(input)?; /// ... } }
/// --- program/src/instruction.rs --- impl AmmInstruction { /// Unpacks a byte buffer into a [AmmInstruction](enum.AmmInstruction.html). pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> { let (&tag, rest) = input .split_first() .ok_or(ProgramError::InvalidInstructionData)?; Ok(match tag { 0 => { let (nonce, rest) = Self::unpack_u8(rest)?; let (open_time, _reset) = Self::unpack_u64(rest)?; Self::Initialize(InitializeInstruction { nonce, open_time }) } 1 => { let (nonce, rest) = Self::unpack_u8(rest)?; let (open_time, rest) = Self::unpack_u64(rest)?; let (init_pc_amount, rest) = Self::unpack_u64(rest)?; let (init_coin_amount, _reset) = Self::unpack_u64(rest)?; Self::Initialize2(InitializeInstruction2 { nonce, open_time, init_pc_amount, init_coin_amount, }) } 2 => { let (plan_order_limit, rest) = Self::unpack_u16(rest)?; let (place_order_limit, rest) = Self::unpack_u16(rest)?; let (cancel_order_limit, _rest) = Self::unpack_u16(rest)?; Self::MonitorStep(MonitorStepInstruction { plan_order_limit, place_order_limit, cancel_order_limit, }) } 3 => { let (max_coin_amount, rest) = Self::unpack_u64(rest)?; let (max_pc_amount, rest) = Self::unpack_u64(rest)?; let (base_side, rest) = Self::unpack_u64(rest)?; let other_amount_min = if rest.len() >= 8 { let (other_amount_min, _rest) = Self::unpack_u64(rest)?; Some(other_amount_min) } else { None }; Self::Deposit(DepositInstruction { max_coin_amount, max_pc_amount, base_side, other_amount_min, }) } 4 => { let (amount, rest) = Self::unpack_u64(rest)?; let (min_coin_amount, min_pc_amount) = if rest.len() >= 16 { let (min_coin_amount, rest) = Self::unpack_u64(rest)?; let (min_pc_amount, _rest) = Self::unpack_u64(rest)?; (Some(min_coin_amount), Some(min_pc_amount)) } else { (None, None) }; Self::Withdraw(WithdrawInstruction { amount, min_coin_amount, min_pc_amount, }) } 5 => Self::MigrateToOpenBook, 6 => { let (param, rest) = Self::unpack_u8(rest)?; match AmmParams::from_u64(param as u64) { AmmParams::AmmOwner => { if rest.len() >= 32 { let new_pubkey = array_ref![rest, 0, 32]; Self::SetParams(SetParamsInstruction { param, value: None, new_pubkey: Some(Pubkey::new_from_array(*new_pubkey)), fees: None, last_order_distance: None, }) } else { return Err(ProgramError::InvalidInstructionData.into()); } } AmmParams::Fees => { if rest.len() >= Fees::LEN { let (fees, _rest) = rest.split_at(Fees::LEN); let fees = Fees::unpack_from_slice(fees)?; Self::SetParams(SetParamsInstruction { param, value: None, new_pubkey: None, fees: Some(fees), last_order_distance: None, }) } else { return Err(ProgramError::InvalidInstructionData.into()); } } AmmParams::LastOrderDistance => { if rest.len() >= 16 { let (last_order_numerator, rest) = Self::unpack_u64(rest)?; let (last_order_denominator, _rest) = Self::unpack_u64(rest)?; Self::SetParams(SetParamsInstruction { param, value: None, new_pubkey: None, fees: None, last_order_distance: Some(LastOrderDistance { last_order_numerator, last_order_denominator, }), }) } else { return Err(ProgramError::InvalidInstructionData.into()); } } _ => { if rest.len() >= 8 { let (value, _rest) = Self::unpack_u64(rest)?; Self::SetParams(SetParamsInstruction { param, value: Some(value), new_pubkey: None, fees: None, last_order_distance: None, }) } else { return Err(ProgramError::InvalidInstructionData.into()); } } } } 7 => Self::WithdrawPnl, 8 => { let (amount, _rest) = Self::unpack_u64(rest)?; Self::WithdrawSrm(WithdrawSrmInstruction { amount }) } 9 => { let (amount_in, rest) = Self::unpack_u64(rest)?; let (minimum_amount_out, _rest) = Self::unpack_u64(rest)?; Self::SwapBaseIn(SwapInstructionBaseIn { amount_in, minimum_amount_out, }) } 10 => { let (nonce, _rest) = Self::unpack_u8(rest)?; Self::PreInitialize(PreInitializeInstruction { nonce }) } 11 => { let (max_amount_in, rest) = Self::unpack_u64(rest)?; let (amount_out, _rest) = Self::unpack_u64(rest)?; Self::SwapBaseOut(SwapInstructionBaseOut { max_amount_in, amount_out, }) } 12 => { let (param, rest) = Self::unpack_u8(rest)?; match SimulateParams::from_u64(param as u64) { SimulateParams::PoolInfo | SimulateParams::RunCrankInfo => { Self::SimulateInfo(SimulateInstruction { param, swap_base_in_value: None, swap_base_out_value: None, }) } SimulateParams::SwapBaseInInfo => { let (amount_in, rest) = Self::unpack_u64(rest)?; let (minimum_amount_out, _rest) = Self::unpack_u64(rest)?; let swap_base_in = Some(SwapInstructionBaseIn { amount_in, minimum_amount_out, }); Self::SimulateInfo(SimulateInstruction { param, swap_base_in_value: swap_base_in, swap_base_out_value: None, }) } SimulateParams::SwapBaseOutInfo => { let (max_amount_in, rest) = Self::unpack_u64(rest)?; let (amount_out, _rest) = Self::unpack_u64(rest)?; let swap_base_out = Some(SwapInstructionBaseOut { max_amount_in, amount_out, }); Self::SimulateInfo(SimulateInstruction { param, swap_base_in_value: None, swap_base_out_value: swap_base_out, }) } } } 13 => { let (limit, _rest) = Self::unpack_u16(rest)?; Self::AdminCancelOrders(AdminCancelOrdersInstruction { limit }) } 14 => Self::CreateConfigAccount, 15 => { let (param, rest) = Self::unpack_u8(rest)?; match param { 0 | 1 => { let pubkey = array_ref![rest, 0, 32]; Self::UpdateConfigAccount(ConfigArgs { param, owner: Some(Pubkey::new_from_array(*pubkey)), create_pool_fee: None, }) } 2 => { let (create_pool_fee, _rest) = Self::unpack_u64(rest)?; Self::UpdateConfigAccount(ConfigArgs { param, owner: None, create_pool_fee: Some(create_pool_fee), }) } _ => { return Err(ProgramError::InvalidInstructionData.into()); } } } _ => return Err(ProgramError::InvalidInstructionData.into()), }) } /// ... }

process_initialize2

The process_initialize2 function is responsible for creating and configuring a new AMM instance. This includes:
  • Verifying all input accounts.
  • Charging pool creation fees.
  • Initializing various on-chain accounts like vaults, LP (liquidity provider) mint, target orders, and open orders accounts.
  • Depositing initial tokens into vaults.
  • Minting initial LP tokens to the user.
By the end of process_initialize2, a fully functional AMM instance is set up and ready to operate according to the Radium AMM logic.
 
 
 
/// --- program/src/processor.rs --- #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct InitializeInstruction2 { /// nonce used to create valid program address pub nonce: u8, /// utc timestamps for pool open pub open_time: u64, /// init token pc amount pub init_pc_amount: u64, /// init token coin amount pub init_coin_amount: u64, } /// Processes an [Initialize](enum.Instruction.html). pub fn process_initialize2( program_id: &Pubkey, accounts: &[AccountInfo], init: InitializeInstruction2, ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let token_program_info = next_account_info(account_info_iter)?; let ata_token_program_info = next_account_info(account_info_iter)?; let system_program_info = next_account_info(account_info_iter)?; let rent_sysvar_info = next_account_info(account_info_iter)?; let amm_info = next_account_info(account_info_iter)?; let amm_authority_info = next_account_info(account_info_iter)?; let amm_open_orders_info = next_account_info(account_info_iter)?; let amm_lp_mint_info = next_account_info(account_info_iter)?; let amm_coin_mint_info = next_account_info(account_info_iter)?; let amm_pc_mint_info = next_account_info(account_info_iter)?; let amm_coin_vault_info = next_account_info(account_info_iter)?; let amm_pc_vault_info = next_account_info(account_info_iter)?; let amm_target_orders_info = next_account_info(account_info_iter)?; let amm_config_info = next_account_info(account_info_iter)?; let create_fee_destination_info = next_account_info(account_info_iter)?; let market_program_info = next_account_info(account_info_iter)?; let market_info = next_account_info(account_info_iter)?; let user_wallet_info = next_account_info(account_info_iter)?; let user_token_coin_info = next_account_info(account_info_iter)?; let user_token_pc_info = next_account_info(account_info_iter)?; let user_token_lp_info = next_account_info(account_info_iter)?; let (pda, _) = Pubkey::find_program_address(&[&AMM_CONFIG_SEED], program_id); if pda != *amm_config_info.key || amm_config_info.owner != program_id { return Err(AmmError::InvalidConfigAccount.into()); } msg!(arrform!(LOG_SIZE, "initialize2: {:?}", init).as_str()); if !user_wallet_info.is_signer { return Err(AmmError::InvalidSignAccount.into()); } check_assert_eq!( *token_program_info.key, spl_token::id(), "spl_token_program", AmmError::InvalidSplTokenProgram ); let spl_token_program_id = token_program_info.key; check_assert_eq!( *ata_token_program_info.key, spl_associated_token_account::id(), "spl_associated_token_account", AmmError::InvalidSplTokenProgram ); check_assert_eq!( *market_program_info.key, config_feature::openbook_program::id(), "market_program", AmmError::InvalidMarketProgram ); check_assert_eq!( *system_program_info.key, solana_program::system_program::id(), "sys_program", AmmError::InvalidSysProgramAddress ); let (expect_amm_authority, expect_nonce) = Pubkey::find_program_address(&[&AUTHORITY_AMM], program_id); if *amm_authority_info.key != expect_amm_authority || init.nonce != expect_nonce { return Err(AmmError::InvalidProgramAddress.into()); } if *create_fee_destination_info.key != config_feature::create_pool_fee_address::id() { return Err(AmmError::InvalidFee.into()); } let amm_config = AmmConfig::load_checked(&amm_config_info, program_id)?; // Charge the fee to create a pool if amm_config.create_pool_fee != 0 { invoke( &system_instruction::transfer( user_wallet_info.key, create_fee_destination_info.key, amm_config.create_pool_fee, ), &[ user_wallet_info.clone(), create_fee_destination_info.clone(), system_program_info.clone(), ], )?; invoke( &spl_token::instruction::sync_native( token_program_info.key, create_fee_destination_info.key, )?, &[ token_program_info.clone(), create_fee_destination_info.clone(), ], )?; } // unpack and check coin_mint let coin_mint = Self::unpack_mint(&amm_coin_mint_info, spl_token_program_id)?; // unpack and check pc_mint let pc_mint = Self::unpack_mint(&amm_pc_mint_info, spl_token_program_id)?; // create target_order account Self::generate_amm_associated_account( program_id, program_id, market_info, amm_target_orders_info, user_wallet_info, system_program_info, rent_sysvar_info, TARGET_ASSOCIATED_SEED, size_of::<TargetOrders>(), )?; // create lp mint account let lp_decimals = coin_mint.decimals; Self::generate_amm_associated_spl_mint( program_id, spl_token_program_id, market_info, amm_lp_mint_info, user_wallet_info, system_program_info, rent_sysvar_info, token_program_info, amm_authority_info, LP_MINT_ASSOCIATED_SEED, lp_decimals, )?; // create coin vault account Self::generate_amm_associated_spl_token( program_id, spl_token_program_id, market_info, amm_coin_vault_info, amm_coin_mint_info, user_wallet_info, system_program_info, rent_sysvar_info, token_program_info, amm_authority_info, COIN_VAULT_ASSOCIATED_SEED, )?; // create pc vault account Self::generate_amm_associated_spl_token( program_id, spl_token_program_id, market_info, amm_pc_vault_info, amm_pc_mint_info, user_wallet_info, system_program_info, rent_sysvar_info, token_program_info, amm_authority_info, PC_VAULT_ASSOCIATED_SEED, )?; // create amm account Self::generate_amm_associated_account( program_id, program_id, market_info, amm_info, user_wallet_info, system_program_info, rent_sysvar_info, AMM_ASSOCIATED_SEED, size_of::<AmmInfo>(), )?; // create amm open order account Self::generate_amm_associated_account( program_id, market_program_info.key, market_info, amm_open_orders_info, user_wallet_info, system_program_info, rent_sysvar_info, OPEN_ORDER_ASSOCIATED_SEED, size_of::<serum_dex::state::OpenOrders>() + 12, )?; // init open orders account Invokers::invoke_dex_init_open_orders( market_program_info.clone(), amm_open_orders_info.clone(), amm_authority_info.clone(), market_info.clone(), rent_sysvar_info.clone(), AUTHORITY_AMM, init.nonce as u8, )?; // create user ata lp token Invokers::create_ata_spl_token( user_token_lp_info.clone(), user_wallet_info.clone(), user_wallet_info.clone(), amm_lp_mint_info.clone(), token_program_info.clone(), ata_token_program_info.clone(), system_program_info.clone(), )?; // transfer user tokens to vault Invokers::token_transfer( token_program_info.clone(), user_token_coin_info.clone(), amm_coin_vault_info.clone(), user_wallet_info.clone(), init.init_coin_amount, )?; Invokers::token_transfer( token_program_info.clone(), user_token_pc_info.clone(), amm_pc_vault_info.clone(), user_wallet_info.clone(), init.init_pc_amount, )?; // load AmmInfo let mut amm = AmmInfo::load_mut(&amm_info)?; if amm.status != AmmStatus::Uninitialized.into_u64() { return Err(AmmError::AlreadyInUse.into()); } // unpack and check token_coin let amm_coin_vault = Self::unpack_token_account(&amm_coin_vault_info, spl_token_program_id)?; check_assert_eq!( amm_coin_vault.owner, *amm_authority_info.key, "coin_vault_owner", AmmError::InvalidOwner ); if amm_coin_vault.amount == 0 { return Err(AmmError::InvalidSupply.into()); } if amm_coin_vault.delegate.is_some() { return Err(AmmError::InvalidDelegate.into()); } if amm_coin_vault.close_authority.is_some() { return Err(AmmError::InvalidCloseAuthority.into()); } check_assert_eq!( *amm_coin_mint_info.key, amm_coin_vault.mint, "coin_mint", AmmError::InvalidCoinMint ); // unpack and check token_pc let amm_pc_vault = Self::unpack_token_account(&amm_pc_vault_info, spl_token_program_id)?; check_assert_eq!( amm_pc_vault.owner, *amm_authority_info.key, "pc_vault_owner", AmmError::InvalidOwner ); if amm_pc_vault.amount == 0 { return Err(AmmError::InvalidSupply.into()); } if amm_pc_vault.delegate.is_some() { return Err(AmmError::InvalidDelegate.into()); } if amm_pc_vault.close_authority.is_some() { return Err(AmmError::InvalidCloseAuthority.into()); } check_assert_eq!( *amm_pc_mint_info.key, amm_pc_vault.mint, "pc_mint", AmmError::InvalidPCMint ); // load and check market let market_state = Market::load(market_info, &config_feature::openbook_program::id(), false)?; if identity(market_state.coin_mint) != amm_coin_vault.mint.to_aligned_bytes() || identity(market_state.coin_mint) != (*amm_coin_mint_info.key).to_aligned_bytes() { return Err(AmmError::InvalidCoinMint.into()); } if identity(market_state.pc_mint) != amm_pc_vault.mint.to_aligned_bytes() || identity(market_state.pc_mint) != (*amm_pc_mint_info.key).to_aligned_bytes() { return Err(AmmError::InvalidPCMint.into()); } if market_state.pc_lot_size == 0 || market_state.coin_lot_size == 0 { msg!( "pc_lot_size:{}, coin_lot_size:{}", identity(market_state.pc_lot_size), identity(market_state.coin_lot_size) ); return Err(AmmError::InvalidMarket.into()); } let lp_mint = Self::unpack_mint(&amm_lp_mint_info, spl_token_program_id)?; if lp_mint.supply != 0 { return Err(AmmError::InvalidSupply.into()); } if COption::Some(*amm_authority_info.key) != lp_mint.mint_authority { return Err(AmmError::InvalidOwner.into()); } if lp_mint.freeze_authority.is_some() { return Err(AmmError::InvalidFreezeAuthority.into()); } let liquidity = Calculator::to_u64( U128::from(amm_pc_vault.amount) .checked_mul(amm_coin_vault.amount.into()) .unwrap() .integer_sqrt() .as_u128(), )?; let user_lp_amount = liquidity .checked_sub((10u64).checked_pow(lp_mint.decimals.into()).unwrap()) .ok_or(AmmError::InitLpAmountTooLess)?; // liquidity is measured in terms of token_a's value since both sides of // the pool are equal Invokers::token_mint_to( token_program_info.clone(), amm_lp_mint_info.clone(), user_token_lp_info.clone(), amm_authority_info.clone(), AUTHORITY_AMM, init.nonce, user_lp_amount, )?; amm.initialize( init.nonce, init.open_time, coin_mint.decimals, pc_mint.decimals, market_state.coin_lot_size, market_state.pc_lot_size, )?; encode_ray_log(InitLog { log_type: LogType::Init.into_u8(), time: init.open_time, pc_decimals: amm.pc_decimals as u8, coin_decimals: amm.coin_decimals as u8, pc_lot_size: market_state.pc_lot_size, coin_lot_size: market_state.coin_lot_size, pc_amount: amm_pc_vault.amount, coin_amount: amm_coin_vault.amount, market: *market_info.key, }); let x = Calculator::normalize_decimal_v2( amm_pc_vault.amount, amm.pc_decimals, amm.sys_decimal_value, ); let y = Calculator::normalize_decimal_v2( amm_coin_vault.amount, amm.coin_decimals, amm.sys_decimal_value, ); // check and init target orders account if amm_target_orders_info.owner != program_id { return Err(AmmError::InvalidProgramAddress.into()); } let mut target_order = TargetOrders::load_mut(amm_target_orders_info)?; target_order.check_init(x.as_u128(), y.as_u128(), amm_info.key)?; amm.coin_vault = *amm_coin_vault_info.key; amm.pc_vault = *amm_pc_vault_info.key; amm.coin_vault_mint = *amm_coin_mint_info.key; amm.pc_vault_mint = *amm_pc_mint_info.key; amm.lp_mint = *amm_lp_mint_info.key; amm.open_orders = *amm_open_orders_info.key; amm.market = *market_info.key; amm.market_program = *market_program_info.key; amm.target_orders = *amm_target_orders_info.key; amm.amm_owner = config_feature::amm_owner::ID; amm.lp_amount = liquidity; amm.status = if init.open_time > (Clock::get()?.unix_timestamp as u64) { AmmStatus::WaitingTrade.into_u64() } else { AmmStatus::SwapOnly.into_u64() }; amm.reset_flag = AmmResetFlag::ResetYes.into_u64(); Ok(()) }

Reference