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 BufferAccounts<'a, A> {
67 pub buffer_pda: &'a A,
68 pub mint: &'a A,
69}
70
71pub struct CreateBufferInput<'a, A> {
73 pub payer: &'a A,
74 pub token_program: &'a A,
75 buffer_pairs: &'a [[A; 2]],
76}
77
78impl<'a, A> CreateBufferInput<'a, A> {
79 pub fn buffers(&self) -> impl Iterator<Item = BufferAccounts<'a, A>> + '_ {
81 self.buffer_pairs
82 .iter()
83 .map(|[buffer_pda, mint]| BufferAccounts { buffer_pda, mint })
84 }
85}
86
87impl<'a, A> InstructionInputParsing<'a, A> for CreateBufferInput<'a, A> {
88 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::CreateBuffer;
89
90 fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
91 if !instruction_data.is_empty() {
92 return Err(ProgramError::InvalidInstructionData);
93 }
94 let [payer, _system, token_program, rest @ ..] = accounts else {
100 return Err(ProgramError::NotEnoughAccountKeys);
101 };
102 let rest: &'a [A] = rest;
107 let (buffers, remainder) = rest.as_chunks::<2>();
108 if !remainder.is_empty() || buffers.is_empty() {
109 return Err(ProgramError::NotEnoughAccountKeys);
110 }
111
112 Ok(Self {
113 payer,
114 token_program,
115 buffer_pairs: buffers,
116 })
117 }
118}
119
120#[cfg(any(test, feature = "test-fixtures"))]
123pub mod fixtures {
124 use solana_address::Address;
125
126 use super::{CreateBuffers, Instruction};
127
128 pub const NUM_SHARED_ACCOUNTS: usize = 3;
131
132 pub fn create_buffer_data() -> Vec<u8> {
135 let zero = Address::new_from_array([0; 32]);
136 Instruction::from(CreateBuffers {
137 program_id: zero,
138 payer: zero,
139 buffers: &[(zero, zero)],
140 })
141 .data
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::fixtures::{create_buffer_data, NUM_SHARED_ACCOUNTS};
148 use super::*;
149 use crate::instruction::fixtures::{
150 fake_account, fake_account_from_array, fake_sequential_accounts,
151 };
152 use crate::instruction::tests::{
153 assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
154 };
155 use solana_account_view::AccountView;
156 use solana_address::Address;
157
158 #[test]
159 fn create_buffer_input_parses_valid_input() {
160 let program_id: Address = Address::new_from_array([1; 32]);
161 let payer: Address = Address::new_from_array([2; 32]);
162 let system_program = fake_account_from_array([4; 32]);
163 let token_program = Address::new_from_array([3; 32]);
164 let buffer_pda = Address::new_from_array([5; 32]);
165 let mint = Address::new_from_array([6; 32]);
166
167 let data = Instruction::from(CreateBuffers {
168 program_id,
169 payer,
170 buffers: &[(buffer_pda, mint)],
171 })
172 .data;
173 let accounts = [
174 fake_account(payer),
175 system_program,
176 fake_account(token_program),
177 fake_account(buffer_pda),
178 fake_account(mint),
179 ];
180
181 let input = CreateBufferInput::parse(&data, &accounts).expect("parse should succeed");
182
183 assert_eq!(*input.payer.address(), payer);
184 assert_eq!(*input.token_program.address(), token_program);
185 let buffers: Vec<_> = input.buffers().collect();
186 assert_eq!(buffers.len(), 1, "one buffer is one (pda, mint) pair");
187 assert_eq!(*buffers[0].buffer_pda.address(), buffer_pda);
188 assert_eq!(*buffers[0].mint.address(), mint);
189 }
190
191 #[test]
192 fn create_buffer_input_parses_multiple_buffers() {
193 let program_id = Address::new_from_array([1; 32]);
194 let payer = Address::new_from_array([2; 32]);
195 let token_program = Address::new_from_array([3; 32]);
196 let buffer_a = Address::new_from_array([5; 32]);
197 let mint_a = Address::new_from_array([6; 32]);
198 let buffer_b = Address::new_from_array([7; 32]);
199 let mint_b = Address::new_from_array([8; 32]);
200
201 let data = Instruction::from(CreateBuffers {
202 program_id,
203 payer,
204 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
205 })
206 .data;
207 let accounts = [
208 fake_account(payer),
209 fake_account_from_array([4; 32]),
210 fake_account(token_program),
211 fake_account(buffer_a),
212 fake_account(mint_a),
213 fake_account(buffer_b),
214 fake_account(mint_b),
215 ];
216
217 let input = CreateBufferInput::parse(&data, &accounts).expect("parse should succeed");
218
219 let buffers: Vec<_> = input.buffers().collect();
220 assert_eq!(
221 [*buffers[0].buffer_pda.address(), *buffers[0].mint.address()],
222 [buffer_a, mint_a]
223 );
224 assert_eq!(
225 [*buffers[1].buffer_pda.address(), *buffers[1].mint.address()],
226 [buffer_b, mint_b]
227 );
228 }
229
230 #[test]
231 fn create_buffer_input_rejects_zero_buffers() {
232 let data = vec![SettlementInstruction::CreateBuffer.discriminator()];
233 let accounts = fake_sequential_accounts::<NUM_SHARED_ACCOUNTS>();
235 assert_eq!(
236 CreateBufferInput::parse(&data, &accounts).err(),
237 Some(ProgramError::NotEnoughAccountKeys),
238 "an instruction that creates no buffers is rejected",
239 );
240 }
241
242 #[test]
243 fn create_buffer_input_rejects_long_data() {
244 let mut data = create_buffer_data();
245 data.push(0); let accounts: [AccountView; 0] = [];
247 assert_eq!(
248 CreateBufferInput::parse(&data, &accounts).err(),
249 Some(ProgramError::InvalidInstructionData),
250 );
251 }
252
253 #[test]
254 fn create_buffer_input_rejects_missing_accounts() {
255 let data = create_buffer_data();
256 let accounts = fake_sequential_accounts::<{ NUM_SHARED_ACCOUNTS - 1 }>();
258 assert_eq!(
259 CreateBufferInput::parse(&data, &accounts).err(),
260 Some(ProgramError::NotEnoughAccountKeys),
261 );
262 }
263
264 #[test]
265 fn create_buffer_input_rejects_odd_pair_accounts() {
266 let data = create_buffer_data();
267 let accounts = fake_sequential_accounts::<4>();
269 assert_eq!(
270 CreateBufferInput::parse(&data, &accounts).err(),
271 Some(ProgramError::NotEnoughAccountKeys),
272 );
273 }
274
275 #[test]
276 fn instruction_data_has_expected_layout() {
277 let program_id = Pubkey::new_from_array([1; 32]);
278 let payer = Pubkey::new_from_array([2; 32]);
279 let buffer_pda = Pubkey::new_from_array([3; 32]);
280 let mint = Pubkey::new_from_array([4; 32]);
281 let Instruction { data, .. } = CreateBuffers {
282 program_id,
283 payer,
284 buffers: &[(buffer_pda, mint)],
285 }
286 .into();
287 assert_eq!(
288 data,
289 vec![SettlementInstruction::CreateBuffer.discriminator()]
290 );
291 }
292
293 #[test]
294 fn single_buffer_has_expected_accounts() {
295 let program_id = Pubkey::new_from_array([1; 32]);
296 let payer = Pubkey::new_from_array([2; 32]);
297 let buffer_pda = Pubkey::new_from_array([3; 32]);
298 let mint = Pubkey::new_from_array([4; 32]);
299 let Instruction { accounts, .. } = CreateBuffers {
300 program_id,
301 payer,
302 buffers: &[(buffer_pda, mint)],
303 }
304 .into();
305
306 assert_eq!(accounts.len(), 5);
307 assert_writable_signer(&accounts[0], payer);
311 assert_readonly_nonsigner(&accounts[1], SYSTEM_PROGRAM_ID);
312 assert_readonly_nonsigner(&accounts[2], SPL_TOKEN_PROGRAM_ID);
313 assert_writable_nonsigner(&accounts[3], buffer_pda);
314 assert_readonly_nonsigner(&accounts[4], mint);
315 }
316
317 #[test]
318 fn multiple_buffers_append_pairs_after_shared_accounts() {
319 let program_id = Pubkey::new_from_array([1; 32]);
320 let payer = Pubkey::new_from_array([2; 32]);
321 let buffer_a = Pubkey::new_from_array([3; 32]);
322 let mint_a = Pubkey::new_from_array([4; 32]);
323 let buffer_b = Pubkey::new_from_array([5; 32]);
324 let mint_b = Pubkey::new_from_array([6; 32]);
325 let Instruction { accounts, .. } = CreateBuffers {
326 program_id,
327 payer,
328 buffers: &[(buffer_a, mint_a), (buffer_b, mint_b)],
329 }
330 .into();
331
332 assert_eq!(accounts.len(), 3 + 2 * 2);
334 assert_writable_nonsigner(&accounts[3], buffer_a);
335 assert_readonly_nonsigner(&accounts[4], mint_a);
336 assert_writable_nonsigner(&accounts[5], buffer_b);
337 assert_readonly_nonsigner(&accounts[6], mint_b);
338 }
339
340 #[test]
341 fn empty_buffers_has_only_shared_accounts() {
342 let program_id = Pubkey::new_from_array([1; 32]);
343 let payer = Pubkey::new_from_array([2; 32]);
344 let Instruction { accounts, .. } = CreateBuffers {
345 program_id,
346 payer,
347 buffers: &[],
348 }
349 .into();
350 assert_eq!(accounts.len(), 3);
351 }
352}