@@ -57,30 +57,40 @@ pub struct ScaledUiAmountConfig {
57
57
pub new_multiplier : PodF64 ,
58
58
}
59
59
impl 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 ( )
63
63
} 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 )
67
70
}
68
71
69
72
/// 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.
71
77
pub fn amount_to_ui_amount (
72
78
& self ,
73
79
amount : u64 ,
74
80
decimals : u8 ,
75
81
unix_timestamp : i64 ,
76
82
) -> 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 ) ;
79
86
Some ( trim_ui_amount_string ( ui_amount, decimals) )
80
87
}
81
88
82
89
/// 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.
84
94
pub fn try_ui_amount_into_amount (
85
95
& self ,
86
96
ui_amount : & str ,
@@ -94,9 +104,9 @@ impl ScaledUiAmountConfig {
94
104
if amount > ( u64:: MAX as f64 ) || amount < ( u64:: MIN as f64 ) || amount. is_nan ( ) {
95
105
Err ( ProgramError :: InvalidArgument )
96
106
} 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"
98
108
// answers
99
- Ok ( amount. round ( ) as u64 )
109
+ Ok ( amount. trunc ( ) as u64 )
100
110
}
101
111
}
102
112
}
@@ -116,12 +126,12 @@ mod tests {
116
126
let new_multiplier = 10.0 ;
117
127
let new_multiplier_effective_timestamp = 1 ;
118
128
let config = ScaledUiAmountConfig {
119
- authority : OptionalNonZeroPubkey :: default ( ) ,
120
129
multiplier : PodF64 :: from ( multiplier) ,
121
130
new_multiplier : PodF64 :: from ( new_multiplier) ,
122
131
new_multiplier_effective_timestamp : UnixTimestamp :: from (
123
132
new_multiplier_effective_timestamp,
124
133
) ,
134
+ ..Default :: default ( )
125
135
} ;
126
136
assert_eq ! (
127
137
config. total_multiplier( 0 , new_multiplier_effective_timestamp) ,
@@ -140,7 +150,6 @@ mod tests {
140
150
fn specific_amount_to_ui_amount ( ) {
141
151
// 5x
142
152
let config = ScaledUiAmountConfig {
143
- authority : OptionalNonZeroPubkey :: default ( ) ,
144
153
multiplier : PodF64 :: from ( 5.0 ) ,
145
154
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
146
155
..Default :: default ( )
@@ -160,20 +169,28 @@ mod tests {
160
169
161
170
// huge values
162
171
let config = ScaledUiAmountConfig {
163
- authority : OptionalNonZeroPubkey :: default ( ) ,
164
172
multiplier : PodF64 :: from ( f64:: MAX ) ,
165
173
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
166
174
..Default :: default ( )
167
175
} ;
168
176
let ui_amount = config. amount_to_ui_amount ( u64:: MAX , 0 , 0 ) . unwrap ( ) ;
169
177
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" ) ;
170
188
}
171
189
172
190
#[ test]
173
191
fn specific_ui_amount_to_amount ( ) {
174
192
// constant 5x
175
193
let config = ScaledUiAmountConfig {
176
- authority : OptionalNonZeroPubkey :: default ( ) ,
177
194
multiplier : 5.0 . into ( ) ,
178
195
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
179
196
..Default :: default ( )
@@ -199,7 +216,6 @@ mod tests {
199
216
200
217
// huge values
201
218
let config = ScaledUiAmountConfig {
202
- authority : OptionalNonZeroPubkey :: default ( ) ,
203
219
multiplier : 5.0 . into ( ) ,
204
220
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
205
221
..Default :: default ( )
@@ -209,7 +225,6 @@ mod tests {
209
225
. unwrap ( ) ;
210
226
assert_eq ! ( amount, u64 :: MAX ) ;
211
227
let config = ScaledUiAmountConfig {
212
- authority : OptionalNonZeroPubkey :: default ( ) ,
213
228
multiplier : f64:: MAX . into ( ) ,
214
229
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
215
230
..Default :: default ( )
@@ -220,7 +235,6 @@ mod tests {
220
235
. unwrap ( ) ;
221
236
assert_eq ! ( amount, 1 ) ;
222
237
let config = ScaledUiAmountConfig {
223
- authority : OptionalNonZeroPubkey :: default ( ) ,
224
238
multiplier : 9.745314011399998e288 . into ( ) ,
225
239
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
226
240
..Default :: default ( )
@@ -237,7 +251,6 @@ mod tests {
237
251
238
252
// this is unfortunate, but underflows can happen due to floats
239
253
let config = ScaledUiAmountConfig {
240
- authority : OptionalNonZeroPubkey :: default ( ) ,
241
254
multiplier : 1.0 . into ( ) ,
242
255
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
243
256
..Default :: default ( )
@@ -251,7 +264,6 @@ mod tests {
251
264
252
265
// overflow u64 fail
253
266
let config = ScaledUiAmountConfig {
254
- authority : OptionalNonZeroPubkey :: default ( ) ,
255
267
multiplier : 0.1 . into ( ) ,
256
268
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
257
269
..Default :: default ( )
@@ -267,12 +279,23 @@ mod tests {
267
279
config. try_ui_amount_into_amount( fail_ui_amount, 0 , 0 )
268
280
) ;
269
281
}
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 ) ;
270
294
}
271
295
272
296
#[ test]
273
297
fn specific_amount_to_ui_amount_no_scale ( ) {
274
298
let config = ScaledUiAmountConfig {
275
- authority : OptionalNonZeroPubkey :: default ( ) ,
276
299
multiplier : 1.0 . into ( ) ,
277
300
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
278
301
..Default :: default ( )
@@ -288,7 +311,6 @@ mod tests {
288
311
#[ test]
289
312
fn specific_ui_amount_to_amount_no_scale ( ) {
290
313
let config = ScaledUiAmountConfig {
291
- authority : OptionalNonZeroPubkey :: default ( ) ,
292
314
multiplier : 1.0 . into ( ) ,
293
315
new_multiplier_effective_timestamp : UnixTimestamp :: from ( 1 ) ,
294
316
..Default :: default ( )
@@ -333,7 +355,6 @@ mod tests {
333
355
decimals in 0u8 ..20u8 ,
334
356
) {
335
357
let config = ScaledUiAmountConfig {
336
- authority: OptionalNonZeroPubkey :: default ( ) ,
337
358
multiplier: scale. into( ) ,
338
359
new_multiplier_effective_timestamp: UnixTimestamp :: from( 1 ) ,
339
360
..Default :: default ( )
0 commit comments