|
| 1 | +use core::{mem::MaybeUninit, slice::from_raw_parts, str::from_utf8_unchecked}; |
1 | 2 | use pinocchio::{ |
2 | | - account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult, |
| 3 | + account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, syscalls::sol_memcpy_, |
| 4 | + ProgramResult, |
3 | 5 | }; |
4 | 6 | use token_interface::{ |
5 | 7 | error::TokenError, |
@@ -67,6 +69,12 @@ const INCINERATOR_ID: Pubkey = |
67 | 69 | /// System program id. |
68 | 70 | const SYSTEM_PROGRAM_ID: Pubkey = pinocchio_pubkey::pubkey!("11111111111111111111111111111111"); |
69 | 71 |
|
| 72 | +/// An uninitialized byte. |
| 73 | +const UNINIT_BYTE: MaybeUninit<u8> = MaybeUninit::uninit(); |
| 74 | + |
| 75 | +/// Maximum number of digits in a `u64``. |
| 76 | +const MAX_DIGITS_U64: usize = 20; |
| 77 | + |
70 | 78 | #[inline(always)] |
71 | 79 | fn is_owned_by_system_program_or_incinerator(owner: &Pubkey) -> bool { |
72 | 80 | &SYSTEM_PROGRAM_ID == owner || &INCINERATOR_ID == owner |
@@ -120,56 +128,62 @@ fn validate_owner( |
120 | 128 | Ok(()) |
121 | 129 | } |
122 | 130 |
|
123 | | -/// Convert a raw amount to its UI representation using the given decimals field |
124 | | -/// Excess zeroes or unneeded decimal point are trimmed. |
125 | | -#[inline(always)] |
126 | | -fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String { |
127 | | - let mut s = amount_to_ui_amount_string(amount, decimals); |
128 | | - if decimals > 0 { |
129 | | - let zeros_trimmed = s.trim_end_matches('0'); |
130 | | - s = zeros_trimmed.trim_end_matches('.').to_string(); |
131 | | - } |
132 | | - s |
133 | | -} |
134 | | - |
135 | | -/// Convert a raw amount to its UI representation (using the decimals field |
136 | | -/// defined in its mint) |
137 | | -#[inline(always)] |
138 | | -fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String { |
139 | | - let decimals = decimals as usize; |
140 | | - if decimals > 0 { |
141 | | - // Left-pad zeros to decimals + 1, so we at least have an integer zero |
142 | | - let mut s = format!("{:01$}", amount, decimals + 1); |
143 | | - // Add the decimal point (Sorry, "," locales!) |
144 | | - s.insert(s.len() - decimals, '.'); |
145 | | - s |
146 | | - } else { |
147 | | - amount.to_string() |
148 | | - } |
149 | | -} |
150 | | - |
151 | 131 | /// Try to convert a UI representation of a token amount to its raw amount using |
152 | 132 | /// the given decimals field |
153 | | -fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result<u64, ProgramError> { |
| 133 | +fn try_ui_amount_into_amount(ui_amount: &str, decimals: u8) -> Result<u64, ProgramError> { |
154 | 134 | let decimals = decimals as usize; |
155 | 135 | let mut parts = ui_amount.split('.'); |
| 136 | + |
156 | 137 | // splitting a string, even an empty one, will always yield an iterator of at |
157 | 138 | // least length == 1 |
158 | | - let mut amount_str = parts.next().unwrap().to_string(); |
| 139 | + let amount_str = parts.next().unwrap(); |
| 140 | + let mut length = amount_str.len(); |
| 141 | + |
| 142 | + let mut digits = [UNINIT_BYTE; MAX_DIGITS_U64]; |
| 143 | + let mut ptr = digits.as_mut_ptr(); |
| 144 | + |
| 145 | + unsafe { |
| 146 | + sol_memcpy_( |
| 147 | + ptr as *mut _, |
| 148 | + amount_str.as_ptr() as *const _, |
| 149 | + length as u64, |
| 150 | + ); |
| 151 | + } |
| 152 | + |
159 | 153 | let after_decimal = parts.next().unwrap_or(""); |
160 | 154 | let after_decimal = after_decimal.trim_end_matches('0'); |
| 155 | + |
161 | 156 | if (amount_str.is_empty() && after_decimal.is_empty()) |
162 | 157 | || parts.next().is_some() |
163 | 158 | || after_decimal.len() > decimals |
164 | 159 | { |
165 | 160 | return Err(ProgramError::InvalidArgument); |
166 | 161 | } |
167 | 162 |
|
168 | | - amount_str.push_str(after_decimal); |
169 | | - for _ in 0..decimals.saturating_sub(after_decimal.len()) { |
170 | | - amount_str.push('0'); |
| 163 | + unsafe { |
| 164 | + sol_memcpy_( |
| 165 | + ptr.add(length) as *mut _, |
| 166 | + after_decimal.as_ptr() as *const _, |
| 167 | + after_decimal.len() as u64, |
| 168 | + ); |
| 169 | + |
| 170 | + length += after_decimal.len(); |
| 171 | + ptr = ptr.add(length); |
| 172 | + } |
| 173 | + |
| 174 | + let remaining = decimals.saturating_sub(after_decimal.len()); |
| 175 | + |
| 176 | + for offset in 0..remaining { |
| 177 | + unsafe { |
| 178 | + (ptr.add(offset) as *mut u8).write(b'0'); |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + length += remaining; |
| 183 | + |
| 184 | + unsafe { |
| 185 | + from_utf8_unchecked(from_raw_parts(digits.as_ptr() as _, length)) |
| 186 | + .parse::<u64>() |
| 187 | + .map_err(|_| ProgramError::InvalidArgument) |
171 | 188 | } |
172 | | - amount_str |
173 | | - .parse::<u64>() |
174 | | - .map_err(|_| ProgramError::InvalidArgument) |
175 | 189 | } |
0 commit comments