Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit dc4066e

Browse files
committed
Add safety comments (#8)
* Remove spl-token test cases * Update CU values * Add safety comments * Use git dependencies
1 parent f4b8d7e commit dc4066e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+225
-143
lines changed

Cargo.lock

Lines changed: 5 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ license = "Apache-2.0"
1515
repository = "https://github.com/febo/token"
1616

1717
[workspace.dependencies]
18-
pinocchio = "0.7.0"
19-
pinocchio-log = "0.3.0"
20-
pinocchio-pubkey = "0.2.2"
18+
pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }
19+
pinocchio-log = { version = "0.3", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }
20+
pinocchio-pubkey = { version = "0.2", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" }

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,30 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL
3434
| Instruction | Completed | CU (`p-token`) | CU (`spl-token`) |
3535
|----------------------------|-----------|----------------|------------------|
3636
| `InitializeMint` || 100 | 2967 |
37-
| `InitializeAccount` || 170 | 4527 |
38-
| `InitializeMultisig` || 190 | 2973 |
39-
| `Transfer` || 153 | 4645 |
40-
| `Approve` || 124 | 2904 |
37+
| `InitializeAccount` || 185 | 4527 |
38+
| `InitializeMultisig` || 204 | 2973 |
39+
| `Transfer` || 155 | 4645 |
40+
| `Approve` || 122 | 2904 |
4141
| `Revoke` || 97 | 2677 |
42-
| `SetAuthority` || 126 | 3167 |
43-
| `MintTo` || 154 | 4538 |
42+
| `SetAuthority` || 127 | 3167 |
43+
| `MintTo` || 155 | 4538 |
4444
| `Burn` || 168 | 4753 |
45-
| `CloseAccount` || 147 | 2916 |
45+
| `CloseAccount` || 154 | 2916 |
4646
| `FreezeAccount` || 136 | 4265 |
4747
| `ThawAccount` || 136 | 4267 |
48-
| `TransferChecked` || 206 | 6201 |
48+
| `TransferChecked` || 204 | 6201 |
4949
| `ApproveChecked` || 162 | 4459 |
5050
| `MintToChecked` || 164 | 4546 |
51-
| `BurnChecked` || 170 | 4755 |
52-
| `InitializeAccount2` || 150 | 4388 |
51+
| `BurnChecked` || 169 | 4755 |
52+
| `InitializeAccount2` || 164 | 4388 |
5353
| `SyncNative` || | |
5454
| `InitializeAccount3` || 272 | 4240 |
5555
| `InitializeMultisig2` || 319 | 2826 |
5656
| `InitializeMint2` || 234 | 2827 |
5757
| `GetAccountDataSize` || | |
5858
| `InitializeImmutableOwner` || | |
59-
| `AmountToUiAmount` || 483 | 2501 |
60-
| `UiAmountToAmount` || 873 | 3161 |
59+
| `AmountToUiAmount` || 503 | 2501 |
60+
| `UiAmountToAmount` || 875 | 3161 |
6161

6262
> Tests were run using Solana `v2.1.0`.
6363

program/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ test-sbf = []
2020
[dependencies]
2121
pinocchio = { workspace = true }
2222
pinocchio-log = { workspace = true }
23-
pinocchio-pubkey = { workspace = true }
2423
token-interface = { version = "^0", path = "../interface" }
2524

2625
[dev-dependencies]

program/src/processor/amount_to_ui_amount.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pub fn process_amount_to_ui_amount(
2323

2424
let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
2525
check_account_owner(mint_info)?;
26-
// SAFETY: there is a single borrow to the `Mint` account.
26+
// SAFETY: single immutable borrow to `mint_info` account data and
27+
// `load` validates that the mint is initialized.
2728
let mint = unsafe {
2829
load::<Mint>(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)?
2930
};

program/src/processor/burn_checked.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,20 @@ use super::shared;
44

55
#[inline(always)]
66
pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
7-
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
8-
let amount = u64::from_le_bytes(
9-
amount
10-
.try_into()
11-
.map_err(|_error| ProgramError::InvalidInstructionData)?,
12-
);
7+
// expected u64 (8) + u8 (1)
8+
let (amount, decimals) = if instruction_data.len() == 9 {
9+
let (amount, decimals) = instruction_data.split_at(core::mem::size_of::<u64>());
10+
(
11+
u64::from_le_bytes(
12+
amount
13+
.try_into()
14+
.map_err(|_error| ProgramError::InvalidInstructionData)?,
15+
),
16+
decimals.first(),
17+
)
18+
} else {
19+
return Err(ProgramError::InvalidInstructionData);
20+
};
1321

14-
shared::burn::process_burn(
15-
accounts,
16-
amount,
17-
Some(
18-
*decimals
19-
.first()
20-
.ok_or(ProgramError::InvalidInstructionData)?,
21-
),
22-
)
22+
shared::burn::process_burn(accounts, amount, decimals.copied())
2323
}

program/src/processor/close_account.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ use pinocchio::{
33
};
44
use token_interface::{
55
error::TokenError,
6-
state::{account::Account, load_mut},
6+
state::{account::Account, load},
77
};
88

99
use super::validate_owner;
1010

11-
/// Incinerator address.
12-
const INCINERATOR_ID: Pubkey =
13-
pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111");
11+
/// Incinerator (`1nc1nerator11111111111111111111111111111111`) address.
12+
const INCINERATOR_ID: Pubkey = [
13+
0, 51, 144, 114, 141, 52, 17, 96, 121, 189, 201, 17, 191, 255, 0, 219, 212, 77, 46, 205, 204,
14+
247, 156, 166, 225, 0, 56, 225, 0, 0, 0, 0,
15+
];
1416

1517
#[inline(always)]
1618
pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult {
@@ -24,26 +26,30 @@ pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult {
2426
// raw pointer.
2527
if source_account_info == destination_account_info {
2628
return Err(ProgramError::InvalidAccountData);
27-
}
28-
29-
let source_account =
30-
unsafe { load_mut::<Account>(source_account_info.borrow_mut_data_unchecked())? };
31-
32-
if !source_account.is_native() && source_account.amount() != 0 {
33-
return Err(TokenError::NonNativeHasBalance.into());
34-
}
35-
36-
let authority = source_account
37-
.close_authority()
38-
.unwrap_or(&source_account.owner);
39-
40-
if !source_account.is_owned_by_system_program_or_incinerator() {
41-
validate_owner(authority, authority_info, remaining)?;
42-
} else if destination_account_info.key() != &INCINERATOR_ID {
43-
return Err(ProgramError::InvalidAccountData);
29+
} else {
30+
// SAFETY: scoped immutable borrow to `source_account_info` account data and
31+
// `load` validates that the account is initialized.
32+
let source_account =
33+
unsafe { load::<Account>(source_account_info.borrow_data_unchecked())? };
34+
35+
if !source_account.is_native() && source_account.amount() != 0 {
36+
return Err(TokenError::NonNativeHasBalance.into());
37+
}
38+
39+
let authority = source_account
40+
.close_authority()
41+
.unwrap_or(&source_account.owner);
42+
43+
if !source_account.is_owned_by_system_program_or_incinerator() {
44+
validate_owner(authority, authority_info, remaining)?;
45+
} else if destination_account_info.key() != &INCINERATOR_ID {
46+
return Err(ProgramError::InvalidAccountData);
47+
}
4448
}
4549

4650
let destination_starting_lamports = destination_account_info.lamports();
51+
// SAFETY: single mutable borrow to `destination_account_info` lamports and
52+
// there are no "active" borrows of `source_account_info` account data.
4753
unsafe {
4854
// Moves the lamports to the destination account.
4955
*destination_account_info.borrow_mut_lamports_unchecked() = destination_starting_lamports

program/src/processor/get_account_data_size.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult
1717
// Make sure the mint is valid.
1818
check_account_owner(mint_info)?;
1919

20+
// SAFETY: single immutable borrow to `mint_info` account data and
21+
// `load` validates that the mint is initialized.
2022
let _ = unsafe {
2123
load::<Mint>(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)?
2224
};
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
program_error::ProgramError,
4+
pubkey::{Pubkey, PUBKEY_BYTES},
5+
ProgramResult,
6+
};
27

38
use super::shared;
49

@@ -7,6 +12,13 @@ pub fn process_initialize_account2(
712
accounts: &[AccountInfo],
813
instruction_data: &[u8],
914
) -> ProgramResult {
10-
let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) };
15+
// SAFETY: validate `instruction_data` length.
16+
let owner = unsafe {
17+
if instruction_data.len() != PUBKEY_BYTES {
18+
return Err(ProgramError::InvalidInstructionData);
19+
} else {
20+
&*(instruction_data.as_ptr() as *const Pubkey)
21+
}
22+
};
1123
shared::initialize_account::process_initialize_account(accounts, Some(owner), true)
1224
}
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
1+
use pinocchio::{
2+
account_info::AccountInfo,
3+
program_error::ProgramError,
4+
pubkey::{Pubkey, PUBKEY_BYTES},
5+
ProgramResult,
6+
};
27

38
use super::shared;
49

@@ -7,6 +12,13 @@ pub fn process_initialize_account3(
712
accounts: &[AccountInfo],
813
instruction_data: &[u8],
914
) -> ProgramResult {
10-
let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) };
15+
// SAFETY: validate `instruction_data` length.
16+
let owner = unsafe {
17+
if instruction_data.len() != PUBKEY_BYTES {
18+
return Err(ProgramError::InvalidInstructionData);
19+
} else {
20+
&*(instruction_data.as_ptr() as *const Pubkey)
21+
}
22+
};
1123
shared::initialize_account::process_initialize_account(accounts, Some(owner), false)
1224
}

0 commit comments

Comments
 (0)