Skip to main content

cow_settlement_interface/instruction/
initialize.rs

1//! `Initialize` instruction builder.
2//!
3//! Allocates the singleton settlement state PDA (see [`crate::pda::state`]).
4
5use core::mem::size_of;
6
7use arrayref::array_refs;
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/// Builder for an `Initialize` instruction.
18///
19/// `payer` funds the new account's rent and signs. It is meant to be the
20/// transaction's fee payer: the state is created once at deployment and never
21/// deallocated, so there's no need for a dedicated funding account separate
22/// from whoever pays for the deployment transaction.
23///
24/// `state_pda` must be the canonical PDA returned by
25/// [`crate::pda::state::find_state_pda`]; the program derives the bump itself
26/// and rejects any other address.
27///
28/// `manager` and `reclaim_authority` are recorded verbatim in the state PDA's
29/// data: the account authorized to add and remove solvers, and the account
30/// authorized to reclaim rent for buffers. See
31/// [`crate::data::state::StateAccount`].
32///
33/// The state account is owned by the settlement program. This instruction
34/// succeeds only once: a second call fails because the account already
35/// exists.
36///
37/// Wire format: `[discriminator=3, manager (32 bytes), reclaim_authority (32
38/// bytes)]`, 65 bytes. Required accounts: `[payer (W,S), state_pda (W),
39/// system_program (R)]`. The system program must be available for the
40/// `CreateAccount` CPI but doesn't need to sit at that specific position.
41pub struct Initialize {
42    pub program_id: Pubkey,
43    pub payer: Pubkey,
44    pub state_pda: Pubkey,
45    pub manager: Pubkey,
46    pub reclaim_authority: Pubkey,
47}
48
49impl From<Initialize> for Instruction {
50    fn from(builder: Initialize) -> Self {
51        let mut data = vec![SettlementInstruction::Initialize.discriminator()];
52        data.extend_from_slice(&builder.manager.to_bytes());
53        data.extend_from_slice(&builder.reclaim_authority.to_bytes());
54        Instruction {
55            program_id: builder.program_id,
56            accounts: vec![
57                AccountMeta::new(builder.payer, true),
58                AccountMeta::new(builder.state_pda, false),
59                AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
60            ],
61            data,
62        }
63    }
64}
65
66/// Parsed inputs of an `Initialize` instruction.
67pub struct InitializeInput<'a, A> {
68    pub payer: &'a A,
69    pub state_pda: &'a A,
70    pub manager: Pubkey,
71    pub reclaim_authority: Pubkey,
72}
73
74impl<'a, A> InstructionInputParsing<'a, A> for InitializeInput<'a, A> {
75    const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::Initialize;
76
77    fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
78        let authorities: &[u8; 2 * size_of::<Pubkey>()] = instruction_data
79            .try_into()
80            .map_err(|_| ProgramError::InvalidInstructionData)?;
81        let (manager, reclaim_authority) =
82            array_refs![authorities, size_of::<Pubkey>(), size_of::<Pubkey>()];
83        let manager = Pubkey::new_from_array(*manager);
84        let reclaim_authority = Pubkey::new_from_array(*reclaim_authority);
85
86        // Accounts: [payer (W,S), state_pda (W), system_program (R)]. The system
87        // program needs to be present for the `CreateAccount` CPI but doesn't
88        // need to be referenced directly and can be at any later position.
89        let [payer, state_pda, _system, ..] = accounts else {
90            return Err(ProgramError::NotEnoughAccountKeys);
91        };
92
93        Ok(Self {
94            payer,
95            state_pda,
96            manager,
97            reclaim_authority,
98        })
99    }
100}
101
102/// Test scaffolding for `Initialize` parsing and handling, shared by this
103/// crate's tests and the settlement program's via the `test-fixtures` feature.
104#[cfg(any(test, feature = "test-fixtures"))]
105pub mod fixtures {
106    use solana_address::Address;
107
108    use super::{Initialize, Instruction};
109
110    /// Number of accounts `Initialize` expects: payer, state PDA, system program.
111    pub const NUM_ACCOUNTS: usize = 3;
112
113    /// `Initialize` instruction data with placeholder addresses, for failure
114    /// cases where the actual addresses don't matter.
115    pub fn initialize_data() -> Vec<u8> {
116        let zero = Address::new_from_array([0; 32]);
117        Instruction::from(Initialize {
118            program_id: zero,
119            payer: zero,
120            state_pda: zero,
121            reclaim_authority: zero,
122            manager: zero,
123        })
124        .data
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::fixtures::{initialize_data, NUM_ACCOUNTS};
131    use super::*;
132    use crate::data::state::EncodedStateAccount;
133    use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
134    use crate::instruction::tests::{
135        assert_readonly_nonsigner, assert_writable_nonsigner, assert_writable_signer,
136    };
137    use crate::tests::pubkey_from_seed;
138    use solana_account_view::AccountView;
139    use solana_address::Address;
140
141    #[test]
142    fn initialize_input_parses_valid_input() {
143        let program_id = Address::new_unique();
144        let payer = fake_account(pubkey_from_seed("payer"));
145        let state_pda = fake_account(pubkey_from_seed("state pda"));
146        let reclaim_authority = pubkey_from_seed("reclaim authority");
147        let manager = pubkey_from_seed("manager");
148        let data = Instruction::from(Initialize {
149            program_id,
150            payer: *payer.address(),
151            state_pda: *state_pda.address(),
152            manager,
153            reclaim_authority,
154        })
155        .data;
156
157        let system_program = fake_account(pubkey_from_seed("system program"));
158        let accounts = [payer, state_pda, system_program];
159
160        let InitializeInput {
161            payer: parsed_payer,
162            state_pda: parsed_state_pda,
163            manager: parsed_manager,
164            reclaim_authority: parsed_reclaim_authority,
165        } = InitializeInput::parse(&data, &accounts).expect("parse should succeed");
166
167        assert_eq!(parsed_payer.address(), payer.address());
168        assert_eq!(parsed_state_pda.address(), state_pda.address());
169        assert_eq!(parsed_manager, manager);
170        assert_eq!(parsed_reclaim_authority, reclaim_authority);
171    }
172
173    #[test]
174    fn initialize_input_rejects_long_data() {
175        let mut data = initialize_data();
176        data.push(0); // trailing byte
177        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
178        assert_eq!(
179            InitializeInput::parse(&data, &accounts).err(),
180            Some(ProgramError::InvalidInstructionData),
181        );
182    }
183
184    #[test]
185    fn initialize_input_rejects_short_data() {
186        let mut data = initialize_data();
187        data.pop(); // one byte short
188        let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
189        assert_eq!(
190            InitializeInput::parse(&data, &accounts).err(),
191            Some(ProgramError::InvalidInstructionData),
192        );
193    }
194
195    #[test]
196    fn initialize_input_rejects_missing_accounts() {
197        let data = initialize_data();
198        let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
199        accounts.pop();
200        assert_eq!(
201            InitializeInput::parse(&data, &accounts).err(),
202            Some(ProgramError::NotEnoughAccountKeys),
203        );
204    }
205
206    #[test]
207    fn instruction_data_has_expected_layout() {
208        let program_id = pubkey_from_seed("program id");
209        let payer = pubkey_from_seed("payer");
210        let state_pda = pubkey_from_seed("state pda");
211        let reclaim_authority = pubkey_from_seed("reclaim authority");
212        let manager = pubkey_from_seed("manager");
213
214        let Instruction { data, .. } = Initialize {
215            program_id,
216            payer,
217            state_pda,
218            reclaim_authority,
219            manager,
220        }
221        .into();
222        assert_eq!(data.len(), EncodedStateAccount::SIZE);
223        assert_eq!(data[0], SettlementInstruction::Initialize.discriminator());
224        assert_eq!(&data[1..33], &manager.to_bytes());
225        assert_eq!(&data[33..], &reclaim_authority.to_bytes());
226    }
227
228    #[test]
229    fn instruction_data_regression() {
230        let manager = Pubkey::new_from_array([0x11; 32]);
231        let reclaim_authority = Pubkey::new_from_array([0x22; 32]);
232
233        let Instruction { data, .. } = Initialize {
234            program_id: pubkey_from_seed("program id"),
235            payer: pubkey_from_seed("payer"),
236            state_pda: pubkey_from_seed("state pda"),
237            manager,
238            reclaim_authority,
239        }
240        .into();
241
242        #[rustfmt::skip]
243        let expected: [u8; EncodedStateAccount::SIZE] = [
244            // discriminator (Initialize = 3)
245            0x03,
246            // manager
247            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
248            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
249            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
250            0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
251            // reclaim_authority
252            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
253            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
254            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
255            0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
256        ];
257        assert_eq!(data, expected);
258    }
259
260    #[test]
261    fn instruction_has_expected_accounts() {
262        let program_id = pubkey_from_seed("program id");
263        let payer = pubkey_from_seed("payer");
264        let state_pda = pubkey_from_seed("state pda");
265        let manager = pubkey_from_seed("manager");
266        let reclaim_authority = pubkey_from_seed("reclaim authority");
267
268        let Instruction { accounts, .. } = Initialize {
269            program_id,
270            payer,
271            state_pda,
272            reclaim_authority,
273            manager,
274        }
275        .into();
276
277        assert_eq!(accounts.len(), 3);
278        // payer funds the new account's rent; state_pda is signed for by the
279        // program via PDA seeds; the system program is only referenced.
280        assert_writable_signer(&accounts[0], payer);
281        assert_writable_nonsigner(&accounts[1], state_pda);
282        assert_readonly_nonsigner(&accounts[2], SYSTEM_PROGRAM_ID);
283    }
284}