Skip to main content

rialo_spl_token_2022/
lib.rs

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