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