Skip to content

Commit ae4630e

Browse files
authored
p-token: Add unwrap_lamports instruction (#87)
* Add processor * Add tests * Add instruction * Fix formatting * Fix discriminator value * Add check for unwrap lamports * Update discriminator on tests * Make amount optional * Address review comments * Review comments * Update import
1 parent 2769f3b commit ae4630e

File tree

6 files changed

+744
-1
lines changed

6 files changed

+744
-1
lines changed

p-interface/src/instruction.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,23 @@ pub enum TokenInstruction {
498498
/// 3. `..+M` `[signer]` M signer accounts.
499499
WithdrawExcessLamports = 38,
500500

501+
/// Transfer lamports from a native SOL account to a destination account.
502+
///
503+
/// This is useful to unwrap lamports from a wrapped SOL account.
504+
///
505+
/// Accounts expected by this instruction:
506+
///
507+
/// 0. `[writable]` The source account.
508+
/// 1. `[writable]` The destination account.
509+
/// 2. `[signer]` The source account's owner/delegate.
510+
///
511+
/// Data expected by this instruction:
512+
///
513+
/// - `Option<u64>` The amount of lamports to transfer. When an amount is
514+
/// not specified, the entire balance of the source account will be
515+
/// transferred.
516+
UnwrapLamports = 45,
517+
501518
/// Executes a batch of instructions. The instructions to be executed are
502519
/// specified in sequence on the instruction data. Each instruction
503520
/// provides:

p-token/src/entrypoint.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,13 @@ fn inner_process_remaining_instruction(
486486

487487
process_withdraw_excess_lamports(accounts)
488488
}
489+
// 45 - UnwrapLamports
490+
45 => {
491+
#[cfg(feature = "logging")]
492+
pinocchio::msg!("Instruction: UnwrapLamports");
493+
494+
process_unwrap_lamports(accounts, instruction_data)
495+
}
489496
_ => Err(TokenError::InvalidInstruction.into()),
490497
}
491498
}

p-token/src/processor/batch.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ pub fn process_batch(mut accounts: &[AccountInfo], mut instruction_data: &[u8])
8484
// 13 - ApproveChecked
8585
// 22 - InitializeImmutableOwner
8686
// 38 - WithdrawExcessLamports
87-
4..=13 | 22 | 38 => {
87+
// 45 - UnwrapLamports
88+
4..=13 | 22 | 38 | 45 => {
8889
let [a0, ..] = ix_accounts else {
8990
return Err(ProgramError::NotEnoughAccountKeys);
9091
};

p-token/src/processor/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub mod thaw_account;
4141
pub mod transfer;
4242
pub mod transfer_checked;
4343
pub mod ui_amount_to_amount;
44+
pub mod unwrap_lamports;
4445
pub mod withdraw_excess_lamports;
4546
// Shared processors.
4647
pub mod shared;
@@ -61,6 +62,7 @@ pub use {
6162
set_authority::process_set_authority, sync_native::process_sync_native,
6263
thaw_account::process_thaw_account, transfer::process_transfer,
6364
transfer_checked::process_transfer_checked, ui_amount_to_amount::process_ui_amount_to_amount,
65+
unwrap_lamports::process_unwrap_lamports,
6466
withdraw_excess_lamports::process_withdraw_excess_lamports,
6567
};
6668

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use {
2+
super::validate_owner,
3+
crate::processor::{check_account_owner, unpack_amount},
4+
pinocchio::{
5+
account_info::AccountInfo, hint::likely, program_error::ProgramError, ProgramResult,
6+
},
7+
pinocchio_token_interface::{
8+
error::TokenError,
9+
state::{account::Account, load_mut},
10+
},
11+
};
12+
13+
#[allow(clippy::arithmetic_side_effects)]
14+
pub fn process_unwrap_lamports(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
15+
// instruction data: expected u8 (1) + optional u64 (8)
16+
let [has_amount, maybe_amount @ ..] = instruction_data else {
17+
return Err(TokenError::InvalidInstruction.into());
18+
};
19+
20+
let maybe_amount = if likely(*has_amount == 0) {
21+
None
22+
} else if *has_amount == 1 {
23+
Some(unpack_amount(maybe_amount)?)
24+
} else {
25+
return Err(TokenError::InvalidInstruction.into());
26+
};
27+
28+
let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts
29+
else {
30+
return Err(ProgramError::NotEnoughAccountKeys);
31+
};
32+
33+
// SAFETY: single immutable borrow to `source_account_info` account data
34+
let source_account =
35+
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
36+
37+
if !source_account.is_native() {
38+
return Err(TokenError::NonNativeNotSupported.into());
39+
}
40+
41+
// SAFETY: `authority_info` is not currently borrowed; in the case
42+
// `authority_info` is the same as `source_account_info`, then it cannot be
43+
// a multisig.
44+
unsafe { validate_owner(&source_account.owner, authority_info, remaining)? };
45+
46+
// If we have an amount, we need to validate whether there are enough lamports
47+
// to unwrap or not; otherwise we just use the full amount.
48+
let (amount, remaining_amount) = if let Some(amount) = maybe_amount {
49+
(
50+
amount,
51+
source_account
52+
.amount()
53+
.checked_sub(amount)
54+
.ok_or(TokenError::InsufficientFunds)?,
55+
)
56+
} else {
57+
(source_account.amount(), 0)
58+
};
59+
60+
// Comparing whether the AccountInfo's "point" to the same account or
61+
// not - this is a faster comparison since it just checks the internal
62+
// raw pointer.
63+
let self_transfer = source_account_info == destination_account_info;
64+
65+
if self_transfer || amount == 0 {
66+
// Validates the token account owner since we are not writing
67+
// to the account.
68+
check_account_owner(source_account_info)
69+
} else {
70+
source_account.set_amount(remaining_amount);
71+
72+
// SAFETY: single mutable borrow to `source_account_info` lamports.
73+
let source_lamports = unsafe { source_account_info.borrow_mut_lamports_unchecked() };
74+
// Note: The amount of a source token account is already validated and the
75+
// `lamports` on the account is always greater than `amount`.
76+
*source_lamports -= amount;
77+
78+
// SAFETY: single mutable borrow to `destination_account_info` lamports; the
79+
// account is already validated to be different from `source_account_info`.
80+
let destination_lamports =
81+
unsafe { destination_account_info.borrow_mut_lamports_unchecked() };
82+
// Note: The total lamports supply is bound to `u64::MAX`.
83+
*destination_lamports += amount;
84+
85+
Ok(())
86+
}
87+
}

0 commit comments

Comments
 (0)