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 use spl_token_interface::ID as SPL_TOKEN_PROGRAM_ID;
20
21pub struct CreateBuffers<'a> {
41 pub program_id: Pubkey,
42 pub payer: Pubkey,
43 pub buffers: &'a [(Pubkey, Pubkey)],
44}
45
46impl From<CreateBuffers<'_>> for Instruction {
47 fn from(builder: CreateBuffers<'_>) -> Self {
48 let mut accounts = vec![
49 AccountMeta::new(builder.payer, true),
50 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
51 AccountMeta::new_readonly(SPL_TOKEN_PROGRAM_ID, false),
52 ];
53 for (buffer_pda, mint) in builder.buffers {
54 accounts.push(AccountMeta::new(*buffer_pda, false));
55 accounts.push(AccountMeta::new_readonly(*mint, false));
56 }
57 Instruction {
58 program_id: builder.program_id,
59 accounts,
60 data: vec![SettlementInstruction::CreateBuffer.discriminator()],
61 }
62 }
63}
64
65pub struct CreateBufferInput<'a, A> {
67 pub payer: &'a A,
68 pub token_program: &'a A,
69 pub buffers: &'a [[A; 2]],
71}
72
73impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {
74 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateBuffer;
75
76 fn parse_body(instruction_data: &[u8], accounts: &'a mut [A]) -> Result<Self, ProgramError> {
77 if !instruction_data.is_empty() {
78 return Err(ProgramError::InvalidInstructionData);
79 }
80 let [payer, _system, token_program, rest @ ..] = accounts else {
86 return Err(ProgramError::NotEnoughAccountKeys);
87 };
88 let rest: &'a [A] = rest;
93 let (buffers, remainder) = rest.as_chunks::<2>();
94 if !remainder.is_empty() || buffers.is_empty() {
95 return Err(ProgramError::NotEnoughAccountKeys);
96 }
97
98 Ok(Self {
99 payer,
100 token_program,
101 buffers,
102 })
103 }
104}
105
106#[cfg(any(test, feature = "test-fixtures"))]
109pub mod fixtures {
110 use solana_address::Address;
111
112 use super::{CreateBuffers, Instruction};
113
114 pub const NUM_SHARED_ACCOUNTS: usize = 3;
117
118 pub fn create_buffer_data() -> Vec<u8> {
121 let zero = Address::new_from_array([0; 32]);
122 Instruction::from(CreateBuffers {
123 program_id: zero,
124 payer: zero,
125 buffers: &[(zero, zero)],
126 })
127 .data
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::fixtures::{create_buffer_data, NUM_SHARED_ACCOUNTS};
134 use super::*;
135 use crate::instruction::fixtures::{
136 fake_account, fake_account_from_array, fake_sequential_accounts,
137 };
138 use crate::instruction::tests::{
139 assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
140 };
141 use solana_account_view::AccountView;
142 use solana_address::Address;
143
144 #[test]
145 fn create_buffer_input_parses_valid_input() {
146 let program_id: Address = Address::new_from_array([1; 32]);
147 let payer: Address = Address::new_from_array([2; 32]);
148 let system_program = fake_account_from_array([4; 32]);
149 let token_program = Address::new_from_array([3; 32]);
150 let buffer_pda = Address::new_from_array([5; 32]);
151 let mint = Address::new_from_array([6; 32]);
152
153 let data = Instruction::from(CreateBuffers {
154 program_id,
155 payer,
156 buffers: &[(buffer_pda, mint)],
157 })
158 .data;
159 let mut accounts = [
160 fake_account(payer),
161 system_program,
162 fake_account(token_program),
163 fake_account(buffer_pda),
164 fake_account(mint),
165 ];
166
167 let CreateBufferInput {
168 payer: parsed_payer,
169 token_program: parsed_token_program,
170 buffers,
171 } = CreateBufferInput::parse(&data, &mut accounts).expect("parse should succeed");
172
173 assert_eq!(*parsed_payer.address(), payer);
174 assert_eq!(*parsed_token_program.address(), token_program);
175 assert_eq!(buffers.len(), 1, "one buffer is one (pda, mint) pair");
176 assert_eq!(*buffers[0][0].address(), buffer_pda);
177 assert_eq!(*buffers[0][1].address(), mint);
178 }
179
180 #[test]
181 fn create_buffer_input_parses_multiple_buffers() {
182 let program_id = Address::new_from_array([1; 32]);
183 let payer = Address::new_from_array([2; 32]);
184 let token_program = Address::new_from_array([3; 32]);
185 let buffer_a = Address::new_from_array([5; 32]);
186 let mint_a = Address::new_from_array([6; 32]);
187 let buffer_b = Address::new_from_array([7; 32]);
188 let mint_b = Address::new_from_array([8; 32]);
189
190 let data = Instruction::from(CreateBuffers {
191 program_id,
192 payer,
193 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
194 })
195 .data;
196 let mut accounts = [
197 fake_account(payer),
198 fake_account_from_array([4; 32]),
199 fake_account(token_program),
200 fake_account(buffer_a),
201 fake_account(mint_a),
202 fake_account(buffer_b),
203 fake_account(mint_b),
204 ];
205
206 let CreateBufferInput { buffers, .. } =
207 CreateBufferInput::parse(&data, &mut accounts).expect("parse should succeed");
208
209 assert_eq!(
210 buffers[0].each_ref().map(|a| *a.address()),
211 [buffer_a, mint_a]
212 );
213 assert_eq!(
214 buffers[1].each_ref().map(|a| *a.address()),
215 [buffer_b, mint_b]
216 );
217 }
218
219 #[test]
220 fn create_buffer_input_rejects_zero_buffers() {
221 let data = vec![SettlementInstruction::CreateBuffer.discriminator()];
222 let mut accounts = fake_sequential_accounts::<NUM_SHARED_ACCOUNTS>();
224 assert_eq!(
225 CreateBufferInput::parse(&data, &mut accounts).err(),
226 Some(ProgramError::NotEnoughAccountKeys),
227 "an instruction that creates no buffers is rejected",
228 );
229 }
230
231 #[test]
232 fn create_buffer_input_rejects_long_data() {
233 let mut data = create_buffer_data();
234 data.push(0); let mut accounts: [AccountView; 0] = [];
236 assert_eq!(
237 CreateBufferInput::parse(&data, &mut accounts).err(),
238 Some(ProgramError::InvalidInstructionData),
239 );
240 }
241
242 #[test]
243 fn create_buffer_input_rejects_missing_accounts() {
244 let data = create_buffer_data();
245 let mut accounts = fake_sequential_accounts::<{ NUM_SHARED_ACCOUNTS - 1 }>();
247 assert_eq!(
248 CreateBufferInput::parse(&data, &mut accounts).err(),
249 Some(ProgramError::NotEnoughAccountKeys),
250 );
251 }
252
253 #[test]
254 fn create_buffer_input_rejects_odd_pair_accounts() {
255 let data = create_buffer_data();
256 let mut accounts = fake_sequential_accounts::<4>();
258 assert_eq!(
259 CreateBufferInput::parse(&data, &mut accounts).err(),
260 Some(ProgramError::NotEnoughAccountKeys),
261 );
262 }
263
264 #[test]
265 fn instruction_data_has_expected_layout() {
266 let program_id = Pubkey::new_from_array([1; 32]);
267 let payer = Pubkey::new_from_array([2; 32]);
268 let buffer_pda = Pubkey::new_from_array([3; 32]);
269 let mint = Pubkey::new_from_array([4; 32]);
270 let Instruction { data, .. } = CreateBuffers {
271 program_id,
272 payer,
273 buffers: &[(buffer_pda, mint)],
274 }
275 .into();
276 assert_eq!(
277 data,
278 vec![SettlementInstruction::CreateBuffer.discriminator()]
279 );
280 }
281
282 #[test]
283 fn single_buffer_has_expected_accounts() {
284 let program_id = Pubkey::new_from_array([1; 32]);
285 let payer = Pubkey::new_from_array([2; 32]);
286 let buffer_pda = Pubkey::new_from_array([3; 32]);
287 let mint = Pubkey::new_from_array([4; 32]);
288 let Instruction { accounts, .. } = CreateBuffers {
289 program_id,
290 payer,
291 buffers: &[(buffer_pda, mint)],
292 }
293 .into();
294
295 assert_eq!(accounts.len(), 5);
296 assert_writable_signer(&accounts[0], payer);
300 assert_readonly_nonsigner(&accounts[1], SYSTEM_PROGRAM_ID);
301 assert_readonly_nonsigner(&accounts[2], SPL_TOKEN_PROGRAM_ID);
302 assert_writable_nonsigner(&accounts[3], buffer_pda);
303 assert_readonly_nonsigner(&accounts[4], mint);
304 }
305
306 #[test]
307 fn multiple_buffers_append_pairs_after_shared_accounts() {
308 let program_id = Pubkey::new_from_array([1; 32]);
309 let payer = Pubkey::new_from_array([2; 32]);
310 let buffer_a = Pubkey::new_from_array([3; 32]);
311 let mint_a = Pubkey::new_from_array([4; 32]);
312 let buffer_b = Pubkey::new_from_array([5; 32]);
313 let mint_b = Pubkey::new_from_array([6; 32]);
314 let Instruction { accounts, .. } = CreateBuffers {
315 program_id,
316 payer,
317 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
318 }
319 .into();
320
321 assert_eq!(accounts.len(), 3 + 2 * 2);
323 assert_writable_nonsigner(&accounts[3], buffer_a);
324 assert_readonly_nonsigner(&accounts[4], mint_a);
325 assert_writable_nonsigner(&accounts[5], buffer_b);
326 assert_readonly_nonsigner(&accounts[6], mint_b);
327 }
328
329 #[test]
330 fn empty_buffers_has_only_shared_accounts() {
331 let program_id = Pubkey::new_from_array([1; 32]);
332 let payer = Pubkey::new_from_array([2; 32]);
333 let Instruction { accounts, .. } = CreateBuffers {
334 program_id,
335 payer,
336 buffers: &[],
337 }
338 .into();
339 assert_eq!(accounts.len(), 3);
340 }
341}