@@ -57,30 +57,40 @@ pub struct ScaledUiAmountConfig {
5757 pub new_multiplier : PodF64 ,
5858}
5959impl ScaledUiAmountConfig {
60- fn total_multiplier ( & self , decimals : u8 , unix_timestamp : i64 ) -> f64 {
61- let multiplier = if unix_timestamp >= self . new_multiplier_effective_timestamp . into ( ) {
62- self . new_multiplier
60+ fn current_multiplier ( & self , unix_timestamp : i64 ) -> f64 {
61+ if unix_timestamp >= self . new_multiplier_effective_timestamp . into ( ) {
62+ self . new_multiplier . into ( )
6363 } else {
64- self . multiplier
65- } ;
66- f64:: from ( multiplier) / 10_f64 . powi ( decimals as i32 )
64+ self . multiplier . into ( )
65+ }
66+ }
67+
68+ fn total_multiplier ( & self , decimals : u8 , unix_timestamp : i64 ) -> f64 {
69+ self . current_multiplier ( unix_timestamp) / 10_f64 . powi ( decimals as i32 )
6770 }
6871
6972 /// Convert a raw amount to its UI representation using the given decimals
70- /// field. Excess zeroes or unneeded decimal point are trimmed.
73+ /// field.
74+ ///
75+ /// The value is converted to a float and then truncated towards 0. Excess
76+ /// zeroes or unneeded decimal point are trimmed.
7177 pub fn amount_to_ui_amount (
7278 & self ,
7379 amount : u64 ,
7480 decimals : u8 ,
7581 unix_timestamp : i64 ,
7682 ) -> Option < String > {
77- let scaled_amount = ( amount as f64 ) * self . total_multiplier ( decimals, unix_timestamp) ;
78- let ui_amount = format ! ( "{scaled_amount:.*}" , decimals as usize ) ;
83+ let scaled_amount = ( amount as f64 ) * self . current_multiplier ( unix_timestamp) ;
84+ let truncated_amount = scaled_amount. trunc ( ) / 10_f64 . powi ( decimals as i32 ) ;
85+ let ui_amount = format ! ( "{truncated_amount:.*}" , decimals as usize ) ;
7986 Some ( trim_ui_amount_string ( ui_amount, decimals) )
8087 }
8188
8289 /// Try to convert a UI representation of a token amount to its raw amount
83- /// using the given decimals field
90+ /// using the given decimals field.
91+ ///
92+ /// The string is parsed to a float, scaled, and then truncated towards 0
93+ /// before being converted to a fixed-point number.
8494 pub fn try_ui_amount_into_amount (
8595 & self ,
8696 ui_amount : & str ,
@@ -94,9 +104,9 @@ impl ScaledUiAmountConfig {
94104 if amount > ( u64:: MAX as f64 ) || amount < ( u64:: MIN as f64 ) || amount. is_nan ( ) {
95105 Err ( ProgramError :: InvalidArgument )
96106 } else {
97- // this is important, if you round earlier, you'll get wrong "inf"
107+ // this is important, if you truncate earlier, you'll get wrong "inf"
98108 // answers
99- Ok ( amount. round ( ) as u64 )
109+ Ok ( amount. trunc ( ) as u64 )
100110 }
101111 }
102112}
@@ -116,12 +126,12 @@ mod tests {
116126 let new_multiplier = 10.0 ;
117127 let new_multiplier_effective_timestamp = 1 ;
118128 let config = ScaledUiAmountConfig {
119- authority : OptionalNonZeroPubkey :: default ( ) ,
120129 multiplier : PodF64 :: from ( multiplier) ,
121130 new_multiplier : PodF64 :: from ( new_multiplier) ,
122131 new_multiplier_effective_timestamp : UnixTimestamp :: from (
123132 new_multiplier_effective_timestamp,
124133 ) ,
134+ ..Default :: default ( )
125135 } ;
126136 assert_eq ! (
127137 config. total_multiplier( 0 , new_multiplier_effective_timestamp) ,
@@ -140,7 +150,6 @@ mod tests {
140150 fn specific_amount_to_ui_amount ( ) {
141151 // 5x
142152 let config = ScaledUiAmountConfig {
143- authority : OptionalNonZeroPubkey :: default ( ) ,
144153 multiplier : PodF64 :: from ( 5.0 ) ,
145154 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
146155 ..Default :: default ( )
@@ -160,20 +169,28 @@ mod tests {
160169
161170 // huge values
162171 let config = ScaledUiAmountConfig {
163- authority : OptionalNonZeroPubkey :: default ( ) ,
164172 multiplier : PodF64 :: from ( f64:: MAX ) ,
165173 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
166174 ..Default :: default ( )
167175 } ;
168176 let ui_amount = config. amount_to_ui_amount ( u64:: MAX , 0 , 0 ) . unwrap ( ) ;
169177 assert_eq ! ( ui_amount, "inf" ) ;
178+
179+ // truncation
180+ let config = ScaledUiAmountConfig {
181+ multiplier : PodF64 :: from ( 0.99 ) ,
182+ new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
183+ ..Default :: default ( )
184+ } ;
185+ // This is really 0.99999... but it gets truncated
186+ let ui_amount = config. amount_to_ui_amount ( 101 , 2 , 0 ) . unwrap ( ) ;
187+ assert_eq ! ( ui_amount, "0.99" ) ;
170188 }
171189
172190 #[ test]
173191 fn specific_ui_amount_to_amount ( ) {
174192 // constant 5x
175193 let config = ScaledUiAmountConfig {
176- authority : OptionalNonZeroPubkey :: default ( ) ,
177194 multiplier : 5.0 . into ( ) ,
178195 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
179196 ..Default :: default ( )
@@ -199,7 +216,6 @@ mod tests {
199216
200217 // huge values
201218 let config = ScaledUiAmountConfig {
202- authority : OptionalNonZeroPubkey :: default ( ) ,
203219 multiplier : 5.0 . into ( ) ,
204220 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
205221 ..Default :: default ( )
@@ -209,7 +225,6 @@ mod tests {
209225 . unwrap ( ) ;
210226 assert_eq ! ( amount, u64 :: MAX ) ;
211227 let config = ScaledUiAmountConfig {
212- authority : OptionalNonZeroPubkey :: default ( ) ,
213228 multiplier : f64:: MAX . into ( ) ,
214229 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
215230 ..Default :: default ( )
@@ -220,7 +235,6 @@ mod tests {
220235 . unwrap ( ) ;
221236 assert_eq ! ( amount, 1 ) ;
222237 let config = ScaledUiAmountConfig {
223- authority : OptionalNonZeroPubkey :: default ( ) ,
224238 multiplier : 9.745314011399998e288 . into ( ) ,
225239 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
226240 ..Default :: default ( )
@@ -237,7 +251,6 @@ mod tests {
237251
238252 // this is unfortunate, but underflows can happen due to floats
239253 let config = ScaledUiAmountConfig {
240- authority : OptionalNonZeroPubkey :: default ( ) ,
241254 multiplier : 1.0 . into ( ) ,
242255 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
243256 ..Default :: default ( )
@@ -251,7 +264,6 @@ mod tests {
251264
252265 // overflow u64 fail
253266 let config = ScaledUiAmountConfig {
254- authority : OptionalNonZeroPubkey :: default ( ) ,
255267 multiplier : 0.1 . into ( ) ,
256268 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
257269 ..Default :: default ( )
@@ -267,12 +279,23 @@ mod tests {
267279 config. try_ui_amount_into_amount( fail_ui_amount, 0 , 0 )
268280 ) ;
269281 }
282+
283+ // truncation
284+ let config = ScaledUiAmountConfig {
285+ multiplier : PodF64 :: from ( 0.99 ) ,
286+ new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
287+ ..Default :: default ( )
288+ } ;
289+ // There are a few possibilities for what "0.99" means, it could be 101
290+ // or 100 underlying tokens, but the result gives the fewest possible
291+ // tokens that give that UI amount.
292+ let amount = config. try_ui_amount_into_amount ( "0.99" , 2 , 0 ) . unwrap ( ) ;
293+ assert_eq ! ( amount, 100 ) ;
270294 }
271295
272296 #[ test]
273297 fn specific_amount_to_ui_amount_no_scale ( ) {
274298 let config = ScaledUiAmountConfig {
275- authority : OptionalNonZeroPubkey :: default ( ) ,
276299 multiplier : 1.0 . into ( ) ,
277300 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
278301 ..Default :: default ( )
@@ -288,7 +311,6 @@ mod tests {
288311 #[ test]
289312 fn specific_ui_amount_to_amount_no_scale ( ) {
290313 let config = ScaledUiAmountConfig {
291- authority : OptionalNonZeroPubkey :: default ( ) ,
292314 multiplier : 1.0 . into ( ) ,
293315 new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
294316 ..Default :: default ( )
@@ -333,7 +355,6 @@ mod tests {
333355 decimals in 0u8 ..20u8 ,
334356 ) {
335357 let config = ScaledUiAmountConfig {
336- authority: OptionalNonZeroPubkey :: default ( ) ,
337358 multiplier: scale. into( ) ,
338359 new_multiplier_effective_timestamp: UnixTimestamp :: from( 1 ) ,
339360 ..Default :: default ( )
0 commit comments