spl_token_2022/
lib.rs

1#![allow(clippy::arithmetic_side_effects)]
2#![deny(missing_docs)]
3#![cfg_attr(not(test), forbid(unsafe_code))]
4
5//! An ERC20-like Token program for the Solana blockchain
6
7pub mod error;
8pub mod extension;
9pub mod generic_token_account;
10pub mod instruction;
11pub mod native_mint;
12pub mod offchain;
13pub mod onchain;
14pub mod pod;
15pub mod pod_instruction;
16pub mod processor;
17#[cfg(feature = "serde-traits")]
18pub mod serialization;
19pub mod state;
20
21#[cfg(not(feature = "no-entrypoint"))]
22mod entrypoint;
23
24// Export current sdk types for downstream users building with a different sdk
25// version
26use {
27    error::TokenError,
28    solana_program::{
29        entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, system_program,
30    },
31    solana_zk_sdk::encryption::pod::elgamal::PodElGamalCiphertext,
32};
33pub use {solana_program, solana_zk_sdk};
34
35/// Convert the UI representation of a token amount (using the decimals field
36/// defined in its mint) to the raw amount
37pub fn ui_amount_to_amount(ui_amount: f64, decimals: u8) -> u64 {
38    (ui_amount * 10_usize.pow(decimals as u32) as f64) as u64
39}
40
41/// Convert a raw amount to its UI representation (using the decimals field
42/// defined in its mint)
43pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> f64 {
44    amount as f64 / 10_usize.pow(decimals as u32) as f64
45}
46
47/// Convert a raw amount to its UI representation (using the decimals field
48/// defined in its mint)
49pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String {
50    let decimals = decimals as usize;
51    if decimals > 0 {
52        // Left-pad zeros to decimals + 1, so we at least have an integer zero
53        let mut s = format!("{:01$}", amount, decimals + 1);
54        // Add the decimal point (Sorry, "," locales!)
55        s.insert(s.len() - decimals, '.');
56        s
57    } else {
58        amount.to_string()
59    }
60}
61
62/// Convert a raw amount to its UI representation using the given decimals field
63/// Excess zeroes or unneeded decimal point are trimmed.
64pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String {
65    let s = amount_to_ui_amount_string(amount, decimals);
66    trim_ui_amount_string(s, decimals)
67}
68
69/// Trims a string number by removing excess zeroes or unneeded decimal point
70fn trim_ui_amount_string(mut ui_amount: String, decimals: u8) -> String {
71    if decimals > 0 {
72        let zeros_trimmed = ui_amount.trim_end_matches('0');
73        ui_amount = zeros_trimmed.trim_end_matches('.').to_string();
74    }
75    ui_amount
76}
77
78/// Try to convert a UI representation of a token amount to its raw amount using
79/// the given decimals field
80pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result<u64, ProgramError> {
81    let decimals = decimals as usize;
82    let mut parts = ui_amount.split('.');
83    // splitting a string, even an empty one, will always yield an iterator of at
84    // least length == 1
85    let mut amount_str = parts.next().unwrap().to_string();
86    let after_decimal = parts.next().unwrap_or("");
87    let after_decimal = after_decimal.trim_end_matches('0');
88    if (amount_str.is_empty() && after_decimal.is_empty())
89        || parts.next().is_some()
90        || after_decimal.len() > decimals
91    {
92        return Err(ProgramError::InvalidArgument);
93    }
94
95    amount_str.push_str(after_decimal);
96    for _ in 0..decimals.saturating_sub(after_decimal.len()) {
97        amount_str.push('0');
98    }
99    amount_str
100        .parse::<u64>()
101        .map_err(|_| ProgramError::InvalidArgument)
102}
103
104solana_program::declare_id!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
105
106/// Checks that the supplied program ID is correct for spl-token-2022
107pub fn check_program_account(spl_token_program_id: &Pubkey) -> ProgramResult {
108    if spl_token_program_id != &id() {
109        return Err(ProgramError::IncorrectProgramId);
110    }
111    Ok(())
112}
113
114/// Checks that the supplied program ID is correct for spl-token or
115/// spl-token-2022
116pub fn check_spl_token_program_account(spl_token_program_id: &Pubkey) -> ProgramResult {
117    if spl_token_program_id != &id() && spl_token_program_id != &spl_token::id() {
118        return Err(ProgramError::IncorrectProgramId);
119    }
120    Ok(())
121}
122
123/// Checks that the supplied program ID is correct for the ZK ElGamal proof
124/// program
125pub fn check_zk_elgamal_proof_program_account(
126    zk_elgamal_proof_program_id: &Pubkey,
127) -> ProgramResult {
128    if zk_elgamal_proof_program_id != &solana_zk_sdk::zk_elgamal_proof_program::id() {
129        return Err(ProgramError::IncorrectProgramId);
130    }
131    Ok(())
132}
133
134/// Checks if the supplied program ID is that of the system program
135pub fn check_system_program_account(system_program_id: &Pubkey) -> ProgramResult {
136    if system_program_id != &system_program::id() {
137        return Err(ProgramError::IncorrectProgramId);
138    }
139    Ok(())
140}
141
142/// Checks if the supplied program ID is that of the ElGamal registry program
143pub(crate) fn check_elgamal_registry_program_account(
144    elgamal_registry_account_program_id: &Pubkey,
145) -> ProgramResult {
146    if elgamal_registry_account_program_id != &spl_elgamal_registry::id() {
147        return Err(ProgramError::IncorrectProgramId);
148    }
149    Ok(())
150}
151
152/// Check instruction data and proof data auditor ciphertext consistency
153#[cfg(feature = "zk-ops")]
154pub(crate) fn check_auditor_ciphertext(
155    instruction_data_auditor_ciphertext_lo: &PodElGamalCiphertext,
156    instruction_data_auditor_ciphertext_hi: &PodElGamalCiphertext,
157    proof_context_auditor_ciphertext_lo: &PodElGamalCiphertext,
158    proof_context_auditor_ciphertext_hi: &PodElGamalCiphertext,
159) -> ProgramResult {
160    if instruction_data_auditor_ciphertext_lo != proof_context_auditor_ciphertext_lo {
161        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
162    }
163    if instruction_data_auditor_ciphertext_hi != proof_context_auditor_ciphertext_hi {
164        return Err(TokenError::ConfidentialTransferBalanceMismatch.into());
165    }
166    Ok(())
167}