Skip to content

Commit b9cdf7c

Browse files
committed
Make amount optional
1 parent 322a1b5 commit b9cdf7c

File tree

3 files changed

+116
-31
lines changed

3 files changed

+116
-31
lines changed

p-interface/src/instruction.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,9 @@ pub enum TokenInstruction {
510510
///
511511
/// Data expected by this instruction:
512512
///
513-
/// - `u64` The amount of lamports to transfer.
513+
/// - Option<`u64`> The amount of lamports to transfer. When an amount
514+
/// is not specified, the entire balance of the source account will be
515+
/// transferred.
514516
UnwrapLamports = 45,
515517

516518
/// Executes a batch of instructions. The instructions to be executed are

p-token/src/processor/unwrap_lamports.rs

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
11
use {
22
super::validate_owner,
3-
crate::processor::{check_account_owner, unpack_amount},
3+
crate::processor::{check_account_owner, U64_BYTES},
44
pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult},
55
pinocchio_token_interface::{
66
error::TokenError,
7+
likely,
78
state::{account::Account, load_mut},
89
},
910
};
1011

1112
#[allow(clippy::arithmetic_side_effects)]
1213
pub fn process_unwrap_lamports(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
13-
// Amount being unwrapped.
14-
let amount = unpack_amount(instruction_data)?;
14+
// instruction data: expected u8 (1) + optional u64 (8)
15+
let [has_amount, maybe_amount @ ..] = instruction_data else {
16+
return Err(TokenError::InvalidInstruction.into());
17+
};
18+
19+
let maybe_amount = if likely(*has_amount == 0) {
20+
None
21+
} else if maybe_amount.len() >= 8 {
22+
// SAFETY: The slice is guaranteed to be at least 8 bytes long.
23+
Some(u64::from_le_bytes(unsafe {
24+
*(maybe_amount.as_ptr() as *const [u8; U64_BYTES])
25+
}))
26+
} else {
27+
return Err(TokenError::InvalidInstruction.into());
28+
};
1529

1630
let [source_account_info, destination_account_info, authority_info, remaining @ ..] = accounts
1731
else {
@@ -31,10 +45,19 @@ pub fn process_unwrap_lamports(accounts: &[AccountInfo], instruction_data: &[u8]
3145
// a multisig.
3246
unsafe { validate_owner(&source_account.owner, authority_info, remaining)? };
3347

34-
let remaining_amount = source_account
35-
.amount()
36-
.checked_sub(amount)
37-
.ok_or(TokenError::InsufficientFunds)?;
48+
// If we have an amount, we need to validate whether there are enough lamports
49+
// to unwrap or not; otherwise we just use the full amount.
50+
let (amount, remaining_amount) = if let Some(amount) = maybe_amount {
51+
(
52+
amount,
53+
source_account
54+
.amount()
55+
.checked_sub(amount)
56+
.ok_or(TokenError::InsufficientFunds)?,
57+
)
58+
} else {
59+
(source_account.amount(), 0)
60+
};
3861

3962
// Comparing whether the AccountInfo's "point" to the same account or
4063
// not - this is a faster comparison since it just checks the internal
@@ -44,7 +67,7 @@ pub fn process_unwrap_lamports(accounts: &[AccountInfo], instruction_data: &[u8]
4467
if self_transfer || amount == 0 {
4568
// Validates the token account owner since we are not writing
4669
// to the account.
47-
check_account_owner(source_account_info)?;
70+
check_account_owner(source_account_info)
4871
} else {
4972
source_account.set_amount(remaining_amount);
5073

@@ -55,13 +78,12 @@ pub fn process_unwrap_lamports(accounts: &[AccountInfo], instruction_data: &[u8]
5578
*source_lamports -= amount;
5679

5780
// SAFETY: single mutable borrow to `destination_account_info` lamports; the
58-
// account is already validated to be different from
59-
// `source_account_info`.
81+
// account is already validated to be different from `source_account_info`.
6082
let destination_lamports =
6183
unsafe { destination_account_info.borrow_mut_lamports_unchecked() };
6284
// Note: The total lamports supply is bound to `u64::MAX`.
6385
*destination_lamports += amount;
64-
}
6586

66-
Ok(())
87+
Ok(())
88+
}
6789
}

p-token/tests/unwrap_lamports.rs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,23 @@ fn create_token_account(
5353
}
5454
}
5555

56+
/// Creates a Mollusk instance with the default feature set, excluding the
57+
/// `bpf_account_data_direct_mapping` feature.
58+
fn mollusk() -> Mollusk {
59+
let mut mollusk = Mollusk::default();
60+
mollusk.add_program(
61+
&TOKEN_PROGRAM_ID,
62+
"pinocchio_token_program",
63+
&bpf_loader_upgradeable::id(),
64+
);
65+
mollusk
66+
}
67+
5668
fn unwrap_lamports_instruction(
5769
source: &Pubkey,
5870
destination: &Pubkey,
5971
authority: &Pubkey,
60-
amount: u64,
72+
amount: Option<u64>,
6173
) -> Result<Instruction, ProgramError> {
6274
let accounts = vec![
6375
AccountMeta::new(*source, false),
@@ -67,7 +79,13 @@ fn unwrap_lamports_instruction(
6779

6880
// Start with the batch discriminator
6981
let mut data: Vec<u8> = vec![TokenInstruction::UnwrapLamports as u8];
70-
data.extend_from_slice(&amount.to_le_bytes());
82+
83+
if let Some(amount) = amount {
84+
data.push(1);
85+
data.extend_from_slice(&amount.to_le_bytes());
86+
} else {
87+
data.push(0);
88+
}
7189

7290
Ok(Instruction {
7391
program_id: spl_token::ID,
@@ -76,20 +94,63 @@ fn unwrap_lamports_instruction(
7694
})
7795
}
7896

79-
/// Creates a Mollusk instance with the default feature set, excluding the
80-
/// `bpf_account_data_direct_mapping` feature.
81-
fn mollusk() -> Mollusk {
82-
let mut mollusk = Mollusk::default();
83-
mollusk.add_program(
97+
#[tokio::test]
98+
async fn unwrap_lamports() {
99+
let native_mint = Pubkey::new_from_array(native_mint::ID);
100+
let authority_key = Pubkey::new_unique();
101+
let destination_account_key = Pubkey::new_unique();
102+
103+
// native account:
104+
// - amount: 2_000_000_000
105+
let source_account_key = Pubkey::new_unique();
106+
let source_account = create_token_account(
107+
&native_mint,
108+
&authority_key,
109+
true,
110+
2_000_000_000,
84111
&TOKEN_PROGRAM_ID,
85-
"pinocchio_token_program",
86-
&bpf_loader_upgradeable::id(),
87112
);
88-
mollusk
113+
114+
let instruction = unwrap_lamports_instruction(
115+
&source_account_key,
116+
&destination_account_key,
117+
&authority_key,
118+
None,
119+
)
120+
.unwrap();
121+
122+
// It should succeed to unwrap 2_000_000_000 lamports.
123+
124+
let result = mollusk().process_and_validate_instruction(
125+
&instruction,
126+
&[
127+
(source_account_key, source_account),
128+
(destination_account_key, Account::default()),
129+
(authority_key, Account::default()),
130+
],
131+
&[
132+
Check::success(),
133+
Check::account(&destination_account_key)
134+
.lamports(2_000_000_000)
135+
.build(),
136+
Check::account(&source_account_key)
137+
.lamports(Rent::default().minimum_balance(size_of::<TokenAccount>()))
138+
.build(),
139+
],
140+
);
141+
142+
// And the remaining amount must be 0.
143+
144+
result.resulting_accounts.iter().for_each(|(key, account)| {
145+
if *key == source_account_key {
146+
let token_account = spl_token::state::Account::unpack(&account.data).unwrap();
147+
assert_eq!(token_account.amount, 0);
148+
}
149+
});
89150
}
90151

91152
#[tokio::test]
92-
async fn unwrap_lamports() {
153+
async fn unwrap_lamports_with_amount() {
93154
let native_mint = Pubkey::new_from_array(native_mint::ID);
94155
let authority_key = Pubkey::new_unique();
95156
let destination_account_key = Pubkey::new_unique();
@@ -109,7 +170,7 @@ async fn unwrap_lamports() {
109170
&source_account_key,
110171
&destination_account_key,
111172
&authority_key,
112-
2_000_000_000,
173+
Some(2_000_000_000),
113174
)
114175
.unwrap();
115176

@@ -164,7 +225,7 @@ async fn fail_unwrap_lamports_with_insufficient_funds() {
164225
&source_account_key,
165226
&destination_account_key,
166227
&authority_key,
167-
2_000_000_000,
228+
Some(2_000_000_000),
168229
)
169230
.unwrap();
170231

@@ -205,7 +266,7 @@ async fn unwrap_lamports_with_parial_amount() {
205266
&source_account_key,
206267
&destination_account_key,
207268
&authority_key,
208-
1_000_000_000,
269+
Some(1_000_000_000),
209270
)
210271
.unwrap();
211272

@@ -263,7 +324,7 @@ async fn fail_unwrap_lamports_with_invalid_authority() {
263324
&source_account_key,
264325
&destination_account_key,
265326
&fake_authority_key, // <-- wrong authority
266-
2_000_000_000,
327+
Some(2_000_000_000),
267328
)
268329
.unwrap();
269330

@@ -305,7 +366,7 @@ async fn fail_unwrap_lamports_with_non_native_account() {
305366
&source_account_key,
306367
&destination_account_key,
307368
&authority_key,
308-
1_000_000_000,
369+
Some(1_000_000_000),
309370
)
310371
.unwrap();
311372

@@ -353,7 +414,7 @@ async fn unwrap_lamports_with_self_transfer() {
353414
&source_account_key,
354415
&source_account_key, // <-- destination same as source
355416
&authority_key,
356-
1_000_000_000,
417+
Some(1_000_000_000),
357418
)
358419
.unwrap();
359420

@@ -407,7 +468,7 @@ async fn fail_unwrap_lamports_with_invalid_native_account() {
407468
&source_account_key,
408469
&destination_account_key,
409470
&authority_key,
410-
1_000_000_000,
471+
Some(1_000_000_000),
411472
)
412473
.unwrap();
413474

0 commit comments

Comments
 (0)