Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d4c19ac
Support Token-2022 in `BeginSettle` and `FinalizeSettle`
kaze-cow Aug 28, 2026
ac41319
Carry both token programs in a settlement pair
kaze-cow Aug 28, 2026
ce87d39
fix errors in compilation
kaze-cow Sep 8, 2026
6ecb26c
feat: Save CU by removing parsing/validation of the token program acc…
kaze-cow Sep 11, 2026
454ac3d
Merge branch 'kaze/sc-153-token-2022-settle' of github.com:cowprotoco…
kaze-cow Sep 11, 2026
4585d74
Merge remote-tracking branch 'origin/main' into kaze/sc-153-token-202…
kaze-cow Sep 11, 2026
f96c7bb
fixes from my own review
kaze-cow Sep 14, 2026
e72f305
safari to remove incorrect description of CPI calls as "dispatch"
kaze-cow Sep 14, 2026
80cf092
reduce unnecessary comment
kaze-cow Sep 14, 2026
7af61d9
ensure the fake account is still unrelated
kaze-cow Sep 14, 2026
09d1316
refactor of the token test helpers
kaze-cow Sep 14, 2026
c869048
lint fixes
kaze-cow Sep 14, 2026
4ee6ecc
simplify token program dependency logic a bit
kaze-cow Sep 14, 2026
b86385e
use destructuring
kaze-cow Sep 14, 2026
e29a45b
simplify comment
kaze-cow Sep 14, 2026
193bbe9
simplify comment
kaze-cow Sep 14, 2026
d3eb3f6
simplify comment
kaze-cow Sep 14, 2026
4862b0f
Merge branch 'main' into kaze/sc-153-token-2022-settle
kaze-cow Sep 14, 2026
d5d748d
Merge branch 'kaze/sc-153-token-2022-settle' of github.com:cowprotoco…
kaze-cow Sep 14, 2026
10b5529
fix tests
kaze-cow Sep 14, 2026
e27e40e
update bench
kaze-cow Sep 14, 2026
d3d7ac9
improved clarity of only_token_program comments
kaze-cow Sep 15, 2026
6767484
Update interface/src/instruction/settle/begin.rs
kaze-cow Sep 15, 2026
9ca531f
remove dependency on SPL_TOKEN_PROGRAM_ID and use None instead of Some
kaze-cow Sep 15, 2026
a3d5d13
Merge branch 'kaze/sc-153-token-2022-settle' of github.com:cowprotoco…
kaze-cow Sep 15, 2026
7a3ba54
Update interface/src/instruction/settle/finalize.rs
kaze-cow Sep 15, 2026
813d6d7
Update interface/src/token_program.rs
kaze-cow Sep 15, 2026
ab7a5e1
Update programs/settlement/src/processor/utils/token.rs
kaze-cow Sep 15, 2026
6b522c6
refactor cloned_token_under_unsupported_program
kaze-cow Sep 15, 2026
b8861fc
remove unneeded constant and add test to verify intended effect of
kaze-cow Sep 15, 2026
3d65469
Merge branch 'kaze/sc-153-token-2022-settle' of github.com:cowprotoco…
kaze-cow Sep 15, 2026
26291d2
Update programs/settlement/src/processor/finalize_settle.rs
kaze-cow Sep 15, 2026
99a2f35
Update programs/settlement/tests/begin_settle_orders.rs
kaze-cow Sep 15, 2026
3390c9a
remove token program helpers for determining necessary instruction addrs
kaze-cow Sep 15, 2026
18e8a71
Merge branch 'kaze/sc-153-token-2022-settle' of github.com:cowprotoco…
kaze-cow Sep 15, 2026
0a315b5
fix another lint
kaze-cow Sep 15, 2026
2666d5d
fmt
kaze-cow Sep 15, 2026
7744f4b
Merge remote-tracking branch 'origin/main' into kaze/sc-153-token-202…
kaze-cow Sep 15, 2026
88f1e37
Update programs/settlement/tests/begin_settle_orders.rs
kaze-cow Sep 16, 2026
c511b67
Update programs/settlement/tests/settle_token_programs.rs
kaze-cow Sep 16, 2026
eec9376
Update programs/settlement/tests/settle_token_programs.rs
kaze-cow Sep 16, 2026
26068b5
Update programs/settlement/tests/settle_token_programs.rs
kaze-cow Sep 16, 2026
782d319
Update test-cli/src/cmd/settle.rs
kaze-cow Sep 16, 2026
d91701b
assert settlement error is unneeded
kaze-cow Sep 16, 2026
e44ea53
remove unnecessary test
kaze-cow Sep 16, 2026
6feaeb5
lint fix and fmt
kaze-cow Sep 16, 2026
3234cb5
Merge remote-tracking branch 'origin/main' into kaze/sc-153-token-202…
kaze-cow Sep 16, 2026
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
110 changes: 55 additions & 55 deletions bench-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,61 @@
"reclaim_order/on_chain_order_partially_filled_is_not_reclaimable_before_expiry": 3,
"remove_solver/remove_with_many_existing_solvers": 5,
"remove_solver/removes_a_solver": 5,
"settle/finalizes_with_no_pushes": 5,
"settle/pulls_from_multiple_orders": 15,
"settle/pulls_funds_to_destination": 10,
"settle/pulls_to_multiple_destinations": 11,
"settle/pushes_a_single_order": 9,
"settle/pushes_several_orders_from_different_buffers": 13,
"settle/pushes_several_orders_from_one_buffer": 12,
"settle/settles_a_single_order": 9,
"settle/settles_multiple_orders": 17,
"settle/finalizes_with_no_pushes": 6,
"settle/pulls_from_multiple_orders": 16,
"settle/pulls_funds_to_destination": 11,
"settle/pulls_to_multiple_destinations": 12,
"settle/pushes_a_single_order": 10,
"settle/pushes_several_orders_from_different_buffers": 14,
"settle/pushes_several_orders_from_one_buffer": 13,
"settle/settles_a_single_order": 10,
"settle/settles_multiple_orders": 18,
"transfer_authority/manager_can_transfer_manager": 4,
"transfer_authority/manager_can_transfer_reclaim_authority": 4,
"transfer_authority/manager_can_transfer_withdrawal_authority": 4,
"transfer_authority/reclaim_authority_can_transfer_itself": 4,
"transfer_authority/withdrawal_authority_can_transfer_itself": 4
},
"compute_units": {
"add_solver/add_with_many_existing_solvers": 5075,
"add_solver/adds_a_solver": 4623,
"create_buffers/happy_path_creates_initialized_buffer_token_account": 7377,
"create_buffers/happy_path_creates_initialized_buffer_token_account_token_2022": 12192,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction": 17289,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction_token_2022": 31726,
"create_buffers/max_buffers_in_one_instruction": 163775,
"create_buffers/max_buffers_in_one_instruction_token_2022": 207480,
"create_order/happy_path_creates_order_pda_with_expected_body": 4971,
"initialize/happy_path_initializes_state_pda_with_expected_data": 4547,
"reclaim_buffer/funded_buffer_is_skipped": 4859,
"reclaim_buffer/funded_buffer_is_skipped_token_2022": 4867,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself": 6009,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself_token_2022": 7498,
"reclaim_buffer/max_buffers_in_one_instruction": 126323,
"reclaim_buffer/max_buffers_in_one_instruction_token_2022": 170877,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded": 7611,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded_token_2022": 9104,
"reclaim_order/happy_path_expired_returns_lamports_and_closes_pda": 2204,
"reclaim_order/happy_path_on_chain_order_cancelled_is_reclaimable_before_expiry": 2073,
"reclaim_order/happy_path_on_chain_order_fully_filled_is_reclaimable_before_expiry": 2081,
"add_solver/add_with_many_existing_solvers": 5074,
"add_solver/adds_a_solver": 4622,
"create_buffers/happy_path_creates_initialized_buffer_token_account": 7371,
"create_buffers/happy_path_creates_initialized_buffer_token_account_token_2022": 12178,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction": 17347,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction_token_2022": 31768,
"create_buffers/max_buffers_in_one_instruction": 164697,
"create_buffers/max_buffers_in_one_instruction_token_2022": 208026,
"create_order/happy_path_creates_order_pda_with_expected_body": 4970,
"initialize/happy_path_initializes_state_pda_with_expected_data": 4546,
"reclaim_buffer/funded_buffer_is_skipped": 4849,
"reclaim_buffer/funded_buffer_is_skipped_token_2022": 4859,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself": 6012,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself_token_2022": 7500,
"reclaim_buffer/max_buffers_in_one_instruction": 127428,
"reclaim_buffer/max_buffers_in_one_instruction_token_2022": 172068,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded": 7639,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded_token_2022": 9137,
"reclaim_order/happy_path_expired_returns_lamports_and_closes_pda": 2203,
"reclaim_order/happy_path_on_chain_order_cancelled_is_reclaimable_before_expiry": 2072,
"reclaim_order/happy_path_on_chain_order_fully_filled_is_reclaimable_before_expiry": 2080,
"reclaim_order/off_chain_order_is_reclaimable_only_once_expired": null,
"reclaim_order/on_chain_order_partially_filled_is_not_reclaimable_before_expiry": null,
"remove_solver/remove_with_many_existing_solvers": 3759,
"remove_solver/removes_a_solver": 3494,
"settle/finalizes_with_no_pushes": 7171,
"settle/pulls_from_multiple_orders": 20076,
"settle/pulls_funds_to_destination": 13657,
"settle/pulls_to_multiple_destinations": 14798,
"settle/pushes_a_single_order": 12512,
"settle/pushes_several_orders_from_different_buffers": 17784,
"settle/pushes_several_orders_from_one_buffer": 17783,
"settle/settles_a_single_order": 12530,
"settle/settles_multiple_orders": 23103,
"transfer_authority/manager_can_transfer_manager": 3173,
"transfer_authority/manager_can_transfer_reclaim_authority": 3177,
"transfer_authority/manager_can_transfer_withdrawal_authority": 3176,
"transfer_authority/reclaim_authority_can_transfer_itself": 3187,
"transfer_authority/withdrawal_authority_can_transfer_itself": 3185
"remove_solver/remove_with_many_existing_solvers": 3758,
"remove_solver/removes_a_solver": 3493,
"settle/finalizes_with_no_pushes": 7128,
"settle/pulls_from_multiple_orders": 20184,
"settle/pulls_funds_to_destination": 13695,
"settle/pulls_to_multiple_destinations": 14847,
"settle/pushes_a_single_order": 12540,
"settle/pushes_several_orders_from_different_buffers": 17869,
"settle/pushes_several_orders_from_one_buffer": 17869,
"settle/settles_a_single_order": 12558,
"settle/settles_multiple_orders": 23251,
"transfer_authority/manager_can_transfer_manager": 3172,
"transfer_authority/manager_can_transfer_reclaim_authority": 3176,
"transfer_authority/manager_can_transfer_withdrawal_authority": 3175,
"transfer_authority/reclaim_authority_can_transfer_itself": 3186,
"transfer_authority/withdrawal_authority_can_transfer_itself": 3184
},
"transaction_bytes": {
"add_solver/add_with_many_existing_solvers": 366,
Expand Down Expand Up @@ -107,15 +107,15 @@
"reclaim_order/on_chain_order_partially_filled_is_not_reclaimable_before_expiry": 204,
"remove_solver/remove_with_many_existing_solvers": 365,
"remove_solver/removes_a_solver": 365,
"settle/finalizes_with_no_pushes": 290,
"settle/pulls_from_multiple_orders": 656,
"settle/pulls_funds_to_destination": 473,
"settle/pulls_to_multiple_destinations": 514,
"settle/pushes_a_single_order": 432,
"settle/pushes_several_orders_from_different_buffers": 574,
"settle/pushes_several_orders_from_one_buffer": 542,
"settle/settles_a_single_order": 432,
"settle/settles_multiple_orders": 716,
"settle/finalizes_with_no_pushes": 324,
"settle/pulls_from_multiple_orders": 690,
"settle/pulls_funds_to_destination": 507,
"settle/pulls_to_multiple_destinations": 548,
"settle/pushes_a_single_order": 466,
"settle/pushes_several_orders_from_different_buffers": 608,
"settle/pushes_several_orders_from_one_buffer": 576,
"settle/settles_a_single_order": 466,
"settle/settles_multiple_orders": 750,
"transfer_authority/manager_can_transfer_manager": 333,
"transfer_authority/manager_can_transfer_reclaim_authority": 333,
"transfer_authority/manager_can_transfer_withdrawal_authority": 333,
Expand Down
14 changes: 11 additions & 3 deletions client/src/instruction/begin_settle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use cow_settlement_interface::{
Instruction, Pubkey,
};

// Reexport the interface's `Pull` so the client provides all the types a caller
// needs to build a settlement.
pub use cow_settlement_interface::instruction::settle::Pull;
// Reexport the interface's `Pull` and `TokenProgram` so the client provides
// all the types a caller needs to build a settlement.
pub use cow_settlement_interface::instruction::settle::{Pull, TokenProgram};

/// An order ready to be settled, together with the funds to pull from it:
/// `intent` identifies the order and `pulls` lists the [`Pull`]s to make from
Expand All @@ -26,6 +26,12 @@ pub struct BeginSettle<'a> {
/// The off-chain auction this settlement executes, carried so it can be tied
/// back to its auction off-chain.
pub auction_id: i64,
/// By default, a settlement support both token programs at the same time.
/// If you know you only need a single token program, you can make the byte
/// size of the settlement transaction a bit smaller and reduce the total
/// accounts used in the transaction by specifying the
/// only token program you need here.
pub only_token_program: Option<TokenProgram>,
pub orders: &'a [InitializedIntent<'a>],
}

Expand All @@ -47,6 +53,7 @@ impl From<BeginSettle<'_>> for Instruction {
solver: builder.solver,
finalize_ix_index: builder.finalize_ix_index,
auction_id: builder.auction_id,
only_token_program: builder.only_token_program,
order_pdas: &order_pdas,
sell_token_accounts: &sell_token_accounts,
pulls: &pull_lists,
Expand Down Expand Up @@ -90,6 +97,7 @@ mod tests {
solver: pubkey_from_seed("solver"),
finalize_ix_index,
auction_id: 0,
only_token_program: None,
orders: &orders,
});

Expand Down
16 changes: 11 additions & 5 deletions client/src/instruction/finalize_settle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use cow_settlement_interface::{
Instruction, Pubkey,
};

use super::begin_settle::TokenProgram;

/// A settled order whose proceeds are pushed to it: `intent` identifies the
/// order (its `buy_token_account` is the push destination and its `buy_mint`
/// selects the canonical source buffer) and `amount` is the quantity to push.
Expand All @@ -27,6 +29,12 @@ pub struct FinalizedIntent<'a> {
pub struct FinalizeSettle<'a> {
pub program_id: Pubkey,
pub begin_ix_index: u16,
/// By default, a settlement support both token programs at the same time.
/// If you know you only need a single token program, you can make the byte
/// size of the settlement transaction a bit smaller and reduce the total
/// accounts used in the transaction by specifying the
/// only token program you need here.
pub only_token_program: Option<TokenProgram>,
pub orders: &'a [FinalizedIntent<'a>],
}

Expand Down Expand Up @@ -60,6 +68,7 @@ impl From<FinalizeSettle<'_>> for Instruction {
program_id: builder.program_id,
state_pda,
begin_ix_index: builder.begin_ix_index,
only_token_program: builder.only_token_program,
source_buffers: &source_buffers,
destinations: &destinations,
bumps: &bumps,
Expand All @@ -78,7 +87,7 @@ mod tests {
fixtures::pubkey_from_seed,
instruction::{
fixtures::fake_account_from_array,
settle::{FinalizeSettleInput, INSTRUCTIONS_SYSVAR_ID, SPL_TOKEN_PROGRAM_ID},
settle::{FinalizeSettleInput, INSTRUCTIONS_SYSVAR_ID},
InstructionInputParsing,
},
};
Expand Down Expand Up @@ -107,6 +116,7 @@ mod tests {
let ix = Instruction::from(FinalizeSettle {
program_id,
begin_ix_index,
only_token_program: None,
orders: &orders,
});

Expand Down Expand Up @@ -151,10 +161,6 @@ 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.token_program_account.address(),
&SPL_TOKEN_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/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub mod remove_solver;
pub mod transfer_authority;

pub use add_solver::AddSolver;
pub use begin_settle::{BeginSettle, InitializedIntent, Pull};
pub use begin_settle::{BeginSettle, InitializedIntent, Pull, TokenProgram};
pub use create_buffer::CreateBuffers;
pub use create_order::CreateOrder;
pub use finalize_settle::{FinalizeSettle, FinalizedIntent};
Expand Down
2 changes: 2 additions & 0 deletions client/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ mod tests {
solver: payer,
finalize_ix_index: 1,
auction_id: 42,
only_token_program: None,
orders: &[InitializedIntent {
intent: &intent,
pulls: &[],
Expand All @@ -135,6 +136,7 @@ mod tests {
SettlementInstruction::FinalizeSettle => FinalizeSettle {
program_id,
begin_ix_index: 0,
only_token_program: None,
orders: &[],
}
.into(),
Expand Down
2 changes: 2 additions & 0 deletions interface/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ pub enum SettlementError {
/// mint has to be and couldn't read the answer, so it can't size the
/// buffer.
BufferSizeUnavailable = 40,
/// The token program for a given token or mint is not supported.
InvalidTokenProgram = 41,
}

impl From<SettlementError> for u32 {
Expand Down
14 changes: 6 additions & 8 deletions interface/src/instruction/create_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::SettlementInstruction;
/// `[payer (W,S), system_program (R), token_program (R), (buffer_pda (W), mint (R))...]`.
/// The three shared accounts come first and are read positionally; the
/// per-buffer pairs follow. The system program only has to be present so the
/// `CreateAccount` CPI can dispatch; it isn't read by index.
/// `CreateAccount` CPI can execute; it isn't read by index.
pub struct CreateBuffers<'a> {
pub program_id: Pubkey,
pub payer: Pubkey,
Expand Down 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 execute, 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
Loading