Skip to content
Open
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
38 changes: 19 additions & 19 deletions bench-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@
"transfer_authority/reclaim_authority_can_transfer_itself": 4
},
"compute_units": {
"add_solver/add_with_many_existing_solvers": 5074,
"add_solver/adds_a_solver": 4622,
"create_buffers/happy_path_creates_initialized_buffer_token_account": 7347,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction": 17247,
"create_buffers/max_buffers_in_one_instruction": 169571,
"create_order/happy_path_creates_order_pda_with_expected_body": 4985,
"initialize/happy_path_initializes_state_pda_with_expected_data": 4530,
"reclaim_buffer/funded_buffer_is_skipped": 4835,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself": 5982,
"reclaim_buffer/max_buffers_in_one_instruction": 124622,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded": 7581,
"reclaim_order/happy_path_expired_returns_lamports_and_closes_pda": 2202,
"reclaim_order/happy_path_on_chain_order_cancelled_is_reclaimable_before_expiry": 2071,
"reclaim_order/happy_path_on_chain_order_fully_filled_is_reclaimable_before_expiry": 2079,
"add_solver/add_with_many_existing_solvers": 5075,
"add_solver/adds_a_solver": 4623,
"create_buffers/happy_path_creates_initialized_buffer_token_account": 7348,
"create_buffers/happy_path_creates_multiple_buffers_in_one_instruction": 17248,
"create_buffers/max_buffers_in_one_instruction": 169572,
"create_order/happy_path_creates_order_pda_with_expected_body": 4987,
"initialize/happy_path_initializes_state_pda_with_expected_data": 4531,
"reclaim_buffer/funded_buffer_is_skipped": 4834,
"reclaim_buffer/happy_path_reclaims_empty_buffer_to_the_authority_itself": 5981,
"reclaim_buffer/max_buffers_in_one_instruction": 124621,
"reclaim_buffer/reclaims_multiple_buffers_skipping_funded": 7580,
"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,
"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": 3757,
"remove_solver/removes_a_solver": 3492,
"remove_solver/remove_with_many_existing_solvers": 3759,
"remove_solver/removes_a_solver": 3494,
"settle/finalizes_with_no_pushes": 7154,
"settle/pulls_from_multiple_orders": 20059,
"settle/pulls_funds_to_destination": 13640,
Expand All @@ -59,9 +59,9 @@
"settle/pushes_several_orders_from_one_buffer": 17766,
"settle/settles_a_single_order": 12513,
"settle/settles_multiple_orders": 23086,
"transfer_authority/manager_can_transfer_manager": 3173,
"transfer_authority/manager_can_transfer_reclaim_authority": 3175,
"transfer_authority/reclaim_authority_can_transfer_itself": 3179
"transfer_authority/manager_can_transfer_manager": 3175,
"transfer_authority/manager_can_transfer_reclaim_authority": 3177,
"transfer_authority/reclaim_authority_can_transfer_itself": 3181
},
"transaction_bytes": {
"add_solver/add_with_many_existing_solvers": 366,
Expand Down
27 changes: 27 additions & 0 deletions client/src/instruction/add_solver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//! Builder for the `AddSolver` instruction.

use cow_settlement_interface::{pda::state::find_state_pda, Instruction, Pubkey};

/// Inserts `solver` into the state PDA's solver list. `manager` authorizes the
/// change and must be the current manager; `payer` funds the account's growth.
/// Both sign.
pub struct AddSolver {
pub program_id: Pubkey,
pub manager: Pubkey,
pub payer: Pubkey,
pub solver: Pubkey,
}

impl From<AddSolver> for Instruction {
fn from(builder: AddSolver) -> Self {
let (state_pda, _bump) = find_state_pda(&builder.program_id);
cow_settlement_interface::instruction::add_solver::AddSolver {
program_id: builder.program_id,
manager: builder.manager,
payer: builder.payer,
state_pda,
solver: builder.solver,
}
.into()
}
}
134 changes: 134 additions & 0 deletions client/src/instruction/begin_settle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! Builder for the `BeginSettle` instruction.

use cow_settlement_interface::{
data::intent::OrderIntent,
pda::{order::find_order_pda, state::find_state_pda},
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;

/// 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
/// its sell token account.
pub struct InitializedIntent<'a> {
pub intent: &'a OrderIntent,
pub pulls: &'a [Pull],
}

/// Builder for a `BeginSettle` instruction settling the given orders.
pub struct BeginSettle<'a> {
pub program_id: Pubkey,
pub solver: Pubkey,
pub finalize_ix_index: u16,
/// The off-chain auction this settlement executes, carried so it can be tied
/// back to its auction off-chain.
pub auction_id: i64,
pub orders: &'a [InitializedIntent<'a>],
}

impl From<BeginSettle<'_>> for Instruction {
fn from(builder: BeginSettle<'_>) -> Self {
let mut order_pdas = Vec::with_capacity(builder.orders.len());
let mut sell_token_accounts = Vec::with_capacity(builder.orders.len());
let mut pull_lists: Vec<&[Pull]> = Vec::with_capacity(builder.orders.len());
for order in builder.orders {
let (order_pda, _bump) = find_order_pda(&builder.program_id, &order.intent.uid());
order_pdas.push(order_pda);
sell_token_accounts.push(order.intent.sell_token_account);
pull_lists.push(order.pulls);
}
let (state_pda, _bump) = find_state_pda(&builder.program_id);
cow_settlement_interface::instruction::settle::BeginSettle {
program_id: builder.program_id,
state_pda,
solver: builder.solver,
finalize_ix_index: builder.finalize_ix_index,
auction_id: builder.auction_id,
order_pdas: &order_pdas,
sell_token_accounts: &sell_token_accounts,
pulls: &pull_lists,
}
.into()
}
}

#[cfg(test)]
mod tests {
use super::*;
use ::proptest::{prelude::*, test_runner::TestCaseError};
use cow_settlement_interface::{
data::intent::fixtures::arb_order_intent,
fixtures::pubkey_from_seed,
instruction::{
fixtures::fake_account_from_array,
settle::{BeginSettleInput, INSTRUCTIONS_SYSVAR_ID},
InstructionInputParsing,
},
};

proptest! {
// `BeginSettle` derives each order's PDA from its intent and forwards to
// the interface builder so that the on-chain parser recovers exactly
// those orders.
#[test]
fn begin_settle_derives_orders_from_intents(
finalize_ix_index in any::<u16>(),
intents in prop::collection::vec(arb_order_intent(), 1..=5),
) {
let program_id = pubkey_from_seed("program id");
// No pulls here: this test only checks that orders are derived and
// laid out correctly.
let orders: Vec<InitializedIntent> = intents
.iter()
.map(|intent| InitializedIntent { intent, pulls: &[] })
.collect();
let ix = Instruction::from(BeginSettle {
program_id,
solver: pubkey_from_seed("solver"),
finalize_ix_index,
auction_id: 0,
orders: &orders,
});

// Expected orders: each intent's canonical PDA paired with its sell
// token account, sorted by PDA address (the builder's order).
let mut expected: Vec<(Pubkey, Pubkey)> = intents
.iter()
.map(|intent| {
let (order_pda, _bump) = find_order_pda(&program_id, &intent.uid());
(order_pda, intent.sell_token_account)
})
.collect();
expected.sort_by_key(|(order_pda, _)| *order_pda);

let accounts: Vec<_> = ix
.accounts
.iter()
.map(|meta| fake_account_from_array(meta.pubkey.to_bytes()))
.collect();
let parsed = BeginSettleInput::parse(&ix.data, &accounts)
.map_err(|e| TestCaseError::fail(format!("parse failed: {e:?}")))?;

prop_assert_eq!(parsed.finalize_ix_index, finalize_ix_index);
prop_assert_eq!(
parsed.instructions_sysvar_account.address(),
&INSTRUCTIONS_SYSVAR_ID,
);

let actual: Vec<(Pubkey, Pubkey)> = parsed
.orders
.iter()
.map(|order| {
(
*order.order_pda.address(),
*order.sell_token_account.address(),
)
})
.collect();
prop_assert_eq!(actual, expected);
}
}
}
25 changes: 25 additions & 0 deletions client/src/instruction/create_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Builder for the `CreateBuffer` instruction.

use cow_settlement_interface::{pda::buffer::find_buffer_pda, Instruction, Pubkey};

pub struct CreateBuffers<'a> {
pub program_id: Pubkey,
pub payer: Pubkey,
pub mints: &'a [Pubkey],
}

impl From<CreateBuffers<'_>> for Instruction {
fn from(builder: CreateBuffers<'_>) -> Self {
let buffers: Vec<(Pubkey, Pubkey)> = builder
.mints
.iter()
.map(|mint| (find_buffer_pda(&builder.program_id, mint).0, *mint))
.collect();
cow_settlement_interface::instruction::create_buffer::CreateBuffers {
program_id: builder.program_id,
payer: builder.payer,
buffers: &buffers,
}
.into()
}
}
30 changes: 30 additions & 0 deletions client/src/instruction/create_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Builder for the `CreateOrder` instruction.

use cow_settlement_interface::{
data::intent::{EncodedOrderIntent, OrderIntent},
pda::order::find_order_pda,
Instruction, Pubkey,
};

pub struct CreateOrder<'a> {
pub program_id: Pubkey,
pub owner: Pubkey,
pub created_by: Pubkey,
pub intent: &'a OrderIntent,
}

impl From<CreateOrder<'_>> for Instruction {
fn from(builder: CreateOrder<'_>) -> Self {
let encoded = EncodedOrderIntent::from(builder.intent);
let (order_pda, _bump) = find_order_pda(&builder.program_id, &encoded.hash());
let intent_bytes: [u8; EncodedOrderIntent::SIZE] = (&encoded).into();
cow_settlement_interface::instruction::create_order::CreateOrder {
program_id: builder.program_id,
owner: builder.owner,
created_by: builder.created_by,
order_pda,
intent_bytes,
}
.into()
}
}
Loading