Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions interface/src/instruction/create_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,20 +59,17 @@ impl From<CreateBuffers<'_>> 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<Self, ProgramError> {
fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
if !instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
Expand All @@ -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);
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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),
);
}
Expand Down
17 changes: 7 additions & 10 deletions interface/src/instruction/create_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,20 +69,17 @@ impl From<CreateOrder> 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<Self, ProgramError> {
fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
// Body (discriminator already stripped): exactly the 150 intent bytes.
if instruction_data.len() != EncodedOrderIntent::SIZE {
return Err(ProgramError::InvalidInstructionData);
Expand Down Expand Up @@ -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]
Expand Down
15 changes: 6 additions & 9 deletions interface/src/instruction/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,18 +51,15 @@ impl From<Initialize> 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<Self, ProgramError> {
fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
if !instruction_data.is_empty() {
return Err(ProgramError::InvalidInstructionData);
}
Expand Down Expand Up @@ -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]
Expand Down
24 changes: 12 additions & 12 deletions interface/src/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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<Self, ProgramError>;
fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError>;

fn parse(
instruction_data: &'a [u8],
accounts: &'a mut [AccountView],
) -> Result<Self, ProgramError> {
fn parse(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
match recover_discriminator(instruction_data)? {
(discriminator, remaining_data) if discriminator == Self::DISCRIMINATOR => {
Self::parse_body(remaining_data, accounts)
Expand Down Expand Up @@ -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(
Expand Down
15 changes: 6 additions & 9 deletions interface/src/instruction/reclaim_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Self, ProgramError> {
fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
// Body is a single bump byte, already stripped of the discriminator.
let &[bump] = instruction_data else {
return Err(ProgramError::InvalidInstructionData);
Expand Down Expand Up @@ -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]
Expand Down
35 changes: 16 additions & 19 deletions interface/src/instruction/settle/begin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -124,26 +123,26 @@ impl From<BeginSettle<'_>> 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]],
}

/// 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],
Expand All @@ -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<Item = SettledOrder<'a>> + '_ {
pub fn iter(&self) -> impl Iterator<Item = SettledOrder<'a, A>> + '_ {
let order_count = self.bumps.len();
let mut i = 0usize;
let mut account_offset = 0usize;
Expand Down Expand Up @@ -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<Self, ProgramError> {
fn parse_body(instruction_data: &'a [u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
let (finalize_ix_index, body) = recover_counterpart(instruction_data)?;

let [instructions_sysvar_account, state_pda_account, token_program_account, order_accounts @ ..] =
Expand Down Expand Up @@ -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:
Expand Down
Loading