cow_settlement_interface/instruction/
cancel_order.rs1use solana_instruction::{AccountMeta, Instruction};
8use solana_program_error::ProgramError;
9use solana_pubkey::Pubkey;
10
11pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
12
13use super::InstructionInputParsing;
14use crate::{data::intent::EncodedOrderIntent, SettlementInstruction};
15
16pub struct CancelOrder {
49 pub program_id: Pubkey,
50 pub owner: Pubkey,
51 pub created_by: Pubkey,
52 pub order_pda: Pubkey,
53 pub intent_bytes: Option<[u8; EncodedOrderIntent::SIZE]>,
54}
55
56impl From<CancelOrder> for Instruction {
57 fn from(builder: CancelOrder) -> Self {
58 let mut data = Vec::with_capacity(1 + EncodedOrderIntent::SIZE);
59 data.push(SettlementInstruction::CancelOrder.discriminator());
60
61 let created_by = match builder.intent_bytes {
65 Some(intent_bytes) => {
66 data.extend_from_slice(&intent_bytes);
67 AccountMeta::new(builder.created_by, true)
68 }
69 None => AccountMeta::new_readonly(builder.created_by, false),
70 };
71
72 Instruction {
73 program_id: builder.program_id,
74 accounts: vec![
75 AccountMeta::new_readonly(builder.owner, true),
76 created_by,
77 AccountMeta::new(builder.order_pda, false),
78 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
79 ],
80 data,
81 }
82 }
83}
84
85pub struct CancelOrderInput<'a, A> {
89 pub intent_bytes: Option<[u8; EncodedOrderIntent::SIZE]>,
90 pub owner: &'a A,
91 pub created_by: &'a A,
92 pub order_pda: &'a A,
93}
94
95impl<'a, A> InstructionInputParsing<'a, A> for CancelOrderInput<'a, A> {
96 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CancelOrder;
97
98 fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
99 let intent_bytes = if instruction_data.is_empty() {
100 None
101 } else {
102 Some(
103 instruction_data
104 .try_into()
105 .map_err(|_| ProgramError::InvalidInstructionData)?,
106 )
107 };
108
109 let [owner, created_by, order_pda, _, ..] = accounts else {
114 return Err(ProgramError::NotEnoughAccountKeys);
115 };
116
117 Ok(Self {
118 intent_bytes,
119 owner,
120 created_by,
121 order_pda,
122 })
123 }
124}
125
126#[cfg(any(test, feature = "test-fixtures"))]
129pub mod fixtures {
130 use solana_address::Address;
131
132 use super::{CancelOrder, Instruction};
133 use crate::data::intent::EncodedOrderIntent;
134
135 pub use crate::instruction::create_order::fixtures::valid_intent_bytes;
138
139 pub const NUM_ACCOUNTS: usize = 4;
142
143 pub fn default_cancel_data(intent_bytes: Option<[u8; EncodedOrderIntent::SIZE]>) -> Vec<u8> {
147 let zero = Address::new_from_array([0; 32]);
148 Instruction::from(CancelOrder {
149 program_id: zero,
150 owner: zero,
151 created_by: zero,
152 order_pda: zero,
153 intent_bytes,
154 })
155 .data
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::fixtures::{default_cancel_data, valid_intent_bytes, NUM_ACCOUNTS};
162 use super::*;
163 use crate::fixtures::pubkey_from_seed;
164 use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
165 use crate::instruction::tests::{
166 assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
167 assert_writable_signer,
168 };
169 use solana_account_view::AccountView;
170
171 #[test]
172 fn cancel_order_input_parses_valid_input() {
173 let program_id = pubkey_from_seed("program id");
174 let owner = pubkey_from_seed("owner");
175 let created_by = pubkey_from_seed("created by");
176 let order_pda = pubkey_from_seed("order pda");
177 let intent_bytes = valid_intent_bytes();
178
179 let data = Instruction::from(CancelOrder {
180 program_id,
181 owner,
182 created_by,
183 order_pda,
184 intent_bytes: Some(intent_bytes),
185 })
186 .data;
187 let accounts = [
188 fake_account(owner),
189 fake_account(created_by),
190 fake_account(order_pda),
191 fake_account(pubkey_from_seed("system program")),
192 ];
193
194 let CancelOrderInput {
195 intent_bytes: derived_intent_bytes,
196 owner: derived_owner,
197 created_by: derived_created_by,
198 order_pda: derived_order_pda,
199 } = CancelOrderInput::parse(&data, &accounts).expect("parse should succeed");
200
201 assert_eq!(derived_intent_bytes, Some(intent_bytes));
202 assert_eq!(*derived_order_pda.address(), order_pda);
203 assert_eq!(*derived_owner.address(), owner);
204 assert_eq!(*derived_created_by.address(), created_by);
205 }
206
207 #[test]
208 fn cancel_order_input_parses_without_intent() {
209 let owner = pubkey_from_seed("owner");
210 let created_by = pubkey_from_seed("created by");
211 let order_pda = pubkey_from_seed("order pda");
212
213 let data = default_cancel_data(None);
214 let accounts = [
215 fake_account(owner),
216 fake_account(created_by),
217 fake_account(order_pda),
218 fake_account(pubkey_from_seed("system program")),
219 ];
220
221 let CancelOrderInput {
222 intent_bytes: derived_intent_bytes,
223 owner: derived_owner,
224 order_pda: derived_order_pda,
225 ..
226 } = CancelOrderInput::parse(&data, &accounts).expect("parse should succeed");
227
228 assert_eq!(
229 derived_intent_bytes, None,
230 "an empty body carries no intent"
231 );
232 assert_eq!(*derived_order_pda.address(), order_pda);
233 assert_eq!(*derived_owner.address(), owner);
234 }
235
236 #[test]
237 fn cancel_order_input_rejects_short_data() {
238 let intent_bytes = valid_intent_bytes();
239 let mut data = default_cancel_data(Some(intent_bytes));
240 data.pop();
241 let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
242 assert_eq!(
243 CancelOrderInput::parse(&data, &accounts).err(),
244 Some(ProgramError::InvalidInstructionData),
245 );
246 }
247
248 #[test]
249 fn cancel_order_input_rejects_long_data() {
250 let intent_bytes = valid_intent_bytes();
251 let mut data = default_cancel_data(Some(intent_bytes));
252 data.push(0); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
254 assert_eq!(
255 CancelOrderInput::parse(&data, &accounts).err(),
256 Some(ProgramError::InvalidInstructionData),
257 );
258 }
259
260 #[test]
261 fn cancel_order_input_rejects_missing_accounts() {
262 let intent_bytes = valid_intent_bytes();
263 let data = default_cancel_data(Some(intent_bytes));
264 let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
265 accounts.pop();
266 assert_eq!(
267 CancelOrderInput::parse(&data, &accounts).err(),
268 Some(ProgramError::NotEnoughAccountKeys),
269 );
270 }
271
272 #[test]
273 fn instruction_data_has_expected_layout() {
274 let program_id = pubkey_from_seed("program id");
275 let owner = pubkey_from_seed("owner");
276 let created_by = pubkey_from_seed("created by");
277 let order_pda = pubkey_from_seed("order pda");
278 let intent_bytes = [0x42u8; EncodedOrderIntent::SIZE];
279
280 let Instruction { data, .. } = CancelOrder {
281 program_id,
282 owner,
283 created_by,
284 order_pda,
285 intent_bytes: Some(intent_bytes),
286 }
287 .into();
288
289 assert_eq!(data.len(), 1 + EncodedOrderIntent::SIZE);
290 assert_eq!(data[0], SettlementInstruction::CancelOrder.discriminator());
291 assert_eq!(&data[1..], &intent_bytes);
292 }
293
294 #[test]
295 fn instruction_data_without_intent_is_discriminator_only() {
296 let Instruction { data, .. } = CancelOrder {
297 program_id: pubkey_from_seed("program id"),
298 owner: pubkey_from_seed("owner"),
299 created_by: pubkey_from_seed("created by"),
300 order_pda: pubkey_from_seed("order pda"),
301 intent_bytes: None,
302 }
303 .into();
304
305 assert_eq!(
306 data,
307 vec![SettlementInstruction::CancelOrder.discriminator()],
308 "an intent-less cancellation carries only the discriminator"
309 );
310 }
311
312 #[test]
313 fn instruction_accounts_without_intent_leave_created_by_unsigned() {
314 let program_id = pubkey_from_seed("program id");
315 let owner = pubkey_from_seed("owner");
316 let created_by = pubkey_from_seed("created by");
317 let order_pda = pubkey_from_seed("order pda");
318
319 let Instruction { accounts, .. } = CancelOrder {
320 program_id,
321 owner,
322 created_by,
323 order_pda,
324 intent_bytes: None,
325 }
326 .into();
327
328 assert_eq!(accounts.len(), 4);
331 assert_readonly_signer(&accounts[0], owner);
332 assert_readonly_nonsigner(&accounts[1], created_by);
333 assert_writable_nonsigner(&accounts[2], order_pda);
334 assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
335 }
336
337 #[test]
338 fn instruction_data_has_expected_accounts() {
339 let program_id = pubkey_from_seed("program id");
340 let owner = pubkey_from_seed("owner");
341 let created_by = pubkey_from_seed("created by");
342 let order_pda = pubkey_from_seed("order pda");
343 let intent_bytes = [0u8; EncodedOrderIntent::SIZE];
344
345 let Instruction { accounts, .. } = CancelOrder {
346 program_id,
347 owner,
348 created_by,
349 order_pda,
350 intent_bytes: Some(intent_bytes),
351 }
352 .into();
353
354 assert_eq!(accounts.len(), 4);
355 assert_readonly_signer(&accounts[0], owner);
359 assert_writable_signer(&accounts[1], created_by);
360 assert_writable_nonsigner(&accounts[2], order_pda);
361 assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
362 }
363}