Skip to content

Commit 369b081

Browse files
authored
p-token: Add ui amount precision tests (#98)
* Add precision tests * Use branch dependency * Fix format * Add more tests * Use published crate
1 parent 7511655 commit 369b081

File tree

7 files changed

+218
-5
lines changed

7 files changed

+218
-5
lines changed

Cargo.lock

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

p-token/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ logging = []
1616

1717
[dependencies]
1818
pinocchio = { workspace = true }
19-
pinocchio-log = { version = "0.5", default-features = false }
19+
pinocchio-log = { version = "0.5.1", default-features = false }
2020
pinocchio-token-interface = { version = "^0", path = "../p-interface" }
2121

2222
[dev-dependencies]

p-token/tests/amount_to_ui_amount.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
mod setup;
22

33
use {
4-
setup::{mint, TOKEN_PROGRAM_ID},
4+
mollusk_svm::result::Check,
5+
setup::{
6+
mint,
7+
mollusk::{create_mint_account, mollusk},
8+
TOKEN_PROGRAM_ID,
9+
},
510
solana_program_test::{tokio, ProgramTest},
611
solana_pubkey::Pubkey,
712
solana_signer::Signer,
@@ -45,3 +50,81 @@ async fn amount_to_ui_amount() {
4550

4651
assert!(account.is_some());
4752
}
53+
54+
#[test]
55+
fn amount_to_ui_amount_with_maximum_decimals() {
56+
// Given a mint account with `u8::MAX` as decimals.
57+
58+
let mint = Pubkey::new_unique();
59+
let mint_authority = Pubkey::new_unique();
60+
let freeze_authority = Pubkey::new_unique();
61+
62+
let mint_account = create_mint_account(
63+
mint_authority,
64+
Some(freeze_authority),
65+
u8::MAX,
66+
&TOKEN_PROGRAM_ID,
67+
);
68+
69+
// When we convert a 20 amount using the mint, the transaction should
70+
// succeed and return the correct UI amount.
71+
72+
let instruction =
73+
spl_token::instruction::amount_to_ui_amount(&spl_token::ID, &mint, 20).unwrap();
74+
75+
// The expected UI amount is "0.000....002" without the trailing zeros.
76+
let mut ui_amount = [b'0'; u8::MAX as usize + 1];
77+
ui_amount[1] = b'.';
78+
ui_amount[ui_amount.len() - 1] = b'2';
79+
80+
mollusk().process_and_validate_instruction(
81+
&instruction,
82+
&[(mint, mint_account)],
83+
&[Check::success(), Check::return_data(&ui_amount)],
84+
);
85+
}
86+
87+
#[test]
88+
fn amount_to_ui_amount_with_u64_max() {
89+
// Given a mint account with `u8::MAX` as decimals.
90+
91+
let mint = Pubkey::new_unique();
92+
let mint_authority = Pubkey::new_unique();
93+
let freeze_authority = Pubkey::new_unique();
94+
95+
let mint_account = create_mint_account(
96+
mint_authority,
97+
Some(freeze_authority),
98+
u8::MAX,
99+
&TOKEN_PROGRAM_ID,
100+
);
101+
102+
// When we convert an u64::MAX amount using the mint, the transaction should
103+
// succeed and return the correct UI amount.
104+
105+
let instruction =
106+
spl_token::instruction::amount_to_ui_amount(&spl_token::ID, &mint, u64::MAX).unwrap();
107+
108+
// The expected UI amount is a `u64::MAX` with 255 decimal places.
109+
// - 2 digits for `0.`
110+
// - 255 digits for the maximum decimals.
111+
let mut ui_amount = [b'0'; u8::MAX as usize + 2];
112+
ui_amount[1] = b'.';
113+
114+
let mut offset = ui_amount.len();
115+
let mut value = u64::MAX;
116+
117+
while value > 0 {
118+
let remainder = value % 10;
119+
value /= 10;
120+
offset -= 1;
121+
122+
ui_amount[offset] = b'0' + (remainder as u8);
123+
}
124+
125+
mollusk().process_and_validate_instruction(
126+
&instruction,
127+
&[(mint, mint_account)],
128+
&[Check::success(), Check::return_data(&ui_amount)],
129+
);
130+
}

p-token/tests/setup/mint.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ pub async fn initialize(
1515
mint_authority: Pubkey,
1616
freeze_authority: Option<Pubkey>,
1717
program_id: &Pubkey,
18+
) -> Result<Pubkey, ProgramError> {
19+
initialize_with_decimals(context, mint_authority, freeze_authority, 4, program_id).await
20+
}
21+
22+
pub async fn initialize_with_decimals(
23+
context: &mut ProgramTestContext,
24+
mint_authority: Pubkey,
25+
freeze_authority: Option<Pubkey>,
26+
decimals: u8,
27+
program_id: &Pubkey,
1828
) -> Result<Pubkey, ProgramError> {
1929
// Mint account keypair.
2030
let account = Keypair::new();
@@ -27,7 +37,7 @@ pub async fn initialize(
2737
&account.pubkey(),
2838
&mint_authority,
2939
freeze_authority.as_ref(),
30-
4,
40+
decimals,
3141
)
3242
.unwrap();
3343
// Switches the program id in case we are using a "custom" one.

p-token/tests/setup/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ use solana_pubkey::Pubkey;
44
pub mod account;
55
#[allow(dead_code)]
66
pub mod mint;
7+
#[allow(dead_code)]
8+
pub mod mollusk;
79

810
pub const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array(pinocchio_token_interface::program::ID);

p-token/tests/setup/mollusk.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use {
2+
crate::setup::TOKEN_PROGRAM_ID,
3+
mollusk_svm::Mollusk,
4+
pinocchio_token_interface::state::{load_mut_unchecked, mint::Mint},
5+
solana_account::Account,
6+
solana_pubkey::Pubkey,
7+
solana_rent::Rent,
8+
solana_sdk_ids::bpf_loader_upgradeable,
9+
};
10+
11+
pub fn create_mint_account(
12+
mint_authority: Pubkey,
13+
freeze_authority: Option<Pubkey>,
14+
decimals: u8,
15+
program_owner: &Pubkey,
16+
) -> Account {
17+
let space = size_of::<Mint>();
18+
let lamports = Rent::default().minimum_balance(space);
19+
20+
let mut data: Vec<u8> = vec![0u8; space];
21+
let mint = unsafe { load_mut_unchecked::<Mint>(data.as_mut_slice()).unwrap() };
22+
mint.set_mint_authority(mint_authority.as_array());
23+
if let Some(freeze_authority) = freeze_authority {
24+
mint.set_freeze_authority(freeze_authority.as_array());
25+
}
26+
mint.set_initialized();
27+
mint.decimals = decimals;
28+
29+
Account {
30+
lamports,
31+
data,
32+
owner: *program_owner,
33+
executable: false,
34+
..Default::default()
35+
}
36+
}
37+
38+
/// Creates a Mollusk instance with the default feature set.
39+
pub fn mollusk() -> Mollusk {
40+
let mut mollusk = Mollusk::default();
41+
mollusk.add_program(
42+
&TOKEN_PROGRAM_ID,
43+
"pinocchio_token_program",
44+
&bpf_loader_upgradeable::id(),
45+
);
46+
mollusk
47+
}

p-token/tests/ui_amount_to_amount.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
mod setup;
22

33
use {
4+
crate::setup::mollusk::{create_mint_account, mollusk},
5+
core::str::from_utf8,
6+
mollusk_svm::result::Check,
47
setup::{mint, TOKEN_PROGRAM_ID},
8+
solana_program_error::ProgramError,
59
solana_program_test::{tokio, ProgramTest},
610
solana_pubkey::Pubkey,
711
solana_signer::Signer,
@@ -45,3 +49,70 @@ async fn ui_amount_to_amount() {
4549

4650
assert!(account.is_some());
4751
}
52+
53+
#[test]
54+
fn ui_amount_to_amount_with_maximum_decimals() {
55+
// Given a mint account with `u8::MAX` as decimals.
56+
57+
let mint = Pubkey::new_unique();
58+
let mint_authority = Pubkey::new_unique();
59+
let freeze_authority = Pubkey::new_unique();
60+
61+
let mint_account = create_mint_account(
62+
mint_authority,
63+
Some(freeze_authority),
64+
u8::MAX,
65+
&TOKEN_PROGRAM_ID,
66+
);
67+
68+
// String representing the ui value `0.000....002`
69+
let mut ui_amount = [b'0'; u8::MAX as usize + 1];
70+
ui_amount[1] = b'.';
71+
ui_amount[ui_amount.len() - 1] = b'2';
72+
73+
let input = from_utf8(&ui_amount).unwrap();
74+
75+
// When we convert the ui amount using the mint, the transaction should
76+
// succeed and return 20 as the amount.
77+
78+
let instruction =
79+
spl_token::instruction::ui_amount_to_amount(&spl_token::ID, &mint, input).unwrap();
80+
81+
mollusk().process_and_validate_instruction(
82+
&instruction,
83+
&[(mint, mint_account)],
84+
&[Check::success(), Check::return_data(&20u64.to_le_bytes())],
85+
);
86+
}
87+
88+
#[test]
89+
fn fail_ui_amount_to_amount_with_invalid_ui_amount() {
90+
// Given a mint account with `u8::MAX` as decimals.
91+
92+
let mint = Pubkey::new_unique();
93+
let mint_authority = Pubkey::new_unique();
94+
let freeze_authority = Pubkey::new_unique();
95+
96+
let mint_account = create_mint_account(
97+
mint_authority,
98+
Some(freeze_authority),
99+
u8::MAX,
100+
&TOKEN_PROGRAM_ID,
101+
);
102+
103+
// String representing the ui value `2.0`
104+
let ui_amount = [b'2', b'.', b'0'];
105+
let input = from_utf8(&ui_amount).unwrap();
106+
107+
// When we try to convert the ui amount using the mint, the transaction should
108+
// fail with an error since the resulting value does not fit in an `u64`.
109+
110+
let instruction =
111+
spl_token::instruction::ui_amount_to_amount(&spl_token::ID, &mint, input).unwrap();
112+
113+
mollusk().process_and_validate_instruction(
114+
&instruction,
115+
&[(mint, mint_account)],
116+
&[Check::err(ProgramError::InvalidArgument)],
117+
);
118+
}

0 commit comments

Comments
 (0)