cow_settlement_interface/instruction/
create_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 CreateOrder {
51 pub program_id: Pubkey,
52 pub owner: Pubkey,
53 pub created_by: Pubkey,
54 pub order_pda: Pubkey,
55 pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
56}
57
58impl From<CreateOrder> for Instruction {
59 fn from(builder: CreateOrder) -> Self {
60 let mut data = Vec::with_capacity(1 + EncodedOrderIntent::SIZE);
61 data.push(SettlementInstruction::CreateOrder.discriminator());
62 data.extend_from_slice(&builder.intent_bytes);
63
64 Instruction {
65 program_id: builder.program_id,
66 accounts: vec![
67 AccountMeta::new_readonly(builder.owner, true),
68 AccountMeta::new(builder.created_by, true),
69 AccountMeta::new(builder.order_pda, false),
70 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
71 ],
72 data,
73 }
74 }
75}
76
77pub struct CreateOrderInput<'a, A> {
79 pub intent_bytes: [u8; EncodedOrderIntent::SIZE],
80 pub owner: &'a A,
81 pub created_by: &'a A,
82 pub order_pda: &'a A,
83}
84
85impl<'a, A> InstructionInputParsing<'a, A> for CreateOrderInput<'a, A> {
86 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateOrder;
87
88 fn parse_body(instruction_data: &'a [u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
89 if instruction_data.len() != EncodedOrderIntent::SIZE {
91 return Err(ProgramError::InvalidInstructionData);
92 }
93 let [owner, created_by, order_pda, _, ..] = accounts else {
98 return Err(ProgramError::NotEnoughAccountKeys);
99 };
100
101 let intent_bytes: [u8; EncodedOrderIntent::SIZE] =
102 instruction_data.try_into().expect("length checked above");
103
104 Ok(Self {
105 intent_bytes,
106 owner,
107 created_by,
108 order_pda,
109 })
110 }
111}
112
113#[cfg(any(test, feature = "test-fixtures"))]
116pub mod fixtures {
117 use solana_address::Address;
118
119 use super::{CreateOrder, Instruction};
120 use crate::data::intent::{fixtures::sample_intent, EncodedOrderIntent, OrderIntent};
121
122 pub const DEFAULT_OWNER: Address = Address::new_from_array([0x11; 32]);
124
125 pub const NUM_ACCOUNTS: usize = 4;
128
129 pub fn valid_intent_bytes() -> [u8; EncodedOrderIntent::SIZE] {
132 (&EncodedOrderIntent::from(&OrderIntent {
133 owner: DEFAULT_OWNER,
134 ..sample_intent(Default::default())
135 }))
136 .into()
137 }
138
139 pub fn default_order_data(intent_bytes: &[u8; EncodedOrderIntent::SIZE]) -> Vec<u8> {
142 let zero = Address::new_from_array([0; 32]);
143 Instruction::from(CreateOrder {
144 program_id: zero,
145 owner: zero,
146 created_by: zero,
147 order_pda: zero,
148 intent_bytes: *intent_bytes,
149 })
150 .data
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::fixtures::{default_order_data, valid_intent_bytes, NUM_ACCOUNTS};
157 use super::*;
158 use crate::instruction::fixtures::{
159 fake_account, fake_account_from_array, fake_sequential_accounts,
160 };
161 use crate::instruction::tests::{
162 assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
163 assert_writable_signer,
164 };
165 use solana_account_view::AccountView;
166 use solana_address::Address;
167
168 #[test]
169 fn create_order_input_parses_valid_input() {
170 let program_id = Address::new_from_array([21; 32]);
171 let owner = Address::new_from_array([22; 32]);
172 let created_by = Address::new_from_array([24; 32]);
173 let order_pda = Address::new_from_array([23; 32]);
174 let intent_bytes = valid_intent_bytes();
175
176 let data = Instruction::from(CreateOrder {
177 program_id,
178 owner,
179 created_by,
180 order_pda,
181 intent_bytes,
182 })
183 .data;
184 let accounts = [
185 fake_account(owner),
186 fake_account(created_by),
187 fake_account(order_pda),
188 fake_account_from_array([4; 32]),
189 ];
190
191 let CreateOrderInput {
192 intent_bytes: derived_intent_bytes,
193 owner: derived_owner,
194 created_by: derived_created_by,
195 order_pda: derived_order_pda,
196 } = CreateOrderInput::parse(&data, &accounts).expect("parse should succeed");
197
198 assert_eq!(derived_intent_bytes, intent_bytes);
199 assert_eq!(*derived_order_pda.address(), order_pda);
200 assert_eq!(*derived_owner.address(), owner);
201 assert_eq!(*derived_created_by.address(), created_by);
202 }
203
204 #[test]
205 fn create_order_input_rejects_short_data() {
206 let intent_bytes = valid_intent_bytes();
207 let mut data = default_order_data(&intent_bytes);
208 data.pop();
209 let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
210 assert_eq!(
211 CreateOrderInput::parse(&data, &accounts).err(),
212 Some(ProgramError::InvalidInstructionData),
213 );
214 }
215
216 #[test]
217 fn create_order_input_rejects_long_data() {
218 let intent_bytes = valid_intent_bytes();
219 let mut data = default_order_data(&intent_bytes);
220 data.push(0); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
222 assert_eq!(
223 CreateOrderInput::parse(&data, &accounts).err(),
224 Some(ProgramError::InvalidInstructionData),
225 );
226 }
227
228 #[test]
229 fn create_order_input_rejects_missing_accounts() {
230 let intent_bytes = valid_intent_bytes();
231 let data = default_order_data(&intent_bytes);
232 let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
233 accounts.pop();
234 assert_eq!(
235 CreateOrderInput::parse(&data, &accounts).err(),
236 Some(ProgramError::NotEnoughAccountKeys),
237 );
238 }
239
240 #[test]
241 fn instruction_data_has_expected_layout() {
242 let program_id = Pubkey::new_from_array([1; 32]);
243 let owner = Pubkey::new_from_array([2; 32]);
244 let created_by = Pubkey::new_from_array([4; 32]);
245 let order_pda = Pubkey::new_from_array([3; 32]);
246 let intent_bytes = [0x42u8; EncodedOrderIntent::SIZE];
247
248 let Instruction { data, .. } = CreateOrder {
249 program_id,
250 owner,
251 created_by,
252 order_pda,
253 intent_bytes,
254 }
255 .into();
256
257 assert_eq!(data.len(), 1 + EncodedOrderIntent::SIZE);
258 assert_eq!(data[0], SettlementInstruction::CreateOrder.discriminator());
259 assert_eq!(&data[1..], &intent_bytes);
260 }
261
262 #[test]
263 fn instruction_data_has_expected_accounts() {
264 let program_id = Pubkey::new_from_array([1; 32]);
265 let owner = Pubkey::new_from_array([2; 32]);
266 let created_by = Pubkey::new_from_array([4; 32]);
267 let order_pda = Pubkey::new_from_array([3; 32]);
268 let intent_bytes = [0u8; EncodedOrderIntent::SIZE];
269
270 let Instruction { accounts, .. } = CreateOrder {
271 program_id,
272 owner,
273 created_by,
274 order_pda,
275 intent_bytes,
276 }
277 .into();
278
279 assert_eq!(accounts.len(), 4);
280 assert_readonly_signer(&accounts[0], owner);
284 assert_writable_signer(&accounts[1], created_by);
285 assert_writable_nonsigner(&accounts[2], order_pda);
286 assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
287 }
288}