Add token Amount/UiAmount conversion Instructions#2928
Add token Amount/UiAmount conversion Instructions#2928CriesofCarrots merged 3 commits intosolana-labs:masterfrom
Conversation
| let _ = Mint::unpack(&mint_info.data.borrow()) | ||
| .map_err(|_| Into::<ProgramError>::into(TokenError::InvalidMint))?; |
There was a problem hiding this comment.
This change is a little out of the scope of this PR, but happened on it while comparing test outputs between the two token programs, which were a little inconsistent.
|
This hacky patch to the ATA program demonstrates how these instructions would be called and data parsed: diff --git a/associated-token-account/program/src/processor.rs b/associated-token-account/program/src/processor.rs
index 58f1e022..3ab568ba 100644
--- a/associated-token-account/program/src/processor.rs
+++ b/associated-token-account/program/src/processor.rs
@@ -155,5 +155,52 @@ fn process_create_associated_token_account(
wallet_account_info.clone(),
spl_token_program_info.clone(),
],
- )
+ )?;
+
+ use std::convert::TryInto;
+ invoke(
+ &spl_token_2022::instruction::ui_amount_to_amount(
+ spl_token_program_id,
+ spl_token_mint_info.key,
+ "0.000042",
+ )?,
+ &[spl_token_mint_info.clone(), spl_token_program_info.clone()],
+ )?;
+
+ let amount = solana_program::program::get_return_data()
+ .ok_or(ProgramError::InvalidInstructionData)
+ .and_then(|(key, data)| {
+ if key != *spl_token_program_id {
+ return Err(ProgramError::IncorrectProgramId);
+ }
+ data.try_into()
+ .map(u64::from_le_bytes)
+ .map_err(|_| ProgramError::InvalidInstructionData)
+ })?;
+ msg!("AMOUNT {:?}", amount);
+
+ invoke(
+ &spl_token_2022::instruction::amount_to_ui_amount(
+ spl_token_program_id,
+ spl_token_mint_info.key,
+ 42000,
+ )?,
+ &[spl_token_mint_info.clone(), spl_token_program_info.clone()],
+ )?;
+
+ let ui_amount = solana_program::program::get_return_data()
+ .ok_or(ProgramError::InvalidInstructionData)
+ .and_then(|(key, data)| {
+ if key != *spl_token_program_id {
+ return Err(ProgramError::IncorrectProgramId);
+ }
+ data.try_into()
+ .map_err(|_| ProgramError::InvalidInstructionData)
+ .and_then(|data| {
+ String::from_utf8(data).map_err(|_| ProgramError::InvalidInstructionData)
+ })
+ })?;
+ msg!("UI_AMOUNT {:?}", ui_amount);
+
+ Ok(())
} |
| pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { | ||
| let decimals = decimals as usize; | ||
| if decimals > 0 { | ||
| // Left-pad zeros to decimals + 1, so we at least have an integer zero | ||
| let mut s = format!("{:01$}", amount, decimals + 1); | ||
| // Add the decimal point (Sorry, "," locales!) | ||
| s.insert(s.len() - decimals, '.'); | ||
| s | ||
| } else { | ||
| amount.to_string() | ||
| } | ||
| } | ||
|
|
||
| /// Convert a raw amount to its UI representation using the given decimals field | ||
| /// Excess zeroes or unneeded decimal point are trimmed. | ||
| pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { | ||
| let mut s = amount_to_ui_amount_string(amount, decimals); | ||
| if decimals > 0 { | ||
| let zeros_trimmed = s.trim_end_matches('0'); | ||
| s = zeros_trimmed.trim_end_matches('.').to_string(); | ||
| } | ||
| s | ||
| } |
There was a problem hiding this comment.
The monorepo methods can be removed and replaced with re-exports of these when spl-token is bumped.
5101d1a to
4424000
Compare
joncinque
left a comment
There was a problem hiding this comment.
Looks great! The test coverage is nice too. I was worried about weird floating point representations, but the way you did it is totally clear. I also read through the FromStr implementation on integer types, and it's really quite simple thankfully, so we're definitely good.
There's just the one question about including the account, but that could be done with a new instruction / separate PR.
| /// | ||
| /// Accounts expected by this instruction: | ||
| /// | ||
| /// 0. `[]` The mint to calculate for |
There was a problem hiding this comment.
I'm wondering if we should also (or instead) have a version that takes an account. For streaming tokens (ie Superfluid), you would also need to calculate the amount based on account parameters, but maybe that's too complicated a use-case for now
There was a problem hiding this comment.
Yeah, I can see a use for that. I wonder if that should/could be part of the iToken extension? Or would it need to be supported by both token programs?
I'll merge this as-is for now, but we can update this interface while building out the extension, if it seems like it would be more ergonomic
The extensions model in spl-token-2022 gets us most of the way to being able to support interest-bearing tokens: solana-labs/solana#15927
In addition to actual iToken extension definitions, we need on-chain methods to convert between raw token amounts (the number stored in the account) and ui token amount strings. The ui_amount currently only includes the decimal place, but would include the effects of interest for iTokens.
The PR implements those instructions for both token programs, so that RPC handling can be consistent.