Skip to content
Draft
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
20 changes: 8 additions & 12 deletions client/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,12 @@ mod tests {
instruction::{
fixtures::fake_account_from_array,
settle::{
BeginSettleInput, FinalizeSettleInput, INSTRUCTIONS_SYSVAR_ID,
SPL_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID,
BeginSettleInput, FinalizeSettleInput, INSTRUCTIONS_SYSVAR_ID, SPL_TOKEN_PROGRAM_ID,
},
InstructionInputParsing,
},
pda::order::find_order_pda,
token_program::SYSTEM_PROGRAM_ID,
};

proptest! {
Expand Down Expand Up @@ -457,16 +457,12 @@ mod tests {
);
let (state_pda, _bump) = find_state_pda(&program_id);
prop_assert_eq!(parsed.state_pda_account.address(), &state_pda);
prop_assert_eq!(
parsed.spl_token_program_account.address(),
&SPL_TOKEN_PROGRAM_ID,
);
// These settlements are legacy-only, so Token-2022's slot stands
// empty.
prop_assert_eq!(
parsed.token_2022_program_account.address(),
&SYSTEM_PROGRAM_ID,
);
// The token-program slots aren't parsed, so the instruction's own
// account list is where they are checked: the legacy program in its
// own slot, and — these settlements being legacy-only — the
// placeholder in Token-2022's.
prop_assert_eq!(ix.accounts[2].pubkey, SPL_TOKEN_PROGRAM_ID);
prop_assert_eq!(ix.accounts[3].pubkey, SYSTEM_PROGRAM_ID);

let parsed_pushes: Vec<_> = parsed.pushes.iter().collect();
prop_assert_eq!(parsed_pushes.len(), expected.len());
Expand Down
2 changes: 1 addition & 1 deletion client/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ mod tests {
use super::*;
use crate::instructions::{
AddSolver, BeginSettle, CreateBuffers, CreateOrder, FinalizeSettle, Initialize,
InitializedIntent, RemoveSolver,
InitializedIntent, RemoveSolver, TokenPrograms,
};
use cow_settlement_interface::{
data::intent::fixtures::sample_intent,
Expand Down
12 changes: 5 additions & 7 deletions interface/src/instruction/create_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ pub struct BufferAccounts<'a, A> {
/// Parsed inputs of a `CreateBuffer` instruction.
pub struct CreateBufferInput<'a, A> {
pub payer: &'a A,
pub token_program: &'a A,
buffer_pairs: &'a [[A; 2]],
}

Expand All @@ -98,10 +97,11 @@ impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {
}
// Accounts: [payer (W,S), system_program (R), token_program (R),
// (buffer_pda (W), mint (R))...]. The three shared accounts come first;
// the per-buffer pairs follow, one pair per buffer. The system program
// needs to be present for the `CreateAccount` CPI but isn't dereferenced
// here.
let [payer, _system, token_program, rest @ ..] = accounts else {
// the per-buffer pairs follow, one pair per buffer. Neither program is
// dereferenced here: they need to be present for the `CreateAccount`
// and `InitializeAccount3` CPIs to dispatch, and each buffer's program
// is the one that owns its mint.
let [payer, _system, _token_program, rest @ ..] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
// Group the trailing accounts into `[buffer_pda, mint]` pairs. Each
Expand All @@ -116,7 +116,6 @@ impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {

Ok(Self {
payer,
token_program,
buffer_pairs: buffers,
})
}
Expand Down Expand Up @@ -186,7 +185,6 @@ mod tests {
let input = CreateBufferInput::parse(&data, &accounts).expect("parse should succeed");

assert_eq!(*input.payer.address(), payer);
assert_eq!(*input.token_program.address(), token_program);
let buffers: Vec<_> = input.buffers().collect();
assert_eq!(buffers.len(), 1, "one buffer is one (pda, mint) pair");
assert_eq!(*buffers[0].buffer_pda.address(), buffer_pda);
Expand Down
10 changes: 4 additions & 6 deletions interface/src/instruction/reclaim_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ pub struct ReclaimBufferInput<'a, A> {
pub state_pda: &'a A,
pub reclaim_authority: &'a A,
pub reclaim_recipient: &'a A,
pub token_program: &'a A,
/// One `[buffer_pda, mint]` pair per buffer to close.
pub buffers: &'a [[A; 2]],
}
Expand All @@ -87,8 +86,10 @@ impl<'a, A> InstructionInputParsing<'a, A> for ReclaimBufferInput<'a, A> {
// Accounts: [state_pda (R), reclaim_authority (R,S), reclaim_recipient
// (W), token_program (R), (buffer_pda (W), mint (R))...]. The four
// shared accounts come first; the per-buffer pairs follow, one pair per
// buffer.
let [state_pda, reclaim_authority, reclaim_recipient, token_program, rest @ ..] = accounts
// buffer. The token program is skipped rather than read: each buffer is
// closed by the program that owns it, so the account is only there to
// put that program in the transaction.
let [state_pda, reclaim_authority, reclaim_recipient, _token_program, rest @ ..] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
Expand All @@ -105,7 +106,6 @@ impl<'a, A> InstructionInputParsing<'a, A> for ReclaimBufferInput<'a, A> {
state_pda,
reclaim_authority,
reclaim_recipient,
token_program,
buffers,
})
}
Expand Down Expand Up @@ -183,14 +183,12 @@ mod tests {
state_pda: parsed_state_pda,
reclaim_authority: parsed_reclaim_authority,
reclaim_recipient: parsed_reclaim_recipient,
token_program: parsed_token_program,
buffers,
} = ReclaimBufferInput::parse(&data, &accounts).expect("parse should succeed");

assert_eq!(*parsed_state_pda.address(), state_pda);
assert_eq!(*parsed_reclaim_authority.address(), reclaim_authority);
assert_eq!(*parsed_reclaim_recipient.address(), reclaim_recipient);
assert_eq!(*parsed_token_program.address(), token_program);
assert_eq!(buffers.len(), 1, "one buffer is one pair");
assert_eq!(*buffers[0][0].address(), buffer_pda);
assert_eq!(*buffers[0][1].address(), mint);
Expand Down
27 changes: 9 additions & 18 deletions interface/src/instruction/settle/begin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ pub struct Pull {
/// spl_token_program (R), token_2022_program (R)]` followed, per order, by
/// `[order_pda (W), sell_token_account (W), destination (W)...]`. The two token
/// programs are the slots [`TokenPrograms`] describes: each transfer is issued
/// against the program that owns the account it moves, and a program this
/// settlement doesn't touch is left out with the system program.
/// against the program that owns the account it moves, so the slots are there
/// to name those programs — a CPI can only dispatch to a program the
/// instruction names. A program this settlement doesn't touch is left out with
/// the system program.
///
/// `solver` must sign, and the solver must be registered in the state pda.
///
Expand Down Expand Up @@ -214,11 +216,6 @@ pub struct BeginSettleInput<'a, A> {
pub solver_account: &'a A,
pub instructions_sysvar_account: &'a A,
pub state_pda_account: &'a A,
/// The legacy SPL Token program's slot: the program itself, or the
/// placeholder where this settlement moves no token under it.
pub spl_token_program_account: &'a A,
/// Token-2022's slot, filled the same way.
pub token_2022_program_account: &'a A,
pub orders: SettledOrders<'a, A>,
}

Expand All @@ -231,7 +228,11 @@ impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> {
fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
let (finalize_ix_index, body) = recover_counterpart(instruction_data)?;

let [solver_account, instructions_sysvar_account, state_pda_account, spl_token_program_account, token_2022_program_account, order_accounts @ ..] =
// The two token-program slots are skipped rather than read: every
// transfer is issued against the program that owns the account it
// moves, so naming the programs is all the slots do. They still take up
// their positions, which is what the order accounts are counted from.
let [solver_account, instructions_sysvar_account, state_pda_account, _spl_token_program_account, _token_2022_program_account, order_accounts @ ..] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
Expand Down Expand Up @@ -285,8 +286,6 @@ impl<'a, A> InstructionInputParsing<'a, A> for BeginSettleInput<'a, A> {
auction_id,
instructions_sysvar_account,
state_pda_account,
spl_token_program_account,
token_2022_program_account,
solver_account,
orders: SettledOrders {
order_accounts,
Expand Down Expand Up @@ -577,16 +576,12 @@ mod tests {
solver_account,
instructions_sysvar_account,
orders,
spl_token_program_account,
token_2022_program_account,
state_pda_account,
} = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
assert_eq!(finalize_ix_index, 0x1337);
assert_eq!(auction_id, 0x0102_0304_0506_0708);
assert_eq!(instructions_sysvar_account.address(), &sysvar);
assert_eq!(orders.iter().count(), 0);
assert_eq!(spl_token_program_account.address(), &spl_token_program);
assert_eq!(token_2022_program_account.address(), &token_2022_program);
assert_eq!(state_pda_account.address(), &state);
assert_eq!(solver_account.address(), &solver);
}
Expand Down Expand Up @@ -665,14 +660,10 @@ mod tests {
instructions_sysvar_account,
orders,
state_pda_account,
spl_token_program_account,
token_2022_program_account,
} = BeginSettleInput::parse(&data, &accounts).expect("parse should succeed");
assert_eq!(finalize_ix_index, 0x1337);
assert_eq!(auction_id, AUCTION_ID);
assert_eq!(instructions_sysvar_account.address(), &sysvar);
assert_eq!(spl_token_program_account.address(), &spl_token_program);
assert_eq!(token_2022_program_account.address(), &token_2022_program);
assert_eq!(state_pda_account.address(), &state);
assert_eq!(solver_account.address(), &solver);

Expand Down
20 changes: 7 additions & 13 deletions interface/src/instruction/settle/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ pub fn finalize_push_data(
/// `[instructions_sysvar (R), state_pda (R), spl_token_program (R),
/// token_2022_program (R)]` followed, per push, by `[source_buffer (W),
/// destination (W)]`. The two token programs are the slots [`TokenPrograms`]
/// describes; the matching `BeginSettle` carries the same ones.
/// describes, there to name the programs this instruction's pushes are issued
/// against; the matching `BeginSettle` carries the ones its pulls need.
///
/// `FinalizeSettle` only executes the transfers. Every push is validated by
/// `BeginSettle`, which reads this instruction through introspection.
Expand Down Expand Up @@ -208,11 +209,6 @@ pub struct FinalizeSettleInput<'a, A> {
pub begin_ix_index: u16,
pub instructions_sysvar_account: &'a A,
pub state_pda_account: &'a A,
/// The legacy SPL Token program's slot: the program itself, or the
/// placeholder where this settlement moves no token under it.
pub spl_token_program_account: &'a A,
/// Token-2022's slot, filled the same way.
pub token_2022_program_account: &'a A,
pub pushes: Pushes<'a, A>,
}

Expand All @@ -225,7 +221,11 @@ impl<'a, A> InstructionInputParsing<'a, A> for FinalizeSettleInput<'a, A> {
fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
let (begin_ix_index, body) = recover_counterpart(instruction_data)?;

let [instructions_sysvar_account, state_pda_account, spl_token_program_account, token_2022_program_account, push_accounts @ ..] =
// The two token-program slots are skipped rather than read: every push
// is issued against the program that owns its destination, so naming
// the programs is all the slots do. They still take up their positions,
// which is what the push accounts are counted from.
let [instructions_sysvar_account, state_pda_account, _spl_token_program_account, _token_2022_program_account, push_accounts @ ..] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
Expand All @@ -247,8 +247,6 @@ impl<'a, A> InstructionInputParsing<'a, A> for FinalizeSettleInput<'a, A> {
begin_ix_index,
instructions_sysvar_account,
state_pda_account,
spl_token_program_account,
token_2022_program_account,
pushes: Pushes {
push_accounts,
bumps,
Expand Down Expand Up @@ -437,15 +435,11 @@ mod tests {
begin_ix_index,
instructions_sysvar_account,
state_pda_account,
spl_token_program_account,
token_2022_program_account,
pushes,
} = FinalizeSettleInput::parse(&data, &accounts).expect("parse should succeed");
assert_eq!(begin_ix_index, 0x1337);
assert_eq!(instructions_sysvar_account.address(), &sysvar);
assert_eq!(state_pda_account.address(), &state);
assert_eq!(spl_token_program_account.address(), &spl_token_program);
assert_eq!(token_2022_program_account.address(), &token_2022_program);
assert_eq!(pushes.iter().count(), 0);
}

Expand Down
10 changes: 4 additions & 6 deletions interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,10 @@ pub enum SettlementError {
/// mint has to be and couldn't read the answer, so it can't size the
/// buffer.
BufferSizeUnavailable = 40,
/// `BeginSettle`/`FinalizeSettle`: a token account it has to move is owned
/// by a supported token program whose slot carries the system-program
/// placeholder, so there is no program to issue that transfer against. The
/// settlement has to carry every token program its accounts live under; see
/// [`token_program::TokenPrograms`].
TokenProgramNotProvided = 41,
/// `FinalizeSettle`: a push's destination isn't owned by a supported token
/// program, so it is no token account at all and there is nothing to issue
/// its transfer against.
PushDestinationInvalid = 41,
}

impl From<SettlementError> for u32 {
Expand Down
23 changes: 13 additions & 10 deletions interface/src/token_program.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! The token programs settlement transfers may be issued against.
//!
//! An instruction that moves tokens has to name the program to issue its
//! transfers against, and that program has to be one of [`TokenProgram::ALL`],
//! which is what [`TokenProgram::try_from`] resolves an address against. How it
//! names them differs by instruction:
//! transfers against — a CPI can only dispatch to a program its instruction
//! names — and the program it targets is the one owning the account it moves,
//! which [`TokenProgram::try_from`] resolves from that account's owner. Naming
//! is all the accounts below do; none of them is read on-chain. How an
//! instruction names them differs:
//!
//! - `CreateBuffer` and `ReclaimBuffer` take a single `token_program` account.
//! Each works on one program's accounts at a time, so a mint under the other
//! needs its own instruction.
//! Each buffer is created under, and closed by, the program owning its mint,
//! so a mint under the program the instruction didn't name needs its own
//! instruction.
//! - `BeginSettle` and `FinalizeSettle` take one account per supported program,
//! described by [`TokenPrograms`], and issue each transfer against the
//! program that owns the account it moves. One settlement can therefore mix
Expand Down Expand Up @@ -65,11 +68,11 @@ impl TryFrom<&Pubkey> for TokenProgram {
/// Both instructions take one account per supported program, at fixed positions
/// and in [`TokenProgram::ALL`] order, and issue each transfer against the
/// program that owns the account it moves — so a single settlement may mix
/// tokens from both. A program the settlement doesn't touch is left out by
/// putting [`SYSTEM_PROGRAM_ID`] in its slot: the transfers still need their
/// program to be named by the transaction, and the placeholder says this one
/// isn't. A token account under a left-out program has nothing to be settled
/// against and is rejected.
/// tokens from both. The slots are what name those programs; they are not read
/// on-chain, and a program the settlement doesn't touch is left out by putting
/// [`SYSTEM_PROGRAM_ID`] in its slot. A transfer of a token account under a
/// left-out program then has no program to dispatch to, and the runtime refuses
/// the instruction.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TokenPrograms {
/// Whether the legacy SPL Token program's slot carries the program rather
Expand Down
Loading
Loading