Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions pinocchio/program/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ was built against tag
[`program@3.5.0`](https://github.com/solana-program/token/releases/tag/program%40v3.5.0),
commit
[`4d5ff3015ae5ad3316f2d2efdde6ab9f7a50716c`](https://github.com/solana-program/token/tree/4d5ff3015ae5ad3316f2d2efdde6ab9f7a50716c).

One fixture was removed due to a change in the `sync-native` behavior to allow
for the `amount` to decrease, due to potential changes in the `Rent` sysvar.
This fixture is still available at `fuzz/blob-old/`.
17 changes: 11 additions & 6 deletions pinocchio/program/src/processor/sync_native.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use {
super::check_account_owner,
pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult},
pinocchio::{
account_info::AccountInfo,
program_error::ProgramError,
sysvars::{rent::Rent, Sysvar},
ProgramResult,
},
pinocchio_token_interface::{
error::TokenError,
state::{account::Account, load_mut},
Expand All @@ -13,20 +18,20 @@ pub fn process_sync_native(accounts: &[AccountInfo]) -> ProgramResult {

check_account_owner(native_account_info)?;

let rent = Rent::get()?;
let rent_exempt_reserve = rent.minimum_balance(native_account_info.data_len());

// SAFETY: single mutable borrow to `native_account_info` account data and
// `load_mut` validates that the account is initialized.
let native_account =
unsafe { load_mut::<Account>(native_account_info.borrow_mut_data_unchecked())? };

if let Option::Some(rent_exempt_reserve) = native_account.native_amount() {
if native_account.is_native() {
let new_amount = native_account_info
.lamports()
.checked_sub(rent_exempt_reserve)
.ok_or(TokenError::Overflow)?;

if new_amount < native_account.amount() {
return Err(TokenError::InvalidState.into());
}
native_account.set_native_amount(rent_exempt_reserve);
native_account.set_amount(new_amount);
} else {
return Err(TokenError::NonNativeNotSupported.into());
Expand Down
49 changes: 49 additions & 0 deletions pinocchio/program/tests/sync_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use {
native_mint,
state::{
account::Account as TokenAccount, account_state::AccountState, load_mut_unchecked,
load_unchecked,
},
},
solana_account::Account,
Expand Down Expand Up @@ -92,3 +93,51 @@ async fn sync_native() {
}
});
}

#[test]
fn sync_native_with_rent_change() {
let native_mint = Pubkey::new_from_array(native_mint::ID);
let authority_key = Pubkey::new_unique();

// native account
// - amount: 1_000_000_000
// - lamports: 2_000_000_000
let source_account_key = Pubkey::new_unique();
let lamports = 2_000_000_000;
let source_account = create_token_account(
&native_mint,
&authority_key,
true,
lamports,
&TOKEN_PROGRAM_ID,
);

let instruction =
spl_token_interface::instruction::sync_native(&TOKEN_PROGRAM_ID, &source_account_key)
.unwrap();

// Executes the sync_native instruction.

let mut rent = Rent::default();
rent.lamports_per_byte_year *= 2;

let space = size_of::<TokenAccount>();
let new_rent_exempt_reserve = rent.minimum_balance(space);
let old_rent_exempt_reserve = source_account.lamports - lamports;
let rent_difference = new_rent_exempt_reserve - old_rent_exempt_reserve;

let mut test_mollusk = mollusk();
test_mollusk.sysvars.rent = rent;
let result = test_mollusk.process_and_validate_instruction_chain(
&[(&instruction, &[Check::success()])],
&[(source_account_key, source_account)],
);

result.resulting_accounts.iter().for_each(|(key, account)| {
if *key == source_account_key {
let token_account = unsafe { load_unchecked::<TokenAccount>(&account.data).unwrap() };
assert_eq!(token_account.amount(), lamports - rent_difference);
assert_eq!(token_account.native_amount(), Some(new_rent_exempt_reserve));
}
});
}
9 changes: 5 additions & 4 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,16 +759,17 @@ impl Processor {
let native_account_info = next_account_info(account_info_iter)?;
Self::check_account_owner(program_id, native_account_info)?;

let rent = Rent::get()?;
let rent_exempt_reserve = rent.minimum_balance(native_account_info.data_len());

let mut native_account = Account::unpack(&native_account_info.data.borrow())?;

if let COption::Some(rent_exempt_reserve) = native_account.is_native {
if native_account.is_native.is_some() {
let new_amount = native_account_info
.lamports()
.checked_sub(rent_exempt_reserve)
.ok_or(TokenError::Overflow)?;
if new_amount < native_account.amount {
return Err(TokenError::InvalidState.into());
}
native_account.is_native = COption::Some(rent_exempt_reserve);
native_account.amount = new_amount;
} else {
return Err(TokenError::NonNativeNotSupported.into());
Expand Down
23 changes: 14 additions & 9 deletions program/tests/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5986,15 +5986,20 @@ fn test_sync_native() {
// reduce sol
native_account.lamports -= 1;

// fail sync
assert_eq!(
Err(TokenError::InvalidState.into()),
do_process_instruction(
sync_native(&program_id, &native_account_key,).unwrap(),
vec![&mut native_account],
&[Check::err(TokenError::InvalidState.into())],
)
);
// succeed sync with reduced value
do_process_instruction(
sync_native(&program_id, &native_account_key).unwrap(),
vec![&mut native_account],
&[
Check::success(),
Check::account(&native_account_key)
.data_slice(64, &(new_lamports - 1).to_le_bytes())
.build(),
],
)
.unwrap();
let account = Account::unpack_unchecked(&native_account.data).unwrap();
assert_eq!(account.amount, new_lamports - 1);
}

#[test]
Expand Down