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