diff --git a/interface/src/instruction/create_buffer.rs b/interface/src/instruction/create_buffer.rs index d8664b5..bf5fc4e 100644 --- a/interface/src/instruction/create_buffer.rs +++ b/interface/src/instruction/create_buffer.rs @@ -5,7 +5,6 @@ //! token authority. Each token is identified by its `mint` account; the buffer //! address must be the canonical PDA for that mint. -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -60,20 +59,17 @@ impl From> for Instruction { } /// Parsed inputs of a `CreateBuffer` instruction. -pub struct CreateBufferInput<'a> { - pub payer: &'a AccountView, - pub token_program: &'a AccountView, +pub struct CreateBufferInput<'a, A> { + pub payer: &'a A, + pub token_program: &'a A, /// One `[buffer_pda, mint]` pair per buffer to create. - pub buffers: &'a [[AccountView; 2]], + pub buffers: &'a [[A; 2]], } -impl<'a> InstructionInputParsing<'a> for CreateBufferInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateBuffer; - fn parse_body( - instruction_data: &[u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result { if !instruction_data.is_empty() { return Err(ProgramError::InvalidInstructionData); } @@ -89,7 +85,7 @@ impl<'a> InstructionInputParsing<'a> for CreateBufferInput<'a> { // buffer needs both, so a stray odd account left over is a malformed // instruction. There must be at least one pair: an instruction that // creates no buffers is rejected as a likely encoding issue. - let rest: &'a [AccountView] = rest; + let rest: &'a [A] = rest; let (buffers, remainder) = rest.as_chunks::<2>(); if !remainder.is_empty() || buffers.is_empty() { return Err(ProgramError::NotEnoughAccountKeys); @@ -135,6 +131,7 @@ mod tests { use crate::instruction::fixtures::{ fake_account, fake_account_from_array, fake_sequential_accounts, }; + use solana_account_view::AccountView; use solana_address::Address; #[test] @@ -228,8 +225,9 @@ mod tests { fn create_buffer_input_rejects_long_data() { let mut data = create_buffer_data(); data.push(0); // trailing byte + let mut accounts: [AccountView; 0] = []; assert_eq!( - CreateBufferInput::parse(&data, &mut []).err(), + CreateBufferInput::parse(&data, &mut accounts).err(), Some(ProgramError::InvalidInstructionData), ); } diff --git a/interface/src/instruction/create_order.rs b/interface/src/instruction/create_order.rs index 9c89381..ab35db2 100644 --- a/interface/src/instruction/create_order.rs +++ b/interface/src/instruction/create_order.rs @@ -4,7 +4,6 @@ //! initial body bytes; the PDA's storage layout lives in //! [`crate::data::order::EncodedOrderAccount`]. -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -70,20 +69,17 @@ impl From for Instruction { } /// Parsed inputs of a `CreateOrder` instruction. -pub struct CreateOrderInput<'a> { +pub struct CreateOrderInput<'a, A> { pub intent_bytes: [u8; EncodedOrderIntent::SIZE], - pub owner: &'a AccountView, - pub created_by: &'a AccountView, - pub order_pda: &'a mut AccountView, + pub owner: &'a A, + pub created_by: &'a A, + pub order_pda: &'a mut A, } -impl<'a> InstructionInputParsing<'a> for CreateOrderInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for CreateOrderInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateOrder; - fn parse_body( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result { // Body (discriminator already stripped): exactly the 150 intent bytes. if instruction_data.len() != EncodedOrderIntent::SIZE { return Err(ProgramError::InvalidInstructionData); @@ -158,6 +154,7 @@ mod tests { use crate::instruction::fixtures::{ fake_account, fake_account_from_array, fake_sequential_accounts, }; + use solana_account_view::AccountView; use solana_address::Address; #[test] diff --git a/interface/src/instruction/initialize.rs b/interface/src/instruction/initialize.rs index 16d0fea..5b56fd8 100644 --- a/interface/src/instruction/initialize.rs +++ b/interface/src/instruction/initialize.rs @@ -2,7 +2,6 @@ //! //! Allocates the singleton settlement state PDA (see [`crate::pda::state`]). -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -52,18 +51,15 @@ impl From for Instruction { } /// Parsed inputs of an `Initialize` instruction. -pub struct InitializeInput<'a> { - pub payer: &'a AccountView, - pub state_pda: &'a mut AccountView, +pub struct InitializeInput<'a, A> { + pub payer: &'a A, + pub state_pda: &'a mut A, } -impl<'a> InstructionInputParsing<'a> for InitializeInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for InitializeInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::Initialize; - fn parse_body( - instruction_data: &[u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result { if !instruction_data.is_empty() { return Err(ProgramError::InvalidInstructionData); } @@ -107,6 +103,7 @@ mod tests { use super::fixtures::{initialize_data, NUM_ACCOUNTS}; use super::*; use crate::instruction::fixtures::{fake_account_from_array, fake_sequential_accounts}; + use solana_account_view::AccountView; use solana_address::Address; #[test] diff --git a/interface/src/instruction/mod.rs b/interface/src/instruction/mod.rs index b77c579..4358659 100644 --- a/interface/src/instruction/mod.rs +++ b/interface/src/instruction/mod.rs @@ -4,7 +4,6 @@ //! settlement instructions, encoding their discriminator (see //! [`crate::SettlementInstruction`]) and laying out the required accounts. -use solana_account_view::AccountView; use solana_program_error::ProgramError; use crate::{recover_discriminator, SettlementInstruction}; @@ -15,24 +14,24 @@ pub mod initialize; pub mod reclaim_order; pub mod settle; -/// Shared components for parsing generic instruction input. +/// Shared components for parsing an instruction's input (data fields and +/// accounts). /// /// Implementations declare which [`SettlementInstruction`] discriminator they /// belong to and parse the remaining instruction data and accounts. The /// discriminator check is shared via the default [`parse`] implementation; an /// impl only needs to provide [`parse_body`]. -pub trait InstructionInputParsing<'a>: Sized { +/// +/// Parsing accounts is purely positional: they are picked out by their index in +/// the slice, never by inspecting them. The account type `A` is left generic so +/// the same layout can be parsed from any account representation, on-chain +/// (`solana_account_view::AccountView`) or off-chain. +pub trait InstructionInputParsing<'a, A>: Sized { const DISCRIMINATOR: SettlementInstruction; - fn parse_body( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result; + fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result; - fn parse( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result { match recover_discriminator(instruction_data)? { (discriminator, remaining_data) if discriminator == Self::DISCRIMINATOR => { Self::parse_body(remaining_data, accounts) @@ -171,11 +170,12 @@ pub mod fixtures { #[cfg(test)] mod tests { use super::*; + use solana_account_view::AccountView; #[test] fn input_parsing_rejects_different_discriminator() { struct TestInputParsing {} - impl<'a> InstructionInputParsing<'a> for TestInputParsing { + impl<'a> InstructionInputParsing<'a, AccountView> for TestInputParsing { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle; fn parse_body( diff --git a/interface/src/instruction/reclaim_order.rs b/interface/src/instruction/reclaim_order.rs index db41103..05105c8 100644 --- a/interface/src/instruction/reclaim_order.rs +++ b/interface/src/instruction/reclaim_order.rs @@ -10,7 +10,6 @@ //! Required accounts: //! `[order_pda (W), reclaim_recipient (W)]`. -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -49,19 +48,16 @@ impl ReclaimOrder { } /// Parsed inputs of a `ReclaimOrder` instruction. -pub struct ReclaimOrderInput<'a> { - pub order_pda: &'a mut AccountView, +pub struct ReclaimOrderInput<'a, A> { + pub order_pda: &'a mut A, pub bump: u8, - pub reclaim_recipient: &'a mut AccountView, + pub reclaim_recipient: &'a mut A, } -impl<'a> InstructionInputParsing<'a> for ReclaimOrderInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for ReclaimOrderInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::ReclaimOrder; - fn parse_body( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result { // Body is a single bump byte, already stripped of the discriminator. let &[bump] = instruction_data else { return Err(ProgramError::InvalidInstructionData); @@ -108,6 +104,7 @@ mod tests { use super::fixtures::{default_reclaim_data, NUM_ACCOUNTS}; use super::*; use crate::instruction::fixtures::{fake_account, fake_sequential_accounts}; + use solana_account_view::AccountView; use solana_address::Address; #[test] diff --git a/interface/src/instruction/settle/begin.rs b/interface/src/instruction/settle/begin.rs index 8524e11..129cefb 100644 --- a/interface/src/instruction/settle/begin.rs +++ b/interface/src/instruction/settle/begin.rs @@ -2,7 +2,6 @@ use std::vec; -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -124,12 +123,12 @@ impl From> for Instruction { /// A single settled order, resulted from parsing `BeginSettle`, together with /// the funds to pull from its sell token account. -pub struct SettledOrder<'a> { - pub order_pda: &'a AccountView, - pub sell_token_account: &'a AccountView, +pub struct SettledOrder<'a, A> { + pub order_pda: &'a A, + pub sell_token_account: &'a A, pub bump: u8, /// Destination accounts for this order's transfers. - pub destinations: &'a [AccountView], + pub destinations: &'a [A], /// Transfer amounts (little-endian `u64`), one per destination. pub amounts: &'a [[u8; 8]], } @@ -137,13 +136,13 @@ pub struct SettledOrder<'a> { /// Struct storing accounts, bumps, transfer counts, and amounts from parsing the /// input of BeginSettle. The parsing step that created this struct guarantees /// that there aren't missing elements or that they are assigned incorrectly. -pub struct SettledOrders<'a> { +pub struct SettledOrders<'a, A> { /// Order accounts, laid out per order as /// [order_accounts_1, order_accounts_2, ...] where /// - each order_accounts is a series of accounts: /// `order_pda_N, sell_token_account_N, destination_N_1, destination_N_2, ..., destination_N_M` /// - and M is `counts[N]` - order_accounts: &'a [AccountView], + order_accounts: &'a [A], bumps: &'a [u8], /// One transfer count per order, parallel to `bumps`. counts: &'a [u8], @@ -152,13 +151,13 @@ pub struct SettledOrders<'a> { amounts: &'a [[u8; 8]], } -impl<'a> SettledOrders<'a> { +impl<'a, A> SettledOrders<'a, A> { /// Returns an iterator yielding one [`SettledOrder`] per step. #[allow( clippy::arithmetic_side_effects, reason = "offsets are bounded by tx limits" )] - pub fn iter(&self) -> impl Iterator> + '_ { + pub fn iter(&self) -> impl Iterator> + '_ { let order_count = self.bumps.len(); let mut i = 0usize; let mut account_offset = 0usize; @@ -199,28 +198,25 @@ impl<'a> SettledOrders<'a> { /// `accounts` but **not validated** against runtime context except confirming /// that the discriminator matches the desired input and that the number of /// accounts and bumps is consistent. -pub struct BeginSettleInput<'a> { +pub struct BeginSettleInput<'a, A> { pub finalize_ix_index: u16, /// The off-chain auction this settlement executes, read from the instruction /// data. Not validated on-chain: it's carried only so the settlement can be /// tied back to its auction off-chain. pub auction_id: i64, - pub instructions_sysvar_account: &'a AccountView, - pub state_pda_account: &'a AccountView, - pub token_program_account: &'a AccountView, - pub orders: SettledOrders<'a>, + pub instructions_sysvar_account: &'a A, + pub state_pda_account: &'a A, + pub token_program_account: &'a A, + pub orders: SettledOrders<'a, A>, } /// This implementation defines how instruction bytes and accounts are laid out /// in the transaction. It's the source of truth for deciding where the data /// is stored. -impl<'a> InstructionInputParsing<'a> for BeginSettleInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::BeginSettle; - fn parse_body( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result { let (finalize_ix_index, body) = recover_counterpart(instruction_data)?; let [instructions_sysvar_account, state_pda_account, token_program_account, order_accounts @ ..] = @@ -299,6 +295,7 @@ mod tests { }; use crate::instruction::settle::tests::ix_data; use hex_literal::hex; + use solana_account_view::AccountView; use solana_address::Address; /// The fixed accounts every `BeginSettle` carries before its order accounts: diff --git a/interface/src/instruction/settle/finalize.rs b/interface/src/instruction/settle/finalize.rs index 886854a..768919c 100644 --- a/interface/src/instruction/settle/finalize.rs +++ b/interface/src/instruction/settle/finalize.rs @@ -2,7 +2,6 @@ use std::vec; -use solana_account_view::AccountView; use solana_instruction::{AccountMeta, Instruction}; use solana_program_error::ProgramError; use solana_pubkey::Pubkey; @@ -95,9 +94,9 @@ impl From> for Instruction { /// A single fund push parsed from `FinalizeSettle`: move `amount` (little-endian /// `u64`) from `source_buffer` to `destination`. `bump` is `source_buffer`'s /// claimed canonical buffer bump, which the program re-derives against. -pub struct Push<'a> { - pub source_buffer: &'a AccountView, - pub destination: &'a AccountView, +pub struct Push<'a, A> { + pub source_buffer: &'a A, + pub destination: &'a A, pub bump: u8, pub amount: &'a [u8; 8], } @@ -107,21 +106,21 @@ pub struct Push<'a> { /// account pairs parallel to `bumps` and `amounts`. The parsing step that created /// this struct guarantees `push_accounts.len() == 2 * amounts.len()` and /// `bumps.len() == amounts.len()`, so the offsets below never run short. -pub struct Pushes<'a> { +pub struct Pushes<'a, A> { /// `[source_buffer, destination]` per push, flattened. - push_accounts: &'a [AccountView], + push_accounts: &'a [A], bumps: &'a [u8], /// One push amount (little-endian `u64`) per push, parallel to `bumps`. amounts: &'a [[u8; 8]], } -impl<'a> Pushes<'a> { +impl<'a, A> Pushes<'a, A> { /// Returns an iterator yielding one [`Push`] per step. #[allow( clippy::arithmetic_side_effects, reason = "offsets are bounded by tx limits" )] - pub fn iter(&self) -> impl Iterator> + '_ { + pub fn iter(&self) -> impl Iterator> + '_ { let push_count = self.bumps.len(); let mut i = 0usize; let mut account_offset = 0usize; @@ -154,24 +153,21 @@ impl<'a> Pushes<'a> { /// `accounts` but **not validated** against runtime context except confirming /// that the discriminator matches the desired input and that the number of /// accounts and amounts is consistent. -pub struct FinalizeSettleInput<'a> { +pub struct FinalizeSettleInput<'a, A> { pub begin_ix_index: u16, - pub instructions_sysvar_account: &'a AccountView, - pub state_pda_account: &'a AccountView, - pub token_program_account: &'a AccountView, - pub pushes: Pushes<'a>, + pub instructions_sysvar_account: &'a A, + pub state_pda_account: &'a A, + pub token_program_account: &'a A, + pub pushes: Pushes<'a, A>, } /// This implementation defines how instruction bytes and accounts are laid out /// in the transaction. It's the source of truth for deciding where the data /// is stored. -impl<'a> InstructionInputParsing<'a> for FinalizeSettleInput<'a> { +impl<'a, A> InstructionInputParsing<'a, A> for FinalizeSettleInput<'a, A> { const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::FinalizeSettle; - fn parse_body( - instruction_data: &'a [u8], - accounts: &'a mut [AccountView], - ) -> Result { + fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result { let (begin_ix_index, body) = recover_counterpart(instruction_data)?; let [instructions_sysvar_account, state_pda_account, token_program_account, push_accounts @ ..] = @@ -226,6 +222,7 @@ mod tests { }; use crate::instruction::settle::tests::ix_data; use hex_literal::hex; + use solana_account_view::AccountView; use solana_address::Address; #[test] diff --git a/programs/settlement/src/settle/begin.rs b/programs/settlement/src/settle/begin.rs index 6703447..b5d7865 100644 --- a/programs/settlement/src/settle/begin.rs +++ b/programs/settlement/src/settle/begin.rs @@ -160,7 +160,7 @@ fn settle_orders<'a>( program_id: &Address, state_pda_account: &AccountView, state_pda_signer: &Signer, - orders: impl IntoIterator>, + orders: impl IntoIterator>, finalize_ix: &IntrospectedInstruction, ) -> ProgramResult { // Orders must be passed strictly increasing by address; this rejects @@ -208,7 +208,7 @@ fn settle_orders<'a>( #[must_use = "ignoring the output may lead to an unintended on-chain state"] fn process_order( program_id: &Address, - order: SettledOrder<'_>, + order: SettledOrder<'_, AccountView>, push_destination: &Address, now: i64, state_account: &AccountView, diff --git a/programs/settlement/src/settle/finalize.rs b/programs/settlement/src/settle/finalize.rs index 9026ebe..3cdb9f4 100644 --- a/programs/settlement/src/settle/finalize.rs +++ b/programs/settlement/src/settle/finalize.rs @@ -65,7 +65,7 @@ fn push_funds<'a>( program_id: &Address, state_pda_account: &AccountView, state_pda_signer: &Signer, - pushes: Pushes<'a>, + pushes: Pushes<'a, AccountView>, ) -> ProgramResult { for push in pushes.iter() { // Read the destination's mint; the borrow ends with this block, before