Bladeren bron

p-token: Add ui amount precision tests (#98)

* Add precision tests

* Use branch dependency

* Fix format

* Add more tests

* Use published crate
Fernando Otero 4 dagen geleden
bovenliggende
commit
369b0818ee

+ 2 - 2
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"

+ 1 - 1
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]

+ 84 - 1
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)],
+    );
+}

+ 11 - 1
p-token/tests/setup/mint.rs

@@ -15,6 +15,16 @@ pub async fn initialize(
     mint_authority: Pubkey,
     freeze_authority: Option<Pubkey>,
     program_id: &Pubkey,
+) -> Result<Pubkey, ProgramError> {
+    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<Pubkey>,
+    decimals: u8,
+    program_id: &Pubkey,
 ) -> Result<Pubkey, ProgramError> {
     // 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.

+ 2 - 0
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);

+ 47 - 0
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<Pubkey>,
+    decimals: u8,
+    program_owner: &Pubkey,
+) -> Account {
+    let space = size_of::<Mint>();
+    let lamports = Rent::default().minimum_balance(space);
+
+    let mut data: Vec<u8> = vec![0u8; space];
+    let mint = unsafe { load_mut_unchecked::<Mint>(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
+}

+ 71 - 0
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)],
+    );
+}