cow_settlement_interface/instruction/
add_solver.rs1use core::mem::size_of;
9
10use solana_instruction::{AccountMeta, Instruction};
11use solana_program_error::ProgramError;
12use solana_pubkey::Pubkey;
13
14pub use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
15
16use crate::instruction::InstructionInputParsing;
17use crate::SettlementInstruction;
18
19pub struct AddSolver {
31 pub program_id: Pubkey,
32 pub manager: Pubkey,
33 pub payer: Pubkey,
34 pub state_pda: Pubkey,
35 pub solver: Pubkey,
36}
37
38impl From<AddSolver> for Instruction {
39 fn from(builder: AddSolver) -> Self {
40 let mut data = vec![SettlementInstruction::AddSolver.discriminator()];
41 data.extend_from_slice(&builder.solver.to_bytes());
42 Instruction {
43 program_id: builder.program_id,
44 accounts: vec![
45 AccountMeta::new_readonly(builder.manager, true),
46 AccountMeta::new(builder.payer, true),
47 AccountMeta::new(builder.state_pda, false),
48 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
49 ],
50 data,
51 }
52 }
53}
54
55pub struct AddSolverInput<'a, A> {
57 pub manager: &'a A,
58 pub payer: &'a A,
59 pub state_pda: &'a A,
60 pub solver: Pubkey,
61}
62
63impl<'a, A> InstructionInputParsing<'a, A> for AddSolverInput<'a, A> {
64 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::AddSolver;
65
66 fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
67 let solver: &[u8; size_of::<Pubkey>()] = instruction_data
68 .try_into()
69 .map_err(|_| ProgramError::InvalidInstructionData)?;
70 let solver = Pubkey::new_from_array(*solver);
71
72 let [manager, payer, state_pda, _system, ..] = accounts else {
76 return Err(ProgramError::NotEnoughAccountKeys);
77 };
78
79 Ok(Self {
80 manager,
81 payer,
82 state_pda,
83 solver,
84 })
85 }
86}
87
88#[cfg(any(test, feature = "test-fixtures"))]
91pub mod fixtures {
92 use solana_address::Address;
93 use solana_instruction::Instruction;
94
95 use super::AddSolver;
96
97 pub const NUM_ACCOUNTS: usize = 4;
100
101 pub fn add_solver_data() -> Vec<u8> {
104 let zero = Address::default();
105 Instruction::from(AddSolver {
106 program_id: zero,
107 manager: zero,
108 payer: zero,
109 state_pda: zero,
110 solver: zero,
111 })
112 .data
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::fixtures::{add_solver_data, NUM_ACCOUNTS};
119 use super::*;
120 use crate::fixtures::pubkey_from_seed;
121 use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
122 use crate::instruction::tests::{
123 assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
124 assert_writable_signer,
125 };
126 use solana_account_view::AccountView;
127
128 #[test]
129 fn add_solver_input_parses_valid_input() {
130 let program_id = pubkey_from_seed("program id");
131 let manager = fake_account(pubkey_from_seed("manager"));
132 let payer = fake_account(pubkey_from_seed("payer"));
133 let state_pda = fake_account(pubkey_from_seed("state pda"));
134 let system_program = fake_account(pubkey_from_seed("system program"));
135 let solver = pubkey_from_seed("solver");
136
137 let data = Instruction::from(AddSolver {
138 program_id,
139 manager: *manager.address(),
140 payer: *payer.address(),
141 state_pda: *state_pda.address(),
142 solver,
143 })
144 .data;
145 let accounts = [manager, payer, state_pda, system_program];
146
147 let AddSolverInput {
148 manager: parsed_manager,
149 payer: parsed_payer,
150 state_pda: parsed_state_pda,
151 solver: parsed_solver,
152 } = AddSolverInput::parse(&data, &accounts).expect("parse should succeed");
153
154 assert_eq!(parsed_manager.address(), manager.address());
155 assert_eq!(parsed_payer.address(), payer.address());
156 assert_eq!(parsed_state_pda.address(), state_pda.address());
157 assert_eq!(parsed_solver, solver);
158 }
159
160 #[test]
161 fn add_solver_input_rejects_long_data() {
162 let mut data = add_solver_data();
163 data.push(42); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
165 assert_eq!(
166 AddSolverInput::parse(&data, &accounts).err(),
167 Some(ProgramError::InvalidInstructionData),
168 );
169 }
170
171 #[test]
172 fn add_solver_input_rejects_short_data() {
173 let mut data = add_solver_data();
174 data.pop(); let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
176 assert_eq!(
177 AddSolverInput::parse(&data, &accounts).err(),
178 Some(ProgramError::InvalidInstructionData),
179 );
180 }
181
182 #[test]
183 fn add_solver_input_rejects_missing_accounts() {
184 let data = add_solver_data();
185 let mut accounts: Vec<AccountView> = fake_sequential_accounts::<NUM_ACCOUNTS>().into();
186 accounts.pop();
187 assert_eq!(
188 AddSolverInput::parse(&data, &accounts).err(),
189 Some(ProgramError::NotEnoughAccountKeys),
190 );
191 }
192
193 #[test]
194 fn instruction_data_has_expected_layout() {
195 let solver = pubkey_from_seed("solver");
196 let Instruction { data, .. } = AddSolver {
197 program_id: pubkey_from_seed("program id"),
198 manager: pubkey_from_seed("manager"),
199 payer: pubkey_from_seed("payer"),
200 state_pda: pubkey_from_seed("state pda"),
201 solver,
202 }
203 .into();
204
205 assert_eq!(data.len(), 1 + size_of::<Pubkey>());
206 assert_eq!(data[0], SettlementInstruction::AddSolver.discriminator());
207 assert_eq!(&data[1..], &solver.to_bytes());
208 }
209
210 #[test]
211 fn instruction_data_regression() {
212 let solver = Pubkey::new_from_array([0x11; 32]);
213 let Instruction { data, .. } = AddSolver {
214 program_id: pubkey_from_seed("program id"),
215 manager: pubkey_from_seed("manager"),
216 payer: pubkey_from_seed("payer"),
217 state_pda: pubkey_from_seed("state pda"),
218 solver,
219 }
220 .into();
221
222 #[rustfmt::skip]
223 let expected: [u8; 1 + size_of::<Pubkey>()] = [
224 0x08,
226 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
228 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
229 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
230 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
231 ];
232 assert_eq!(data, expected);
233 }
234
235 #[test]
236 fn instruction_has_expected_accounts() {
237 let manager = pubkey_from_seed("manager");
238 let payer = pubkey_from_seed("payer");
239 let state_pda = pubkey_from_seed("state pda");
240 let Instruction { accounts, .. } = AddSolver {
241 program_id: pubkey_from_seed("program id"),
242 manager,
243 payer,
244 state_pda,
245 solver: pubkey_from_seed("solver"),
246 }
247 .into();
248
249 assert_eq!(accounts.len(), 4);
250 assert_readonly_signer(&accounts[0], manager);
253 assert_writable_signer(&accounts[1], payer);
254 assert_writable_nonsigner(&accounts[2], state_pda);
255 assert_readonly_nonsigner(&accounts[3], SYSTEM_PROGRAM_ID);
256 }
257}