safe_token_2022/extension/transfer_fee/
processor.rs1use {
2 crate::{
3 check_program_account,
4 error::TokenError,
5 extension::{
6 transfer_fee::{
7 instruction::TransferFeeInstruction, TransferFee, TransferFeeAmount,
8 TransferFeeConfig, MAX_FEE_BASIS_POINTS,
9 },
10 BaseStateWithExtensions, StateWithExtensions, StateWithExtensionsMut,
11 },
12 processor::Processor,
13 state::{Account, Mint},
14 },
15 solana_program::{
16 account_info::{next_account_info, AccountInfo},
17 clock::Clock,
18 entrypoint::ProgramResult,
19 msg,
20 program_option::COption,
21 pubkey::Pubkey,
22 sysvar::Sysvar,
23 },
24 std::convert::TryInto,
25};
26
27fn process_initialize_transfer_fee_config(
28 accounts: &[AccountInfo],
29 transfer_fee_config_authority: COption<Pubkey>,
30 withdraw_withheld_authority: COption<Pubkey>,
31 transfer_fee_basis_points: u16,
32 maximum_fee: u64,
33) -> ProgramResult {
34 let account_info_iter = &mut accounts.iter();
35 let mint_account_info = next_account_info(account_info_iter)?;
36
37 let mut mint_data = mint_account_info.data.borrow_mut();
38 let mut mint = StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data)?;
39 let extension = mint.init_extension::<TransferFeeConfig>(true)?;
40 extension.transfer_fee_config_authority = transfer_fee_config_authority.try_into()?;
41 extension.withdraw_withheld_authority = withdraw_withheld_authority.try_into()?;
42 extension.withheld_amount = 0u64.into();
43
44 if transfer_fee_basis_points > MAX_FEE_BASIS_POINTS {
45 return Err(TokenError::TransferFeeExceedsMaximum.into());
46 }
47 let epoch = Clock::get()?.epoch;
50 let transfer_fee = TransferFee {
51 epoch: epoch.into(),
52 transfer_fee_basis_points: transfer_fee_basis_points.into(),
53 maximum_fee: maximum_fee.into(),
54 };
55 extension.older_transfer_fee = transfer_fee;
56 extension.newer_transfer_fee = transfer_fee;
57
58 Ok(())
59}
60
61fn process_set_transfer_fee(
62 program_id: &Pubkey,
63 accounts: &[AccountInfo],
64 transfer_fee_basis_points: u16,
65 maximum_fee: u64,
66) -> ProgramResult {
67 let account_info_iter = &mut accounts.iter();
68 let mint_account_info = next_account_info(account_info_iter)?;
69 let authority_info = next_account_info(account_info_iter)?;
70 let authority_info_data_len = authority_info.data_len();
71
72 let mut mint_data = mint_account_info.data.borrow_mut();
73 let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
74 let extension = mint.get_extension_mut::<TransferFeeConfig>()?;
75
76 let transfer_fee_config_authority =
77 Option::<Pubkey>::from(extension.transfer_fee_config_authority)
78 .ok_or(TokenError::NoAuthorityExists)?;
79 Processor::validate_owner(
80 program_id,
81 &transfer_fee_config_authority,
82 authority_info,
83 authority_info_data_len,
84 account_info_iter.as_slice(),
85 )?;
86
87 if transfer_fee_basis_points > MAX_FEE_BASIS_POINTS {
88 return Err(TokenError::TransferFeeExceedsMaximum.into());
89 }
90
91 let epoch = Clock::get()?.epoch;
97 if u64::from(extension.newer_transfer_fee.epoch) <= epoch {
98 extension.older_transfer_fee = extension.newer_transfer_fee;
99 }
100 let newer_fee_start_epoch = epoch.saturating_add(2);
102 let transfer_fee = TransferFee {
103 epoch: newer_fee_start_epoch.into(),
104 transfer_fee_basis_points: transfer_fee_basis_points.into(),
105 maximum_fee: maximum_fee.into(),
106 };
107 extension.newer_transfer_fee = transfer_fee;
108
109 Ok(())
110}
111
112fn process_withdraw_withheld_tokens_from_mint(
113 program_id: &Pubkey,
114 accounts: &[AccountInfo],
115) -> ProgramResult {
116 let account_info_iter = &mut accounts.iter();
117 let mint_account_info = next_account_info(account_info_iter)?;
118 let destination_account_info = next_account_info(account_info_iter)?;
119 let authority_info = next_account_info(account_info_iter)?;
120 let authority_info_data_len = authority_info.data_len();
121
122 check_program_account(mint_account_info.owner)?;
124
125 let mut mint_data = mint_account_info.data.borrow_mut();
126 let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
127 let extension = mint.get_extension_mut::<TransferFeeConfig>()?;
128
129 let withdraw_withheld_authority = Option::<Pubkey>::from(extension.withdraw_withheld_authority)
130 .ok_or(TokenError::NoAuthorityExists)?;
131 Processor::validate_owner(
132 program_id,
133 &withdraw_withheld_authority,
134 authority_info,
135 authority_info_data_len,
136 account_info_iter.as_slice(),
137 )?;
138
139 let mut destination_account_data = destination_account_info.data.borrow_mut();
140 let mut destination_account =
141 StateWithExtensionsMut::<Account>::unpack(&mut destination_account_data)?;
142 if destination_account.base.mint != *mint_account_info.key {
143 return Err(TokenError::MintMismatch.into());
144 }
145 if destination_account.base.is_frozen() {
146 return Err(TokenError::AccountFrozen.into());
147 }
148 let withheld_amount = u64::from(extension.withheld_amount);
149 extension.withheld_amount = 0.into();
150 destination_account.base.amount = destination_account
151 .base
152 .amount
153 .checked_add(withheld_amount)
154 .ok_or(TokenError::Overflow)?;
155 destination_account.pack_base();
156
157 Ok(())
158}
159
160fn harvest_from_account<'a, 'b>(
161 mint_key: &'b Pubkey,
162 token_account_info: &'b AccountInfo<'a>,
163) -> Result<u64, TokenError> {
164 let mut token_account_data = token_account_info.data.borrow_mut();
165 let mut token_account = StateWithExtensionsMut::<Account>::unpack(&mut token_account_data)
166 .map_err(|_| TokenError::InvalidState)?;
167 if token_account.base.mint != *mint_key {
168 return Err(TokenError::MintMismatch);
169 }
170 check_program_account(token_account_info.owner).map_err(|_| TokenError::InvalidState)?;
171 let token_account_extension = token_account
172 .get_extension_mut::<TransferFeeAmount>()
173 .map_err(|_| TokenError::InvalidState)?;
174 let account_withheld_amount = u64::from(token_account_extension.withheld_amount);
175 token_account_extension.withheld_amount = 0.into();
176 Ok(account_withheld_amount)
177}
178
179fn process_harvest_withheld_tokens_to_mint(accounts: &[AccountInfo]) -> ProgramResult {
180 let account_info_iter = &mut accounts.iter();
181 let mint_account_info = next_account_info(account_info_iter)?;
182 let token_account_infos = account_info_iter.as_slice();
183
184 let mut mint_data = mint_account_info.data.borrow_mut();
185 let mut mint = StateWithExtensionsMut::<Mint>::unpack(&mut mint_data)?;
186 let mint_extension = mint.get_extension_mut::<TransferFeeConfig>()?;
187
188 for token_account_info in token_account_infos {
189 match harvest_from_account(mint_account_info.key, token_account_info) {
190 Ok(amount) => {
191 let mint_withheld_amount = u64::from(mint_extension.withheld_amount);
192 mint_extension.withheld_amount = mint_withheld_amount
193 .checked_add(amount)
194 .ok_or(TokenError::Overflow)?
195 .into();
196 }
197 Err(e) => {
198 msg!("Error harvesting from {}: {}", token_account_info.key, e);
199 }
200 }
201 }
202 Ok(())
203}
204
205fn process_withdraw_withheld_tokens_from_accounts(
206 program_id: &Pubkey,
207 accounts: &[AccountInfo],
208 num_token_accounts: u8,
209) -> ProgramResult {
210 let account_info_iter = &mut accounts.iter();
211 let mint_account_info = next_account_info(account_info_iter)?;
212 let destination_account_info = next_account_info(account_info_iter)?;
213 let authority_info = next_account_info(account_info_iter)?;
214 let authority_info_data_len = authority_info.data_len();
215 let account_infos = account_info_iter.as_slice();
216 let num_signers = account_infos
217 .len()
218 .saturating_sub(num_token_accounts as usize);
219
220 check_program_account(mint_account_info.owner)?;
222
223 let mint_data = mint_account_info.data.borrow();
224 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
225 let extension = mint.get_extension::<TransferFeeConfig>()?;
226
227 let withdraw_withheld_authority = Option::<Pubkey>::from(extension.withdraw_withheld_authority)
228 .ok_or(TokenError::NoAuthorityExists)?;
229 Processor::validate_owner(
230 program_id,
231 &withdraw_withheld_authority,
232 authority_info,
233 authority_info_data_len,
234 &account_infos[..num_signers],
235 )?;
236
237 let mut destination_account_data = destination_account_info.data.borrow_mut();
238 let mut destination_account =
239 StateWithExtensionsMut::<Account>::unpack(&mut destination_account_data)?;
240 if destination_account.base.mint != *mint_account_info.key {
241 return Err(TokenError::MintMismatch.into());
242 }
243 if destination_account.base.is_frozen() {
244 return Err(TokenError::AccountFrozen.into());
245 }
246 for account_info in &account_infos[num_signers..] {
247 if account_info.key == destination_account_info.key {
249 let token_account_extension = destination_account
250 .get_extension_mut::<TransferFeeAmount>()
251 .map_err(|_| TokenError::InvalidState)?;
252 let account_withheld_amount = u64::from(token_account_extension.withheld_amount);
253 token_account_extension.withheld_amount = 0.into();
254 destination_account.base.amount = destination_account
255 .base
256 .amount
257 .checked_add(account_withheld_amount)
258 .ok_or(TokenError::Overflow)?;
259 } else {
260 match harvest_from_account(mint_account_info.key, account_info) {
261 Ok(amount) => {
262 destination_account.base.amount = destination_account
263 .base
264 .amount
265 .checked_add(amount)
266 .ok_or(TokenError::Overflow)?;
267 }
268 Err(e) => {
269 msg!("Error harvesting from {}: {}", account_info.key, e);
270 }
271 }
272 }
273 }
274 destination_account.pack_base();
275
276 Ok(())
277}
278
279pub(crate) fn process_instruction(
280 program_id: &Pubkey,
281 accounts: &[AccountInfo],
282 instruction: TransferFeeInstruction,
283) -> ProgramResult {
284 check_program_account(program_id)?;
285
286 match instruction {
287 TransferFeeInstruction::InitializeTransferFeeConfig {
288 transfer_fee_config_authority,
289 withdraw_withheld_authority,
290 transfer_fee_basis_points,
291 maximum_fee,
292 } => process_initialize_transfer_fee_config(
293 accounts,
294 transfer_fee_config_authority,
295 withdraw_withheld_authority,
296 transfer_fee_basis_points,
297 maximum_fee,
298 ),
299 TransferFeeInstruction::TransferCheckedWithFee {
300 amount,
301 decimals,
302 fee,
303 } => {
304 msg!("TransferFeeInstruction: TransferCheckedWithFee");
305 Processor::process_transfer(program_id, accounts, amount, Some(decimals), Some(fee))
306 }
307 TransferFeeInstruction::WithdrawWithheldTokensFromMint => {
308 msg!("TransferFeeInstruction: WithdrawWithheldTokensFromMint");
309 process_withdraw_withheld_tokens_from_mint(program_id, accounts)
310 }
311 TransferFeeInstruction::WithdrawWithheldTokensFromAccounts { num_token_accounts } => {
312 msg!("TransferFeeInstruction: WithdrawWithheldTokensFromAccounts");
313 process_withdraw_withheld_tokens_from_accounts(program_id, accounts, num_token_accounts)
314 }
315 TransferFeeInstruction::HarvestWithheldTokensToMint => {
316 msg!("TransferFeeInstruction: HarvestWithheldTokensToMint");
317 process_harvest_withheld_tokens_to_mint(accounts)
318 }
319 TransferFeeInstruction::SetTransferFee {
320 transfer_fee_basis_points,
321 maximum_fee,
322 } => {
323 msg!("TransferFeeInstruction: SetTransferFee");
324 process_set_transfer_fee(program_id, accounts, transfer_fee_basis_points, maximum_fee)
325 }
326 }
327}