cow_settlement_interface/instruction/
create_buffer.rs1use solana_instruction::{AccountMeta, Instruction};
9use solana_program_error::ProgramError;
10use solana_pubkey::Pubkey;
11
12pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
13
14use super::InstructionInputParsing;
15use crate::SettlementInstruction;
16
17pub struct CreateBuffers<'a> {
42 pub program_id: Pubkey,
43 pub payer: Pubkey,
44 pub token_program: Pubkey,
48 pub buffers: &'a [(Pubkey, Pubkey)],
49}
50
51impl From<CreateBuffers<'_>> for Instruction {
52 fn from(builder: CreateBuffers<'_>) -> Self {
53 let mut accounts = vec![
54 AccountMeta::new(builder.payer, true),
55 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
56 AccountMeta::new_readonly(builder.token_program, false),
57 ];
58 for (buffer_pda, mint) in builder.buffers {
59 accounts.push(AccountMeta::new(*buffer_pda, false));
60 accounts.push(AccountMeta::new_readonly(*mint, false));
61 }
62 Instruction {
63 program_id: builder.program_id,
64 accounts,
65 data: vec![SettlementInstruction::CreateBuffer.discriminator()],
66 }
67 }
68}
69
70pub struct BufferAccounts<'a, A> {
72 pub buffer_pda: &'a A,
73 pub mint: &'a A,
74}
75
76pub struct CreateBufferInput<'a, A> {
78 pub payer: &'a A,
79 buffer_pairs: &'a [[A; 2]],
80}
81
82impl<'a, A> CreateBufferInput<'a, A> {
83 pub fn buffers(&self) -> impl Iterator<Item = BufferAccounts<'a, A>> + '_ {
85 self.buffer_pairs
86 .iter()
87 .map(|[buffer_pda, mint]| BufferAccounts { buffer_pda, mint })
88 }
89}
90
91impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {
92 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateBuffer;
93
94 fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
95 if !instruction_data.is_empty() {
96 return Err(ProgramError::InvalidInstructionData);
97 }
98 let [payer, _system, _token_program, rest @ ..] = accounts else {
105 return Err(ProgramError::NotEnoughAccountKeys);
106 };
107 let rest: &'a [A] = rest;
112 let (buffers, remainder) = rest.as_chunks::<2>();
113 if !remainder.is_empty() || buffers.is_empty() {
114 return Err(ProgramError::NotEnoughAccountKeys);
115 }
116
117 Ok(Self {
118 payer,
119 buffer_pairs: buffers,
120 })
121 }
122}
123
124#[cfg(any(test, feature = "test-fixtures"))]
127pub mod fixtures {
128 use solana_address::Address;
129
130 use super::{CreateBuffers, Instruction};
131
132 pub const NUM_SHARED_ACCOUNTS: usize = 3;
135
136 pub fn create_buffer_data() -> Vec<u8> {
139 let zero = Address::new_from_array([0; 32]);
140 Instruction::from(CreateBuffers {
141 program_id: zero,
142 payer: zero,
143 token_program: zero,
144 buffers: &[(zero, zero)],
145 })
146 .data
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::fixtures::{create_buffer_data, NUM_SHARED_ACCOUNTS};
153 use super::*;
154 use crate::fixtures::pubkey_from_seed;
155 use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
156 use crate::instruction::tests::{
157 assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
158 };
159 use solana_account_view::AccountView;
160
161 #[test]
162 fn create_buffer_input_parses_valid_input() {
163 let program_id = pubkey_from_seed("program id");
164 let payer = pubkey_from_seed("payer");
165 let token_program = pubkey_from_seed("token program");
166 let system_program = fake_account(pubkey_from_seed("system program"));
167 let buffer_pda = pubkey_from_seed("buffer pda");
168 let mint = pubkey_from_seed("mint");
169
170 let data = Instruction::from(CreateBuffers {
171 program_id,
172 payer,
173 token_program,
174 buffers: &[(buffer_pda, mint)],
175 })
176 .data;
177 let accounts = [
178 fake_account(payer),
179 system_program,
180 fake_account(token_program),
181 fake_account(buffer_pda),
182 fake_account(mint),
183 ];
184
185 let input = CreateBufferInput::parse(&data, &accounts).expect("parse should succeed");
186
187 assert_eq!(*input.payer.address(), payer);
188 let buffers: Vec<_> = input.buffers().collect();
189 assert_eq!(buffers.len(), 1, "one buffer is one (pda, mint) pair");
190 assert_eq!(*buffers[0].buffer_pda.address(), buffer_pda);
191 assert_eq!(*buffers[0].mint.address(), mint);
192 }
193
194 #[test]
195 fn create_buffer_input_parses_multiple_buffers() {
196 let program_id = pubkey_from_seed("program id");
197 let payer = pubkey_from_seed("payer");
198 let token_program = pubkey_from_seed("token program");
199 let buffer_a = pubkey_from_seed("buffer a");
200 let mint_a = pubkey_from_seed("mint a");
201 let buffer_b = pubkey_from_seed("buffer b");
202 let mint_b = pubkey_from_seed("mint b");
203
204 let data = Instruction::from(CreateBuffers {
205 program_id,
206 payer,
207 token_program,
208 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
209 })
210 .data;
211 let accounts = [
212 fake_account(payer),
213 fake_account(pubkey_from_seed("system program")),
214 fake_account(token_program),
215 fake_account(buffer_a),
216 fake_account(mint_a),
217 fake_account(buffer_b),
218 fake_account(mint_b),
219 ];
220
221 let input = CreateBufferInput::parse(&data, &accounts).expect("parse should succeed");
222
223 let buffers: Vec<_> = input.buffers().collect();
224 assert_eq!(
225 [*buffers[0].buffer_pda.address(), *buffers[0].mint.address()],
226 [buffer_a, mint_a]
227 );
228 assert_eq!(
229 [*buffers[1].buffer_pda.address(), *buffers[1].mint.address()],
230 [buffer_b, mint_b]
231 );
232 }
233
234 #[test]
235 fn create_buffer_input_rejects_zero_buffers() {
236 let data = vec![SettlementInstruction::CreateBuffer.discriminator()];
237 let accounts = fake_sequential_accounts::<NUM_SHARED_ACCOUNTS>();
239 assert_eq!(
240 CreateBufferInput::parse(&data, &accounts).err(),
241 Some(ProgramError::NotEnoughAccountKeys),
242 "an instruction that creates no buffers is rejected",
243 );
244 }
245
246 #[test]
247 fn create_buffer_input_rejects_long_data() {
248 let mut data = create_buffer_data();
249 data.push(0); let accounts: [AccountView; 0] = [];
251 assert_eq!(
252 CreateBufferInput::parse(&data, &accounts).err(),
253 Some(ProgramError::InvalidInstructionData),
254 );
255 }
256
257 #[test]
258 fn create_buffer_input_rejects_missing_accounts() {
259 let data = create_buffer_data();
260 let accounts = fake_sequential_accounts::<{ NUM_SHARED_ACCOUNTS - 1 }>();
262 assert_eq!(
263 CreateBufferInput::parse(&data, &accounts).err(),
264 Some(ProgramError::NotEnoughAccountKeys),
265 );
266 }
267
268 #[test]
269 fn create_buffer_input_rejects_odd_pair_accounts() {
270 let data = create_buffer_data();
271 let accounts = fake_sequential_accounts::<4>();
273 assert_eq!(
274 CreateBufferInput::parse(&data, &accounts).err(),
275 Some(ProgramError::NotEnoughAccountKeys),
276 );
277 }
278
279 #[test]
280 fn instruction_data_has_expected_layout() {
281 let program_id = pubkey_from_seed("program id");
282 let payer = pubkey_from_seed("payer");
283 let token_program = pubkey_from_seed("token program");
284 let buffer_pda = pubkey_from_seed("buffer pda");
285 let mint = pubkey_from_seed("mint");
286 let Instruction { data, .. } = CreateBuffers {
287 program_id,
288 payer,
289 token_program,
290 buffers: &[(buffer_pda, mint)],
291 }
292 .into();
293 assert_eq!(
294 data,
295 vec![SettlementInstruction::CreateBuffer.discriminator()]
296 );
297 }
298
299 #[test]
300 fn single_buffer_has_expected_accounts() {
301 let program_id = pubkey_from_seed("program id");
302 let payer = pubkey_from_seed("payer");
303 let token_program = pubkey_from_seed("token program");
304 let buffer_pda = pubkey_from_seed("buffer pda");
305 let mint = pubkey_from_seed("mint");
306 let Instruction { accounts, .. } = CreateBuffers {
307 program_id,
308 payer,
309 token_program,
310 buffers: &[(buffer_pda, mint)],
311 }
312 .into();
313
314 assert_eq!(accounts.len(), 5);
315 assert_writable_signer(&accounts[0], payer);
319 assert_readonly_nonsigner(&accounts[1], SYSTEM_PROGRAM_ID);
320 assert_readonly_nonsigner(&accounts[2], token_program);
321 assert_writable_nonsigner(&accounts[3], buffer_pda);
322 assert_readonly_nonsigner(&accounts[4], mint);
323 }
324
325 #[test]
326 fn multiple_buffers_append_pairs_after_shared_accounts() {
327 let program_id = pubkey_from_seed("program id");
328 let payer = pubkey_from_seed("payer");
329 let token_program = pubkey_from_seed("token program");
330 let buffer_a = pubkey_from_seed("buffer a");
331 let mint_a = pubkey_from_seed("mint a");
332 let buffer_b = pubkey_from_seed("buffer b");
333 let mint_b = pubkey_from_seed("mint b");
334 let Instruction { accounts, .. } = CreateBuffers {
335 program_id,
336 payer,
337 token_program,
338 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
339 }
340 .into();
341
342 assert_eq!(accounts.len(), 3 + 2 * 2);
344 assert_writable_nonsigner(&accounts[3], buffer_a);
345 assert_readonly_nonsigner(&accounts[4], mint_a);
346 assert_writable_nonsigner(&accounts[5], buffer_b);
347 assert_readonly_nonsigner(&accounts[6], mint_b);
348 }
349
350 #[test]
351 fn empty_buffers_has_only_shared_accounts() {
352 let program_id = pubkey_from_seed("program id");
353 let payer = pubkey_from_seed("payer");
354 let token_program = pubkey_from_seed("token program");
355 let Instruction { accounts, .. } = CreateBuffers {
356 program_id,
357 payer,
358 token_program,
359 buffers: &[],
360 }
361 .into();
362 assert_eq!(accounts.len(), 3);
363 }
364}