Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d767899
fix(spl): align test_process_mint_to_checked's assumption to p-token
Stevengre Jan 8, 2026
f14ffb3
make the diff less
Stevengre Jan 9, 2026
429afcd
npnm p-token:format
Stevengre Jan 9, 2026
b31a7aa
fix error in macro_rule
Stevengre Jan 9, 2026
c606720
fix pnpm p-token:lint
Stevengre Jan 9, 2026
21fb913
pnpm p-token:format && pnpm p-token:lint
Stevengre Jan 9, 2026
46ad9c5
make spl-token and p-token use the same harness for process_mint_to_c…
Stevengre Jan 9, 2026
7d25861
use shared specs for both spl-token and p-token
Stevengre Jan 12, 2026
23d8a79
refactor the structure of specs. making the difference between spl-to…
Stevengre Jan 12, 2026
febff99
optimize the preludes
Stevengre Jan 12, 2026
8f5ae40
solve the compile issues.
Stevengre Jan 13, 2026
ce456e3
provide a README file for specs
Stevengre Jan 13, 2026
ed31dd6
review all the diff between p-token's here and p-token's proofs branch
Stevengre Jan 13, 2026
3bf4664
correct the specs for close_account
Stevengre Jan 14, 2026
947491d
avoid to use !(... == ...)
Stevengre Jan 14, 2026
b799a8b
p-token:format&lint
Stevengre Jan 14, 2026
a7bbb92
fix the spl-token compiling issues
Stevengre Jan 14, 2026
9f79bdb
fix the partial eq for close account and transfer_checked
Stevengre Jan 15, 2026
e8b37bc
README cleanup
dkcumming Jan 16, 2026
fcd04b2
Added some documentation to preludes
dkcumming Jan 16, 2026
54dc85c
added comments to for validate owner check
dkcumming Jan 16, 2026
1094d24
Replacing some inline comments
dkcumming Jan 16, 2026
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,286 changes: 56 additions & 4,230 deletions p-token/src/entrypoint-runtime-verification.rs

Large diffs are not rendered by default.

4,644 changes: 141 additions & 4,503 deletions program/src/entrypoint-runtime-verification.rs

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions specs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Specs - Runtime Verification Harness

This directory contains shared runtime verification specifications for Solana token programs. The specs provide a common harness for verifying both
**p-token** (pinocchio-based) and **spl-token** (solana-based) implementations. The differences between each will be handled by macros,
allowing for small surface area of change that is easily reviewable.

## Architecture

```
specs/
├── prelude-p-token.rs # P-token specific macros and helpers
├── prelude-spl-token.rs # SPL-token specific macros and wrappers
└── shared/ # Common spec files (44 files)
├── inner_test_validate_owner.rs
└── test_process_*.rs
```

### How It Works

Both token implementations include the shared specs via the `include!` macro:

**p-token** (`p-token/src/entrypoint-runtime-verification.rs`):
```rust
include!("../../specs/prelude-p-token.rs");
include!("../../specs/shared/inner_test_validate_owner.rs");
include!("../../specs/shared/test_process_transfer.rs");
// ... more specs
```

**spl-token** (`program/src/entrypoint-runtime-verification.rs`):
```rust
include!("../../specs/prelude-spl-token.rs");
include!("../../specs/shared/inner_test_validate_owner.rs");
include!("../../specs/shared/test_process_transfer.rs");
// ... more specs
```

## API Abstraction

The preludes define macros that abstract API differences between implementations:

### AccountInfo Access Macros

| Macro | p-token (methods) | spl-token (fields) |
|-------|-------------------|-------------------|
| `key!($acc)` | `$acc.key()` | `$acc.key` |
| `owner!($acc)` | `$acc.owner()` | `$acc.owner` |
| `is_signer!($acc)` | `$acc.is_signer()` | `$acc.is_signer` |

### Cheatcode Macros

| Macro | p-token | spl-token |
|-------|---------|-----------|
| `cheatcode_account!($acc)` | `cheatcode_is_account($acc)` | `cheatcode_is_spl_account($acc)` |
| `cheatcode_mint!($acc)` | `cheatcode_is_mint($acc)` | `cheatcode_is_spl_mint($acc)` |

### Process Call Macros

| Macro | p-token | spl-token |
|-------|---------|-----------|
| `call_process_transfer!(...)` | Direct function call | `Processor::process_transfer(...)` with parsing |
| `call_process_mint_to!(...)` | Direct function call | `Processor::process_mint_to(...)` with parsing |

### ID Aliases

| Alias | p-token | spl-token |
|-------|---------|-----------|
| `PROGRAM_ID` | `pinocchio_token_interface::program::ID` | `crate::ID` |
| `RENT_ID` | `pinocchio::sysvars::rent::RENT_ID` | `solana_sysvar::rent::ID` |
| `NATIVE_MINT_ID` | `pinocchio_token_interface::native_mint::ID` | `spl_token_interface::native_mint::ID` |

## Spec File Structure

Each spec file follows a standard structure:

```rust
/// accounts[0] // Source Info
/// accounts[1] // Destination Info
/// accounts[2] // Authority Info
/// instruction_data[0..8] // Little Endian Bytes of u64 amount
#[inline(never)]
fn test_process_transfer(
accounts: &[AccountInfo; 3],
instruction_data: &[u8; 8],
) -> ProgramResult {
// Cheatcodes for symbolic execution setup
cheatcode_account!(&accounts[0]);
cheatcode_account!(&accounts[1]);

//-Initial State-----------------------------------------------------------
let src_old = get_account(&accounts[0]);
// ... capture initial state

//-Process Instruction-----------------------------------------------------
let result = call_process_transfer!(accounts, instruction_data);

//-Assert Postconditions---------------------------------------------------
if /* error condition */ {
assert_eq!(result, Err(ProgramError::...));
return result;
}
// ... more conditions

assert!(result.is_ok());
// ... verify state changes

result
}
```
263 changes: 263 additions & 0 deletions specs/prelude-p-token.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
// =============================================================================
// API Alignment Macros
// =============================================================================
//
// Macros for consistency between SPL and P Token shared specifications

// --- AccountInfo Macros ---

macro_rules! key {
($acc:expr) => {
$acc.key()
};
}
macro_rules! owner {
($acc:expr) => {
$acc.owner()
};
}
macro_rules! is_signer {
($acc:expr) => {
$acc.is_signer()
};
}
macro_rules! same_account {
($acc1:expr, $acc2:expr) => {
$acc1 == $acc2
};
}

// --- Pubkey Macros ---

// For reference types - in p-token, no dereference needed
macro_rules! assert_pubkey_from_slice {
($actual:expr, $slice:expr) => {{
assert_eq!($actual, $slice);
}};
}

// For value types - same as above for p-token
macro_rules! assert_pubkey_from_slice_val {
($actual:expr, $slice:expr) => {{
assert_eq!($actual, $slice);
}};
}

// =============================================================================
// Cheatcodes
// =============================================================================

#[inline(never)]
fn cheatcode_is_account(_: &AccountInfo) {}
#[inline(never)]
fn cheatcode_is_mint(_: &AccountInfo) {}
#[inline(never)]
fn cheatcode_is_rent(_: &AccountInfo) {}
#[inline(never)]
fn cheatcode_is_multisig(_: &AccountInfo) {}

/// Cheatcode macros to abstract naming differences.
macro_rules! cheatcode_account {
($acc:expr) => {
cheatcode_is_account($acc)
};
}
macro_rules! cheatcode_mint {
($acc:expr) => {
cheatcode_is_mint($acc)
};
}
macro_rules! cheatcode_rent {
($acc:expr) => {
cheatcode_is_rent($acc)
};
}
macro_rules! cheatcode_multisig {
($acc:expr) => {
cheatcode_is_multisig($acc)
};
}

// =============================================================================
// Helper functions
// =============================================================================

fn get_account(account_info: &AccountInfo) -> &Account {
unsafe {
let byte_ptr = account_info.borrow_data_unchecked();
let acc_ref = load_unchecked::<Account>(byte_ptr).unwrap();
acc_ref
}
}

fn get_mint(account_info: &AccountInfo) -> &Mint {
unsafe {
let byte_ptr = account_info.borrow_data_unchecked();
let acc_ref = load_unchecked::<Mint>(byte_ptr).unwrap();
acc_ref
}
}

fn get_rent(account_info: &AccountInfo) -> &Rent {
unsafe { Rent::from_bytes_unchecked(account_info.borrow_data_unchecked()) }
}

fn get_multisig(account_info: &AccountInfo) -> &Multisig {
unsafe {
let byte_ptr = account_info.borrow_data_unchecked();
let multisig_ref = load_unchecked::<Multisig>(byte_ptr).unwrap();
multisig_ref
}
}

// =============================================================================
// Aliases
// =============================================================================

use pinocchio_token_interface::program::ID as PROGRAM_ID;
use pinocchio::sysvars::rent::Rent;
use pinocchio::sysvars::rent::RENT_ID;
use pinocchio_token_interface::native_mint::ID as NATIVE_MINT_ID;
use pinocchio_token_interface::state::account::INCINERATOR_ID;
use pinocchio::pubkey::PUBKEY_BYTES;
use pinocchio_token_interface::state::account_state::AccountState;

// =============================================================================
// Process call macros (ordered same as includes)
// =============================================================================

macro_rules! call_process_approve_checked {
($accounts:expr, $instruction_data:expr) => {
process_approve_checked($accounts, $instruction_data)
};
}
macro_rules! call_process_approve {
($accounts:expr, $instruction_data:expr) => {
process_approve($accounts, $instruction_data)
};
}
macro_rules! call_process_freeze_account {
($accounts:expr) => {
process_freeze_account($accounts)
};
}
macro_rules! call_process_get_account_data_size {
($accounts:expr) => {
process_get_account_data_size($accounts)
};
}
macro_rules! call_process_initialize_account2 {
($accounts:expr, $instruction_data:expr) => {
process_initialize_account2($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_account3 {
($accounts:expr, $instruction_data:expr) => {
process_initialize_account3($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_account {
($accounts:expr) => {
process_initialize_account($accounts)
};
}
macro_rules! call_process_initialize_immutable_owner {
($accounts:expr) => {
process_initialize_immutable_owner($accounts)
};
}
macro_rules! call_process_initialize_mint2 {
($accounts:expr, $instruction_data:expr) => {
process_initialize_mint2($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_mint2_no_freeze {
($accounts:expr, $instruction_data:expr) => {
process_initialize_mint2($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_mint {
($accounts:expr, $instruction_data:expr) => {
process_initialize_mint($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_mint_no_freeze {
($accounts:expr, $instruction_data:expr) => {
process_initialize_mint($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_multisig {
($accounts:expr, $instruction_data:expr) => {
process_initialize_multisig($accounts, $instruction_data)
};
}
macro_rules! call_process_initialize_multisig2 {
($accounts:expr, $instruction_data:expr) => {
process_initialize_multisig2($accounts, $instruction_data)
};
}
macro_rules! call_process_mint_to_checked {
($accounts:expr, $instruction_data:expr) => {
process_mint_to_checked($accounts, $instruction_data)
};
}
macro_rules! call_process_mint_to {
($accounts:expr, $instruction_data:expr) => {
process_mint_to($accounts, $instruction_data)
};
}
macro_rules! call_process_revoke {
($accounts:expr) => {
process_revoke($accounts)
};
}
macro_rules! call_process_set_authority {
($accounts:expr, $instruction_data:expr) => {
process_set_authority($accounts, $instruction_data)
};
}
macro_rules! call_process_sync_native {
($accounts:expr) => {
process_sync_native($accounts)
};
}
macro_rules! call_process_thaw_account {
($accounts:expr) => {
process_thaw_account($accounts)
};
}
macro_rules! call_process_close_account {
($accounts:expr) => {
process_close_account($accounts)
};
}
macro_rules! call_process_burn_checked {
($accounts:expr, $instruction_data:expr) => {
process_burn_checked($accounts, $instruction_data)
};
}
macro_rules! call_process_burn {
($accounts:expr, $instruction_data:expr) => {
process_burn($accounts, $instruction_data)
};
}
macro_rules! call_process_transfer_checked {
($accounts:expr, $instruction_data:expr) => {
process_transfer_checked($accounts, $instruction_data)
};
}
macro_rules! call_process_transfer {
($accounts:expr, $instruction_data:expr) => {
process_transfer($accounts, $instruction_data)
};
}
macro_rules! call_process_amount_to_ui_amount {
($accounts:expr, $instruction_data:expr) => {
process_amount_to_ui_amount($accounts, $instruction_data)
};
}
macro_rules! call_process_ui_amount_to_amount {
($accounts:expr, $instruction_data:expr) => {
process_ui_amount_to_amount($accounts, $instruction_data)
};
}
Loading