cow_settlement_interface/instruction/
withdraw_native_sol.rs1use core::mem::size_of;
18
19use solana_instruction::{AccountMeta, Instruction};
20use solana_program_error::ProgramError;
21use solana_pubkey::Pubkey;
22
23use super::InstructionInputParsing;
24use crate::SettlementInstruction;
25
26pub struct WithdrawNativeSol {
34 pub program_id: Pubkey,
35 pub state_pda: Pubkey,
36 pub authority: Pubkey,
37 pub native_sol_buffer: Pubkey,
38 pub recipient: Pubkey,
39 pub amount: u64,
40}
41
42impl From<WithdrawNativeSol> for Instruction {
43 fn from(builder: WithdrawNativeSol) -> Self {
44 let mut data = vec![SettlementInstruction::WithdrawNativeSol.discriminator()];
45 data.extend_from_slice(&builder.amount.to_le_bytes());
46 Instruction {
47 program_id: builder.program_id,
48 accounts: vec![
49 AccountMeta::new_readonly(builder.state_pda, false),
50 AccountMeta::new_readonly(builder.authority, true),
51 AccountMeta::new(builder.native_sol_buffer, false),
52 AccountMeta::new(builder.recipient, false),
53 ],
54 data,
55 }
56 }
57}
58
59pub struct WithdrawNativeSolInput<'a, A> {
61 pub state_pda: &'a A,
62 pub authority: &'a A,
63 pub native_sol_buffer: &'a A,
64 pub recipient: &'a A,
65 pub amount: u64,
66}
67
68impl<'a, A> InstructionInputParsing<'a, A> for WithdrawNativeSolInput<'a, A> {
69 const DISCRIMINATOR: SettlementInstruction = SettlementInstruction::WithdrawNativeSol;
70
71 fn parse_body(instruction_data: &[u8], accounts: &'a [A]) -> Result<Self, ProgramError> {
72 let amount: &[u8; size_of::<u64>()] = instruction_data
73 .try_into()
74 .map_err(|_| ProgramError::InvalidInstructionData)?;
75 let amount = u64::from_le_bytes(*amount);
76
77 let [state_pda, authority, native_sol_buffer, recipient, ..] = accounts else {
80 return Err(ProgramError::NotEnoughAccountKeys);
81 };
82
83 Ok(Self {
84 state_pda,
85 authority,
86 native_sol_buffer,
87 recipient,
88 amount,
89 })
90 }
91}
92
93#[cfg(any(test, feature = "test-fixtures"))]
97pub mod fixtures {
98 use solana_address::Address;
99
100 use super::{Instruction, WithdrawNativeSol};
101
102 pub const NUM_ACCOUNTS: usize = 4;
105
106 pub fn withdraw_native_sol_data(amount: u64) -> Vec<u8> {
109 let zero = Address::new_from_array([0; 32]);
110 Instruction::from(WithdrawNativeSol {
111 program_id: zero,
112 state_pda: zero,
113 authority: zero,
114 native_sol_buffer: zero,
115 recipient: zero,
116 amount,
117 })
118 .data
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::fixtures::{withdraw_native_sol_data, NUM_ACCOUNTS};
125 use super::*;
126 use crate::fixtures::pubkey_from_seed;
127 use crate::instruction::fixtures::{fake_account, fake_sequential_accounts};
128 use crate::instruction::tests::{
129 assert_readonly_nonsigner, assert_readonly_signer, assert_writable_nonsigner,
130 };
131
132 fn sample() -> WithdrawNativeSol {
133 WithdrawNativeSol {
134 program_id: pubkey_from_seed("program id"),
135 state_pda: pubkey_from_seed("state pda"),
136 authority: pubkey_from_seed("authority"),
137 native_sol_buffer: pubkey_from_seed("native sol buffer"),
138 recipient: pubkey_from_seed("recipient"),
139 amount: 0x0102_0304_0506_0708,
140 }
141 }
142
143 #[test]
144 fn withdraw_native_sol_input_parses_valid_input() {
145 let builder = sample();
146 let accounts = [
147 fake_account(builder.state_pda),
148 fake_account(builder.authority),
149 fake_account(builder.native_sol_buffer),
150 fake_account(builder.recipient),
151 ];
152 let (state_pda, authority, native_sol_buffer, recipient, amount) = (
153 builder.state_pda,
154 builder.authority,
155 builder.native_sol_buffer,
156 builder.recipient,
157 builder.amount,
158 );
159 let data = Instruction::from(builder).data;
160
161 let WithdrawNativeSolInput {
162 state_pda: parsed_state_pda,
163 authority: parsed_authority,
164 native_sol_buffer: parsed_native_sol_buffer,
165 recipient: parsed_recipient,
166 amount: parsed_amount,
167 } = WithdrawNativeSolInput::parse(&data, &accounts).expect("parse should succeed");
168
169 assert_eq!(*parsed_state_pda.address(), state_pda);
170 assert_eq!(*parsed_authority.address(), authority);
171 assert_eq!(*parsed_native_sol_buffer.address(), native_sol_buffer);
172 assert_eq!(*parsed_recipient.address(), recipient);
173 assert_eq!(parsed_amount, amount);
174 }
175
176 #[test]
177 fn withdraw_native_sol_input_rejects_short_data() {
178 let mut data = withdraw_native_sol_data(1);
179 data.pop();
180 let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
181 assert_eq!(
182 WithdrawNativeSolInput::parse(&data, &accounts).err(),
183 Some(ProgramError::InvalidInstructionData),
184 );
185 }
186
187 #[test]
188 fn withdraw_native_sol_input_rejects_long_data() {
189 let mut data = withdraw_native_sol_data(1);
190 data.push(0);
191 let accounts = fake_sequential_accounts::<NUM_ACCOUNTS>();
192 assert_eq!(
193 WithdrawNativeSolInput::parse(&data, &accounts).err(),
194 Some(ProgramError::InvalidInstructionData),
195 );
196 }
197
198 #[test]
199 fn withdraw_native_sol_input_rejects_missing_accounts() {
200 let data = withdraw_native_sol_data(1);
201 let accounts = fake_sequential_accounts::<{ NUM_ACCOUNTS - 1 }>();
202 assert_eq!(
203 WithdrawNativeSolInput::parse(&data, &accounts).err(),
204 Some(ProgramError::NotEnoughAccountKeys),
205 );
206 }
207
208 #[test]
209 fn instruction_data_has_expected_layout() {
210 let Instruction { data, .. } = WithdrawNativeSol {
211 amount: 0x0102_0304_0506_0708,
212 ..sample()
213 }
214 .into();
215 assert_eq!(
216 data,
217 [
218 SettlementInstruction::WithdrawNativeSol.discriminator(),
219 8,
220 7,
221 6,
222 5,
223 4,
224 3,
225 2,
226 1,
227 ]
228 );
229 }
230
231 #[test]
232 fn instruction_has_expected_accounts() {
233 let builder = sample();
234 let ix = Instruction::from(sample());
235
236 assert_eq!(ix.program_id, builder.program_id);
237 assert_eq!(ix.accounts.len(), NUM_ACCOUNTS);
238 assert_readonly_nonsigner(&ix.accounts[0], builder.state_pda);
239 assert_readonly_signer(&ix.accounts[1], builder.authority);
240 assert_writable_nonsigner(&ix.accounts[2], builder.native_sol_buffer);
241 assert_writable_nonsigner(&ix.accounts[3], builder.recipient);
242 }
243}