Skip to content

Commit 4f9e003

Browse files
committed
Add withdraw_excess_lamports instruction
1 parent 18adf4f commit 4f9e003

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-1
lines changed

Diff for: interface/src/instruction.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,18 @@ pub enum TokenInstruction {
478478
/// - `&str` The `ui_amount` of tokens to reformat.
479479
UiAmountToAmount,
480480

481+
/// This instruction is to be used to rescue SOL sent to any `TokenProgram`
482+
/// owned account by sending them to any other account, leaving behind only
483+
/// lamports for rent exemption.
484+
///
485+
/// Accounts expected by this instruction:
486+
///
487+
/// 0. `[writable]` Source Account owned by the token program
488+
/// 1. `[writable]` Destination account
489+
/// 2. `[signer]` Authority
490+
/// 3. `..+M` `[signer]` M signer accounts.
491+
WithdrawExcessLamports = 38,
492+
481493
/// Executes a batch of instructions. The instructions to be executed are specified
482494
/// in sequence on the instruction data. Each instruction provides:
483495
/// - `u8`: number of accounts
@@ -503,7 +515,7 @@ impl TryFrom<u8> for TokenInstruction {
503515
fn try_from(value: u8) -> Result<Self, Self::Error> {
504516
match value {
505517
// SAFETY: `value` is guaranteed to be in the range of the enum variants.
506-
0..=24 | 255 => Ok(unsafe { core::mem::transmute::<u8, TokenInstruction>(value) }),
518+
0..=24 | 38 | 255 => Ok(unsafe { core::mem::transmute::<u8, TokenInstruction>(value) }),
507519
_ => Err(ProgramError::InvalidInstructionData),
508520
}
509521
}

Diff for: p-token/src/entrypoint.rs

+7
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,13 @@ fn inner_process_remaining_instruction(
257257

258258
process_ui_amount_to_amount(accounts, instruction_data)
259259
}
260+
// 38 - WithdrawExcessLamports
261+
38 => {
262+
#[cfg(feature = "logging")]
263+
pinocchio::msg!("Instruction: WithdrawExcessLamports");
264+
265+
process_withdraw_excess_lamports(accounts)
266+
}
260267
_ => Err(ProgramError::InvalidInstructionData),
261268
}
262269
}

Diff for: p-token/src/processor/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub mod thaw_account;
3939
pub mod transfer;
4040
pub mod transfer_checked;
4141
pub mod ui_amount_to_amount;
42+
pub mod withdraw_excess_lamports;
4243
// Shared processors.
4344
pub mod shared;
4445

@@ -68,6 +69,7 @@ pub use thaw_account::process_thaw_account;
6869
pub use transfer::process_transfer;
6970
pub use transfer_checked::process_transfer_checked;
7071
pub use ui_amount_to_amount::process_ui_amount_to_amount;
72+
pub use withdraw_excess_lamports::process_withdraw_excess_lamports;
7173

7274
/// Maximum number of digits in a formatted `u64`.
7375
///

Diff for: p-token/src/processor/withdraw_excess_lamports.rs

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
program_error::ProgramError,
4+
sysvars::{rent::Rent, Sysvar},
5+
ProgramResult,
6+
};
7+
use spl_token_interface::{
8+
error::TokenError,
9+
state::{account::Account, load, mint::Mint, multisig::Multisig, Transmutable},
10+
};
11+
12+
use super::validate_owner;
13+
14+
#[inline(always)]
15+
pub fn process_withdraw_excess_lamports(accounts: &[AccountInfo]) -> ProgramResult {
16+
let [source_account_info, destination_info, authority_info, remaining @ ..] = accounts else {
17+
return Err(ProgramError::NotEnoughAccountKeys);
18+
};
19+
20+
// SAFETY: single mutable borrow to `source_account_info` account data
21+
let source_data = unsafe { source_account_info.borrow_data_unchecked() };
22+
23+
match source_data.len() {
24+
Account::LEN => {
25+
let account = unsafe { load::<Account>(source_data)? };
26+
27+
if account.is_native() {
28+
return Err(TokenError::NativeNotSupported.into());
29+
}
30+
31+
validate_owner(&account.owner, authority_info, remaining)?;
32+
}
33+
Mint::LEN => {
34+
let mint = unsafe { load::<Mint>(source_data)? };
35+
36+
if let Some(mint_authority) = mint.mint_authority() {
37+
validate_owner(mint_authority, authority_info, remaining)?;
38+
} else {
39+
return Err(TokenError::AuthorityTypeNotSupported.into());
40+
}
41+
}
42+
Multisig::LEN => {
43+
validate_owner(source_account_info.key(), authority_info, remaining)?;
44+
}
45+
_ => return Err(TokenError::InvalidState.into()),
46+
}
47+
48+
let source_rent_exempt_reserve = Rent::get()?.minimum_balance(source_data.len());
49+
50+
let transfer_amount = source_account_info
51+
.lamports()
52+
.checked_sub(source_rent_exempt_reserve)
53+
.ok_or(TokenError::NotRentExempt)?;
54+
55+
let source_starting_lamports = source_account_info.lamports();
56+
// SAFETY: single mutable borrow to `source_account_info` lamports.
57+
unsafe {
58+
// Moves the lamports out of the source account.
59+
*source_account_info.borrow_mut_lamports_unchecked() = source_starting_lamports
60+
.checked_sub(transfer_amount)
61+
.ok_or(TokenError::Overflow)?;
62+
}
63+
64+
let destination_starting_lamports = destination_info.lamports();
65+
// SAFETY: single mutable borrow to `destination_info` lamports.
66+
unsafe {
67+
// Moves the lamports to the destination account.
68+
*destination_info.borrow_mut_lamports_unchecked() = destination_starting_lamports
69+
.checked_add(transfer_amount)
70+
.ok_or(TokenError::Overflow)?;
71+
}
72+
73+
Ok(())
74+
}

0 commit comments

Comments
 (0)