From 8876df817b28e46e8cb28d1d603afb326d770628 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 2 Oct 2025 15:43:42 +0100 Subject: [PATCH 1/5] Add precision tests --- p-token/tests/amount_to_ui_amount.rs | 40 +++++++++++++++++++++++- p-token/tests/setup/mint.rs | 12 +++++++- p-token/tests/setup/mod.rs | 2 ++ p-token/tests/setup/mollusk.rs | 46 ++++++++++++++++++++++++++++ p-token/tests/ui_amount_to_amount.rs | 38 +++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 p-token/tests/setup/mollusk.rs diff --git a/p-token/tests/amount_to_ui_amount.rs b/p-token/tests/amount_to_ui_amount.rs index 791f6fc5..8453deaf 100644 --- a/p-token/tests/amount_to_ui_amount.rs +++ b/p-token/tests/amount_to_ui_amount.rs @@ -1,7 +1,12 @@ mod setup; use { - setup::{mint, TOKEN_PROGRAM_ID}, + mollusk_svm::result::Check, + setup::{ + mint, + mollusk::{create_mint_account, mollusk}, + TOKEN_PROGRAM_ID, + }, solana_program_test::{tokio, ProgramTest}, solana_pubkey::Pubkey, solana_signer::Signer, @@ -45,3 +50,36 @@ async fn amount_to_ui_amount() { assert!(account.is_some()); } + +#[test] +fn amount_to_ui_amount_with_maximum_decimals() { + // Given a mint account with `u8::MAX` as decimals. + + let mint = Pubkey::new_unique(); + let mint_authority = Pubkey::new_unique(); + let freeze_authority = Pubkey::new_unique(); + + let mint_account = create_mint_account( + mint_authority, + Some(freeze_authority), + u8::MAX, + &TOKEN_PROGRAM_ID, + ); + + // When we convert a 20 amount using the mint the transaction should succeed and + // return the correct UI amount. + + let instruction = + spl_token::instruction::amount_to_ui_amount(&spl_token::ID, &mint, 20).unwrap(); + + // The expected UI amount is "0.000....002" without the trailing zeros. + let mut ui_amount = [b'0'; u8::MAX as usize + 1]; + ui_amount[1] = b'.'; + ui_amount[ui_amount.len() - 1] = b'2'; + + mollusk().process_and_validate_instruction( + &instruction, + &[(mint, mint_account)], + &[Check::success(), Check::return_data(&ui_amount)], + ); +} diff --git a/p-token/tests/setup/mint.rs b/p-token/tests/setup/mint.rs index afc9a44d..8e8dcc6e 100644 --- a/p-token/tests/setup/mint.rs +++ b/p-token/tests/setup/mint.rs @@ -15,6 +15,16 @@ pub async fn initialize( mint_authority: Pubkey, freeze_authority: Option, program_id: &Pubkey, +) -> Result { + initialize_with_decimals(context, mint_authority, freeze_authority, 4, program_id).await +} + +pub async fn initialize_with_decimals( + context: &mut ProgramTestContext, + mint_authority: Pubkey, + freeze_authority: Option, + decimals: u8, + program_id: &Pubkey, ) -> Result { // Mint account keypair. let account = Keypair::new(); @@ -27,7 +37,7 @@ pub async fn initialize( &account.pubkey(), &mint_authority, freeze_authority.as_ref(), - 4, + decimals, ) .unwrap(); // Switches the program id in case we are using a "custom" one. diff --git a/p-token/tests/setup/mod.rs b/p-token/tests/setup/mod.rs index 495520c3..e6ae8879 100644 --- a/p-token/tests/setup/mod.rs +++ b/p-token/tests/setup/mod.rs @@ -4,5 +4,7 @@ use solana_pubkey::Pubkey; pub mod account; #[allow(dead_code)] pub mod mint; +#[allow(dead_code)] +pub mod mollusk; pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array(pinocchio_token_interface::program::ID); diff --git a/p-token/tests/setup/mollusk.rs b/p-token/tests/setup/mollusk.rs new file mode 100644 index 00000000..40482ab4 --- /dev/null +++ b/p-token/tests/setup/mollusk.rs @@ -0,0 +1,46 @@ +use mollusk_svm::Mollusk; +use pinocchio_token_interface::state::{load_mut_unchecked, mint::Mint}; +use solana_account::Account; +use solana_pubkey::Pubkey; +use solana_rent::Rent; +use solana_sdk_ids::bpf_loader_upgradeable; + +use crate::setup::TOKEN_PROGRAM_ID; + +pub fn create_mint_account( + mint_authority: Pubkey, + freeze_authority: Option, + decimals: u8, + program_owner: &Pubkey, +) -> Account { + let space = size_of::(); + let lamports = Rent::default().minimum_balance(space); + + let mut data: Vec = vec![0u8; space]; + let mint = unsafe { load_mut_unchecked::(data.as_mut_slice()).unwrap() }; + mint.set_mint_authority(mint_authority.as_array()); + if let Some(freeze_authority) = freeze_authority { + mint.set_freeze_authority(freeze_authority.as_array()); + } + mint.set_initialized(); + mint.decimals = decimals; + + Account { + lamports, + data, + owner: *program_owner, + executable: false, + ..Default::default() + } +} + +/// Creates a Mollusk instance with the default feature set. +pub fn mollusk() -> Mollusk { + let mut mollusk = Mollusk::default(); + mollusk.add_program( + &TOKEN_PROGRAM_ID, + "pinocchio_token_program", + &bpf_loader_upgradeable::id(), + ); + mollusk +} diff --git a/p-token/tests/ui_amount_to_amount.rs b/p-token/tests/ui_amount_to_amount.rs index 9c41eb03..e19707af 100644 --- a/p-token/tests/ui_amount_to_amount.rs +++ b/p-token/tests/ui_amount_to_amount.rs @@ -1,6 +1,9 @@ mod setup; use { + crate::setup::mollusk::{create_mint_account, mollusk}, + core::str::from_utf8, + mollusk_svm::result::Check, setup::{mint, TOKEN_PROGRAM_ID}, solana_program_test::{tokio, ProgramTest}, solana_pubkey::Pubkey, @@ -45,3 +48,38 @@ async fn ui_amount_to_amount() { assert!(account.is_some()); } + +#[test] +fn ui_amount_to_amount_with_maximum_decimals() { + // Given a mint account with `u8::MAX` as decimals. + + let mint = Pubkey::new_unique(); + let mint_authority = Pubkey::new_unique(); + let freeze_authority = Pubkey::new_unique(); + + let mint_account = create_mint_account( + mint_authority, + Some(freeze_authority), + u8::MAX, + &TOKEN_PROGRAM_ID, + ); + + // String representing the ui value `0.000....002` + let mut ui_amount = [b'0'; u8::MAX as usize + 1]; + ui_amount[1] = b'.'; + ui_amount[ui_amount.len() - 1] = b'2'; + + let input = from_utf8(&ui_amount).unwrap(); + + // When we convert the ui amount using the mint, the transaction should + // succeed and return 20 as the amount. + + let instruction = + spl_token::instruction::ui_amount_to_amount(&spl_token::ID, &mint, input).unwrap(); + + mollusk().process_and_validate_instruction( + &instruction, + &[(mint, mint_account)], + &[Check::success(), Check::return_data(&20u64.to_le_bytes())], + ); +} From ec48ff3d4556411ff39d30f0ba98f8598394c48c Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 2 Oct 2025 16:55:05 +0100 Subject: [PATCH 2/5] Use branch dependency --- Cargo.lock | 9 +++------ Cargo.toml | 2 +- p-interface/Cargo.toml | 2 +- p-token/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db8e89d4..3076777e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3038,20 +3038,17 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" +source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" [[package]] name = "pinocchio-log" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f266eb7ddac75ef32c42025d5cc571da4f8c9e6d5c3d70820c204d5fee72e57a" +source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" [[package]] name = "pinocchio-pubkey" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" +source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" dependencies = [ "five8_const", "pinocchio", diff --git a/Cargo.toml b/Cargo.toml index d8145b0e..246b65de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ consolidate-commits = false mollusk-svm = "0.4.0" mollusk-svm-fuzz-fixture = "0.4.0" num-traits = "0.2" -pinocchio = "0.9.2" +pinocchio = { version = "0.9.2", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } solana-instruction = "2.3.0" solana-program-error = "2.2.2" solana-program-option = "2.2.1" diff --git a/p-interface/Cargo.toml b/p-interface/Cargo.toml index 20a49ed3..c65c948e 100644 --- a/p-interface/Cargo.toml +++ b/p-interface/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["rlib"] [dependencies] pinocchio = { workspace = true } -pinocchio-pubkey = "0.3" +pinocchio-pubkey = { version = "0.3", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } [dev-dependencies] strum = "0.27" diff --git a/p-token/Cargo.toml b/p-token/Cargo.toml index bab4a551..a9f92a8d 100644 --- a/p-token/Cargo.toml +++ b/p-token/Cargo.toml @@ -16,7 +16,7 @@ logging = [] [dependencies] pinocchio = { workspace = true } -pinocchio-log = { version = "0.5", default-features = false } +pinocchio-log = { version = "0.5", default-features = false,git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } pinocchio-token-interface = { version = "^0", path = "../p-interface" } [dev-dependencies] From 278f40e607e9ee2005027417b16926dad5c42164 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 2 Oct 2025 17:02:23 +0100 Subject: [PATCH 3/5] Fix format --- p-token/tests/setup/mollusk.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/p-token/tests/setup/mollusk.rs b/p-token/tests/setup/mollusk.rs index 40482ab4..3eafc27e 100644 --- a/p-token/tests/setup/mollusk.rs +++ b/p-token/tests/setup/mollusk.rs @@ -1,11 +1,12 @@ -use mollusk_svm::Mollusk; -use pinocchio_token_interface::state::{load_mut_unchecked, mint::Mint}; -use solana_account::Account; -use solana_pubkey::Pubkey; -use solana_rent::Rent; -use solana_sdk_ids::bpf_loader_upgradeable; - -use crate::setup::TOKEN_PROGRAM_ID; +use { + crate::setup::TOKEN_PROGRAM_ID, + mollusk_svm::Mollusk, + pinocchio_token_interface::state::{load_mut_unchecked, mint::Mint}, + solana_account::Account, + solana_pubkey::Pubkey, + solana_rent::Rent, + solana_sdk_ids::bpf_loader_upgradeable, +}; pub fn create_mint_account( mint_authority: Pubkey, From b93bb2c422990df1b7c8abe99421907b186bb3be Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 2 Oct 2025 18:25:40 +0100 Subject: [PATCH 4/5] Add more tests --- p-token/tests/amount_to_ui_amount.rs | 49 ++++++++++++++++++++++++++-- p-token/tests/ui_amount_to_amount.rs | 33 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/p-token/tests/amount_to_ui_amount.rs b/p-token/tests/amount_to_ui_amount.rs index 8453deaf..b528e728 100644 --- a/p-token/tests/amount_to_ui_amount.rs +++ b/p-token/tests/amount_to_ui_amount.rs @@ -66,8 +66,8 @@ fn amount_to_ui_amount_with_maximum_decimals() { &TOKEN_PROGRAM_ID, ); - // When we convert a 20 amount using the mint the transaction should succeed and - // return the correct UI amount. + // When we convert a 20 amount using the mint, the transaction should + // succeed and return the correct UI amount. let instruction = spl_token::instruction::amount_to_ui_amount(&spl_token::ID, &mint, 20).unwrap(); @@ -83,3 +83,48 @@ fn amount_to_ui_amount_with_maximum_decimals() { &[Check::success(), Check::return_data(&ui_amount)], ); } + +#[test] +fn amount_to_ui_amount_with_u64_max() { + // Given a mint account with `u8::MAX` as decimals. + + let mint = Pubkey::new_unique(); + let mint_authority = Pubkey::new_unique(); + let freeze_authority = Pubkey::new_unique(); + + let mint_account = create_mint_account( + mint_authority, + Some(freeze_authority), + u8::MAX, + &TOKEN_PROGRAM_ID, + ); + + // When we convert an u64::MAX amount using the mint, the transaction should + // succeed and return the correct UI amount. + + let instruction = + spl_token::instruction::amount_to_ui_amount(&spl_token::ID, &mint, u64::MAX).unwrap(); + + // The expected UI amount is a `u64::MAX` with 255 decimal places. + // - 2 digits for `0.` + // - 255 digits for the maximum decimals. + let mut ui_amount = [b'0'; u8::MAX as usize + 2]; + ui_amount[1] = b'.'; + + let mut offset = ui_amount.len(); + let mut value = u64::MAX; + + while value > 0 { + let remainder = value % 10; + value /= 10; + offset -= 1; + + ui_amount[offset] = b'0' + (remainder as u8); + } + + mollusk().process_and_validate_instruction( + &instruction, + &[(mint, mint_account)], + &[Check::success(), Check::return_data(&ui_amount)], + ); +} diff --git a/p-token/tests/ui_amount_to_amount.rs b/p-token/tests/ui_amount_to_amount.rs index e19707af..7b2d1fbf 100644 --- a/p-token/tests/ui_amount_to_amount.rs +++ b/p-token/tests/ui_amount_to_amount.rs @@ -5,6 +5,7 @@ use { core::str::from_utf8, mollusk_svm::result::Check, setup::{mint, TOKEN_PROGRAM_ID}, + solana_program_error::ProgramError, solana_program_test::{tokio, ProgramTest}, solana_pubkey::Pubkey, solana_signer::Signer, @@ -83,3 +84,35 @@ fn ui_amount_to_amount_with_maximum_decimals() { &[Check::success(), Check::return_data(&20u64.to_le_bytes())], ); } + +#[test] +fn fail_ui_amount_to_amount_with_invalid_ui_amount() { + // Given a mint account with `u8::MAX` as decimals. + + let mint = Pubkey::new_unique(); + let mint_authority = Pubkey::new_unique(); + let freeze_authority = Pubkey::new_unique(); + + let mint_account = create_mint_account( + mint_authority, + Some(freeze_authority), + u8::MAX, + &TOKEN_PROGRAM_ID, + ); + + // String representing the ui value `2.0` + let ui_amount = [b'2', b'.', b'0']; + let input = from_utf8(&ui_amount).unwrap(); + + // When we try to convert the ui amount using the mint, the transaction should + // fail with an error since the resulting value does not fit in an `u64`. + + let instruction = + spl_token::instruction::ui_amount_to_amount(&spl_token::ID, &mint, input).unwrap(); + + mollusk().process_and_validate_instruction( + &instruction, + &[(mint, mint_account)], + &[Check::err(ProgramError::InvalidArgument)], + ); +} From cd8829c5d54aa59849d9a3c21ef39733660fffff Mon Sep 17 00:00:00 2001 From: febo Date: Fri, 3 Oct 2025 18:13:13 +0100 Subject: [PATCH 5/5] Use published crate --- Cargo.lock | 11 +++++++---- Cargo.toml | 2 +- p-interface/Cargo.toml | 2 +- p-token/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3076777e..9933216a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3038,17 +3038,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" version = "0.9.2" -source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" [[package]] name = "pinocchio-log" -version = "0.5.0" -source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd11022408f312e6179ece321c1f7dc0d1b2aa7765fddd39b2a7378d65a899e8" [[package]] name = "pinocchio-pubkey" version = "0.3.0" -source = "git+https://github.com/anza-xyz/pinocchio.git?branch=febo%2Fprecision-cap#d5e105383ec9fd37b9707c63eee252347dc40085" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0225638cadcbebae8932cb7f49cb5da7c15c21beb19f048f05a5ca7d93f065" dependencies = [ "five8_const", "pinocchio", diff --git a/Cargo.toml b/Cargo.toml index 246b65de..d8145b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ consolidate-commits = false mollusk-svm = "0.4.0" mollusk-svm-fuzz-fixture = "0.4.0" num-traits = "0.2" -pinocchio = { version = "0.9.2", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } +pinocchio = "0.9.2" solana-instruction = "2.3.0" solana-program-error = "2.2.2" solana-program-option = "2.2.1" diff --git a/p-interface/Cargo.toml b/p-interface/Cargo.toml index c65c948e..20a49ed3 100644 --- a/p-interface/Cargo.toml +++ b/p-interface/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["rlib"] [dependencies] pinocchio = { workspace = true } -pinocchio-pubkey = { version = "0.3", git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } +pinocchio-pubkey = "0.3" [dev-dependencies] strum = "0.27" diff --git a/p-token/Cargo.toml b/p-token/Cargo.toml index a9f92a8d..8f683a4d 100644 --- a/p-token/Cargo.toml +++ b/p-token/Cargo.toml @@ -16,7 +16,7 @@ logging = [] [dependencies] pinocchio = { workspace = true } -pinocchio-log = { version = "0.5", default-features = false,git = "https://github.com/anza-xyz/pinocchio.git", branch = "febo/precision-cap" } +pinocchio-log = { version = "0.5.1", default-features = false } pinocchio-token-interface = { version = "^0", path = "../p-interface" } [dev-dependencies]