use cow_settlement_interface::{
data::intent::{Asset, OrderIntent},
pda::{
buffer::{find_buffer_pda, find_native_sol_buffer_pda},
order::find_order_pda,
state::find_state_pda,
},
Instruction, Pubkey,
};
use super::begin_settle::TokenProgram;
pub struct FinalizedIntent<'a> {
pub intent: &'a OrderIntent,
pub amount: u64,
pub use_transfer_checked: bool,
}
pub struct FinalizeSettle<'a> {
pub program_id: Pubkey,
pub begin_ix_index: u16,
pub only_token_program: Option<TokenProgram>,
pub orders: &'a [FinalizedIntent<'a>],
}
impl From<FinalizeSettle<'_>> for Instruction {
fn from(builder: FinalizeSettle<'_>) -> Self {
let mut orders: Vec<&FinalizedIntent> = builder.orders.iter().collect();
orders.sort_by_key(|order| find_order_pda(&builder.program_id, &order.intent.uid()).0);
let pushes: Vec<OrderPush> = orders
.iter()
.map(|order| order.push(&builder.program_id))
.collect();
instruction_from_pushes(
builder.program_id,
builder.begin_ix_index,
builder.only_token_program,
&pushes,
)
}
}
#[derive(Debug, PartialEq)]
struct OrderPush {
source_buffer: Pubkey,
destination: Pubkey,
mint: Option<Pubkey>,
bump: u8,
amount: u64,
}
impl FinalizedIntent<'_> {
fn push(&self, program_id: &Pubkey) -> OrderPush {
let (source_buffer, bump, destination, mint) = match &self.intent.buy {
Asset::Native(account) => {
let (buffer, bump) = find_native_sol_buffer_pda(program_id);
(buffer, bump, *account, None)
}
Asset::TokenProgram(token) => {
let (buffer, bump) = find_buffer_pda(program_id, &token.mint);
let mint = self.use_transfer_checked.then_some(token.mint);
(buffer, bump, token.token_account, mint)
}
};
OrderPush {
source_buffer,
destination,
mint,
bump,
amount: self.amount,
}
}
}
fn instruction_from_pushes(
program_id: Pubkey,
begin_ix_index: u16,
only_token_program: Option<TokenProgram>,
pushes: &[OrderPush],
) -> Instruction {
let source_buffers: Vec<Pubkey> = pushes.iter().map(|push| push.source_buffer).collect();
let destinations: Vec<Pubkey> = pushes.iter().map(|push| push.destination).collect();
let mints: Vec<Option<Pubkey>> = pushes.iter().map(|push| push.mint).collect();
let bumps: Vec<u8> = pushes.iter().map(|push| push.bump).collect();
let amounts: Vec<u64> = pushes.iter().map(|push| push.amount).collect();
cow_settlement_interface::instruction::settle::FinalizeSettle {
program_id,
state_pda: find_state_pda(&program_id).0,
begin_ix_index,
only_token_program,
source_buffers: &source_buffers,
destinations: &destinations,
mints: &mints,
bumps: &bumps,
amounts: &amounts,
}
.into()
}
#[cfg(test)]
mod tests {
use super::*;
use ::proptest::prelude::*;
use cow_settlement_interface::{
data::intent::{fixtures::arb_order_intent, TokenAsset},
fixtures::pubkey_from_seed,
instruction::{
fixtures::fake_account_from_array, settle::FinalizeSettleInput, InstructionInputParsing,
},
};
#[test]
fn native_sol_push_never_carries_a_mint() {
let program_id = pubkey_from_seed("program id");
let (native_sol_buffer, native_sol_bump) = find_native_sol_buffer_pda(&program_id);
let recipient = pubkey_from_seed("recipient wallet");
let intent = OrderIntent {
buy: Asset::Native(recipient),
..OrderIntent::default()
};
for use_transfer_checked in [false, true] {
let order = FinalizedIntent {
intent: &intent,
amount: 42,
use_transfer_checked,
};
assert_eq!(
order.push(&program_id),
OrderPush {
source_buffer: native_sol_buffer,
destination: recipient,
mint: None,
bump: native_sol_bump,
amount: 42,
},
);
}
}
#[test]
fn token_push_comes_from_the_mint_buffer() {
let program_id = pubkey_from_seed("program id");
let mint = pubkey_from_seed("buy mint");
let token_account = pubkey_from_seed("buy token account");
let (buffer, bump) = find_buffer_pda(&program_id, &mint);
let intent = OrderIntent {
buy: Asset::TokenProgram(TokenAsset {
mint,
token_account,
}),
..OrderIntent::default()
};
for (use_transfer_checked, expected_mint) in [(false, None), (true, Some(mint))] {
let order = FinalizedIntent {
intent: &intent,
amount: 42,
use_transfer_checked,
};
assert_eq!(
order.push(&program_id),
OrderPush {
source_buffer: buffer,
destination: token_account,
mint: expected_mint,
bump,
amount: 42,
},
);
}
}
#[test]
fn native_sol_order_pushes_from_the_native_sol_buffer() {
let program_id = pubkey_from_seed("program id");
let recipient = pubkey_from_seed("recipient wallet");
let intent = OrderIntent {
buy: Asset::Native(recipient),
..OrderIntent::default()
};
let ix = Instruction::from(FinalizeSettle {
program_id,
begin_ix_index: 0,
only_token_program: None,
orders: &[FinalizedIntent {
intent: &intent,
amount: 1_337,
use_transfer_checked: false,
}],
});
let accounts: Vec<_> = ix
.accounts
.iter()
.map(|meta| fake_account_from_array(meta.pubkey.to_bytes()))
.collect();
let parsed = FinalizeSettleInput::parse(&ix.data, &accounts)
.expect("the builder emits a parseable finalize");
let pushes: Vec<_> = parsed.pushes.iter().collect();
let [push] = pushes.as_slice() else {
panic!("one order pushes once, got {} pushes", pushes.len());
};
let (native_sol_buffer, native_sol_bump) = find_native_sol_buffer_pda(&program_id);
assert_eq!(push.source_buffer.address(), &native_sol_buffer);
assert_eq!(push.bump, native_sol_bump);
assert_eq!(push.destination.address(), &recipient);
assert_eq!(push.amount, 1_337);
}
proptest! {
#[test]
fn finalize_settle_sorts_pushes_by_order_pda(
begin_ix_index in any::<u16>(),
cases in prop::collection::vec(
(arb_order_intent(), any::<u64>(), any::<bool>()),
1..=5,
),
) {
let program_id = pubkey_from_seed("program id");
let orders: Vec<FinalizedIntent> = cases
.iter()
.map(|(intent, amount, use_transfer_checked)| FinalizedIntent {
intent,
amount: *amount,
use_transfer_checked: *use_transfer_checked,
})
.collect();
let ix = Instruction::from(FinalizeSettle {
program_id,
begin_ix_index,
only_token_program: None,
orders: &orders,
});
let mut expected: Vec<(Pubkey, OrderPush)> = orders
.iter()
.map(|order| {
let (order_pda, _bump) = find_order_pda(&program_id, &order.intent.uid());
(order_pda, order.push(&program_id))
})
.collect();
expected.sort_by_key(|(order_pda, _)| *order_pda);
let expected: Vec<OrderPush> = expected.into_iter().map(|(_, push)| push).collect();
prop_assert_eq!(
ix,
instruction_from_pushes(program_id, begin_ix_index, None, &expected),
);
}
}
}