diff --git a/Cargo.lock b/Cargo.lock index db8e89d4..9933216a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3043,9 +3043,9 @@ checksum = "5b971851087bc3699b001954ad02389d50c41405ece3548cbcafc88b3e20017a" [[package]] name = "pinocchio-log" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f266eb7ddac75ef32c42025d5cc571da4f8c9e6d5c3d70820c204d5fee72e57a" +checksum = "cd11022408f312e6179ece321c1f7dc0d1b2aa7765fddd39b2a7378d65a899e8" [[package]] name = "pinocchio-pubkey" diff --git a/p-token/Cargo.toml b/p-token/Cargo.toml index bab4a551..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 } +pinocchio-log = { version = "0.5.1", default-features = false } pinocchio-token-interface = { version = "^0", path = "../p-interface" } [dev-dependencies] diff --git a/p-token/tests/amount_to_ui_amount.rs b/p-token/tests/amount_to_ui_amount.rs index 791f6fc5..b528e728 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,81 @@ 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)], + ); +} + +#[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/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..3eafc27e --- /dev/null +++ b/p-token/tests/setup/mollusk.rs @@ -0,0 +1,47 @@ +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, + 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..7b2d1fbf 100644 --- a/p-token/tests/ui_amount_to_amount.rs +++ b/p-token/tests/ui_amount_to_amount.rs @@ -1,7 +1,11 @@ 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_error::ProgramError, solana_program_test::{tokio, ProgramTest}, solana_pubkey::Pubkey, solana_signer::Signer, @@ -45,3 +49,70 @@ 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())], + ); +} + +#[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)], + ); +}