Skip to main content

cow_settlement_interface/instruction/
create_buffer.rs

1//! `CreateBuffer` instruction builder.
2//!
3//! Allocates one or more per-token buffer PDAs (see [`crate::pda::buffer`]) as
4//! SPL token accounts and initializes each with the settlement state PDA as its
5//! token authority. Each token is identified by its `mint` account; the buffer
6//! address must be the canonical PDA for that mint.
7
8use 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
17/// The SPL Token program. Buffers are created as token accounts owned by this
18/// program.
19pub use spl_token_interface::ID as SPL_TOKEN_PROGRAM_ID;
20
21/// Builder for a `CreateBuffer` instruction that creates one buffer per
22/// `(buffer_pda, mint)` pair in `buffers`.
23///
24/// Each `buffer_pda` must be the canonical PDA returned by
25/// [`crate::pda::buffer::find_buffer_pda`] for its `mint`; the program derives
26/// the bump itself and rejects any other address. `payer` funds every new token
27/// account's rent.
28///
29/// Buffer creation is idempotent: an already-existing `buffer_pda` is left
30/// unchanged and the instruction still succeeds, so two parties racing to
31/// create the same buffer both succeed.
32///
33/// Wire format: `[discriminator=4]`, 1 byte. The tokens are implied by the
34/// `mint` accounts, so no further data is needed.
35/// Required accounts:
36/// `[payer (W,S), system_program (R), token_program (R), (buffer_pda (W), mint (R))...]`.
37/// The three shared accounts come first and are read positionally; the
38/// per-buffer pairs follow. The system program only has to be present so the
39/// `CreateAccount` CPI can dispatch; it isn't read by index.
40pub 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
65/// Parsed inputs of a `CreateBuffer` instruction.
66pub struct CreateBufferInput<'a, A> {
67    pub payer: &'a A,
68    pub token_program: &'a A,
69    /// One `[buffer_pda, mint]` pair per buffer to create.
70    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        // Accounts: [payer (W,S), system_program (R), token_program (R),
81        // (buffer_pda (W), mint (R))...]. The three shared accounts come first;
82        // the per-buffer pairs follow, one pair per buffer. The system program
83        // needs to be present for the `CreateAccount` CPI but isn't dereferenced
84        // here.
85        let [payer, _system, token_program, rest @ ..] = accounts else {
86            return Err(ProgramError::NotEnoughAccountKeys);
87        };
88        // Group the trailing accounts into `[buffer_pda, mint]` pairs. Each
89        // buffer needs both, so a stray odd account left over is a malformed
90        // instruction. There must be at least one pair: an instruction that
91        // creates no buffers is rejected as a likely encoding issue.
92        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/// Test scaffolding for `CreateBuffer` parsing and handling, shared by this
107/// crate's tests and the settlement program's via the `test-fixtures` feature.
108#[cfg(any(test, feature = "test-fixtures"))]
109pub mod fixtures {
110    use solana_address::Address;
111
112    use super::{CreateBuffers, Instruction};
113
114    /// Number of accounts that don't depend on the number of buffers created:
115    /// payer, system program, and token program.
116    pub const NUM_SHARED_ACCOUNTS: usize = 3;
117
118    /// `CreateBuffer` instruction data with placeholder addresses, for failure
119    /// cases where the input is irrelevant.
120    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        // Only the three shared accounts, no (pda, mint) pairs.
223        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); // trailing byte
235        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        // Fewer than the three shared accounts.
246        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        // Three shared accounts plus a dangling account that can't form a pair.
257        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        // payer funds rent; the system and token programs and the mint are only
297        // referenced; buffer_pda is created (written) and the mint is
298        // untouched.
299        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        // Three shared accounts followed by two (buffer, mint) pairs.
322        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}