Skip to content

Commit 4b2ba50

Browse files
committed
Parse pushes in FinalizeSettle
1 parent 4989c8f commit 4b2ba50

11 files changed

Lines changed: 929 additions & 191 deletions

File tree

DESIGN.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,8 @@ Differences with Ethereum:
256256
A settlement transaction is split into multiple instructions. All settlement operations occur between a `BeginSettle` and a `FinalizeSettle` instruction with the exception of arbitrary interactions, which can take place at any point of a transaction. Except for that, the order of instructions in the transaction is arbitrary.
257257

258258
- `BeginSettle`: Snapshots each order's receiver token account, spender token account, and withdrawal balances. Pulls funds from each order’s sell token account to the solver-specified destination accounts, using the settlement state PDA’s token delegation. Carries an explicit `finalize_ix_index` pointing to its paired `FinalizeSettle`.
259-
- `Push`: It references a unique SPL transfer token instruction between `BeginSettle` and `FinalizeSettle` that sends the proceeds of an order to its buy token account.
260259
- (arbitrary interactions): Any instruction from the solver. This could be a token transfer, an AMM swap, or anything else.
261-
- `FinalizeSettle`: Reads balances again, computes deltas against the snapshots, validates clearing/limit prices, updates `amount_received` and order status, revokes solver approvals. Carries an explicit `begin_ix_index` pointing to its paired `BeginSettle`.
260+
- `FinalizeSettle`: Pushes the proceeds of each order from the settlement’s buffer accounts to the order’s buy token account, using the settlement state PDA’s authority over the buffers. Reads balances again, computes deltas against the snapshots, validates clearing/limit prices, updates `amount_received` and order status, revokes solver approvals. Carries an explicit `begin_ix_index` pointing to its paired `BeginSettle`.
262261

263262
Additionally, a settlement transaction will include the batch number as part of the instruction bytes of `BeginSettle`.
264263

client/src/instructions.rs

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,10 @@ use settlement_interface::{
1111
Instruction, Pubkey,
1212
};
1313

14-
// Reexport the instruction builders that don't change from the interface.
15-
// We want the client to provide all instruction builders.
16-
pub use settlement_interface::instruction::settle::FinalizeSettle;
17-
18-
/// An order to settle together with the funds to pull from it: `intent`
19-
/// identifies the order and `pulls` lists the `(destination, amount)` transfers
20-
/// to make from its sell token account.
21-
pub struct SettledOrder<'a> {
14+
/// An order ready to be settled, together with the funds to pull from it:
15+
/// `intent` identifies the order and `pulls` lists the `(destination, amount)`
16+
/// transfers to make from its sell token account.
17+
pub struct SettleableOrder<'a> {
2218
pub intent: &'a OrderIntent,
2319
pub pulls: &'a [(Pubkey, u64)],
2420
}
@@ -27,7 +23,7 @@ pub struct SettledOrder<'a> {
2723
pub struct BeginSettle<'a> {
2824
pub program_id: Pubkey,
2925
pub finalize_ix_index: u16,
30-
pub orders: &'a [SettledOrder<'a>],
26+
pub orders: &'a [SettleableOrder<'a>],
3127
}
3228

3329
impl BeginSettle<'_> {
@@ -57,6 +53,58 @@ impl BeginSettle<'_> {
5753
}
5854
}
5955

56+
/// A settled order whose proceeds are pushed to it: `intent` identifies the
57+
/// order (its `buy_token_account` is the push destination), `mint` selects the
58+
/// canonical source buffer, and `amount` is the quantity to push.
59+
pub struct SettledOrder<'a> {
60+
pub intent: &'a OrderIntent,
61+
pub mint: Pubkey,
62+
pub amount: u64,
63+
}
64+
65+
/// Builder for a `FinalizeSettle` instruction pushing each order's proceeds to
66+
/// its buy token account.
67+
///
68+
/// The destination is the order intent's `buy_token_account` and the source is
69+
/// the canonical buffer PDA for `mint` (see [`find_buffer_pda`]). The orders are
70+
/// sorted by their canonical order PDA (the same key [`BeginSettle`] orders its
71+
/// settled-order list by) so the two instructions present the orders in the
72+
/// same order and their lists line up.
73+
pub struct FinalizeSettle<'a> {
74+
pub program_id: Pubkey,
75+
pub begin_ix_index: u16,
76+
pub orders: &'a [SettledOrder<'a>],
77+
}
78+
79+
impl FinalizeSettle<'_> {
80+
pub fn instruction(self) -> Instruction {
81+
// Sort the orders by their canonical order PDA, the key `BeginSettle`
82+
// lays its settled orders out by, so the two instructions' lists align.
83+
let mut order: Vec<usize> = (0..self.orders.len()).collect();
84+
order.sort_by_key(|&i| find_order_pda(&self.program_id, &self.orders[i].intent.uid()).0);
85+
86+
let mut source_buffers = Vec::with_capacity(self.orders.len());
87+
let mut destinations = Vec::with_capacity(self.orders.len());
88+
let mut amounts = Vec::with_capacity(self.orders.len());
89+
for &i in &order {
90+
let (buffer_pda, _bump) = find_buffer_pda(&self.program_id, &self.orders[i].mint);
91+
source_buffers.push(buffer_pda);
92+
destinations.push(self.orders[i].intent.buy_token_account);
93+
amounts.push(self.orders[i].amount);
94+
}
95+
let (state_pda, _bump) = find_state_pda(&self.program_id);
96+
settlement_interface::instruction::settle::FinalizeSettle {
97+
program_id: self.program_id,
98+
state_pda,
99+
begin_ix_index: self.begin_ix_index,
100+
source_buffers: &source_buffers,
101+
destinations: &destinations,
102+
amounts: &amounts,
103+
}
104+
.instruction()
105+
}
106+
}
107+
60108
pub struct CreateOrder<'a> {
61109
pub program_id: Pubkey,
62110
pub owner: Pubkey,
@@ -124,14 +172,16 @@ mod tests {
124172
data::intent::fixtures::arb_order_intent,
125173
instruction::{
126174
fixtures::fake_account_from_array,
127-
settle::{BeginSettleInput, INSTRUCTIONS_SYSVAR_ID},
175+
settle::{
176+
BeginSettleInput, FinalizeSettleInput, INSTRUCTIONS_SYSVAR_ID, SPL_TOKEN_PROGRAM_ID,
177+
},
128178
InstructionInputParsing,
129179
},
130180
pda::order::find_order_pda,
131181
};
132182

133183
proptest! {
134-
// `begin_settle` derives each order's PDA from its intent and forwards to
184+
// `BeginSettle` derives each order's PDA from its intent and forwards to
135185
// the interface builder so that the on-chain parser recovers exactly
136186
// those orders.
137187
#[test]
@@ -142,9 +192,9 @@ mod tests {
142192
let program_id = Pubkey::new_unique();
143193
// No pulls here: this test only checks that orders are derived and
144194
// laid out correctly.
145-
let orders: Vec<SettledOrder> = intents
195+
let orders: Vec<SettleableOrder> = intents
146196
.iter()
147-
.map(|intent| SettledOrder { intent, pulls: &[] })
197+
.map(|intent| SettleableOrder { intent, pulls: &[] })
148198
.collect();
149199
let ix = BeginSettle {
150200
program_id,
@@ -186,5 +236,76 @@ mod tests {
186236
prop_assert_eq!(order.bump, *bump);
187237
}
188238
}
239+
240+
// `FinalizeSettle` derives each order's source buffer from its mint and
241+
// destination from the intent, sorting by canonical order PDA like
242+
// `BeginSettle` so the on-chain parser recovers exactly those pushes in
243+
// that order.
244+
#[test]
245+
fn finalize_settle_derives_buffers_from_mints(
246+
begin_ix_index in any::<u16>(),
247+
cases in prop::collection::vec(
248+
(arb_order_intent(), any::<[u8; 32]>(), any::<u64>()),
249+
1..=5,
250+
),
251+
) {
252+
let program_id = Pubkey::new_unique();
253+
let orders: Vec<SettledOrder> = cases
254+
.iter()
255+
.map(|(intent, mint, amount)| SettledOrder {
256+
intent,
257+
mint: Pubkey::new_from_array(*mint),
258+
amount: *amount,
259+
})
260+
.collect();
261+
let ix = FinalizeSettle {
262+
program_id,
263+
begin_ix_index,
264+
orders: &orders,
265+
}
266+
.instruction();
267+
268+
// Expected pushes: each order's buffer PDA, buy token account, and
269+
// amount, sorted by the order's canonical PDA (the builder's order).
270+
let mut expected: Vec<(Pubkey, Pubkey, Pubkey, u64)> = orders
271+
.iter()
272+
.map(|order| {
273+
let (order_pda, _bump) = find_order_pda(&program_id, &order.intent.uid());
274+
let (buffer_pda, _bump) = find_buffer_pda(&program_id, &order.mint);
275+
(order_pda, buffer_pda, order.intent.buy_token_account, order.amount)
276+
})
277+
.collect();
278+
expected.sort_by_key(|(order_pda, ..)| *order_pda);
279+
280+
let mut accounts: Vec<_> = ix
281+
.accounts
282+
.iter()
283+
.map(|meta| fake_account_from_array(meta.pubkey.to_bytes()))
284+
.collect();
285+
let parsed = FinalizeSettleInput::parse(&ix.data, &mut accounts)
286+
.map_err(|e| TestCaseError::fail(format!("parse failed: {e:?}")))?;
287+
288+
prop_assert_eq!(parsed.begin_ix_index, begin_ix_index);
289+
prop_assert_eq!(
290+
parsed.instructions_sysvar_account.address(),
291+
&INSTRUCTIONS_SYSVAR_ID,
292+
);
293+
let (state_pda, _bump) = find_state_pda(&program_id);
294+
prop_assert_eq!(parsed.state_pda_account.address(), &state_pda);
295+
prop_assert_eq!(
296+
parsed.token_program_account.address(),
297+
&SPL_TOKEN_PROGRAM_ID,
298+
);
299+
300+
let parsed_pushes: Vec<_> = parsed.pushes.into_iter().collect();
301+
prop_assert_eq!(parsed_pushes.len(), expected.len());
302+
for (push, (_order_pda, buffer, destination, amount)) in
303+
parsed_pushes.iter().zip(&expected)
304+
{
305+
prop_assert_eq!(push.source_buffer.address(), buffer);
306+
prop_assert_eq!(push.destination.address(), destination);
307+
prop_assert_eq!(u64::from_be_bytes(*push.amount), *amount);
308+
}
309+
}
189310
}
190311
}

0 commit comments

Comments
 (0)