99
99
100
100
match outgoing_amount {
101
101
Ok ( outgoing_amount) => {
102
- // f64 which cannot fit in u64 gets cast as 0
102
+ // The conversion succeeded, but the produced f64
103
+ // is larger than the maximum value for a u64.
104
+ // When it gets cast to a u64, it will end up being 0.
103
105
if outgoing_amount != 0.0 && outgoing_amount as u64 == 0 {
104
106
return Box :: new ( err ( RejectBuilder {
105
107
code : ErrorCode :: F08_AMOUNT_TOO_LARGE ,
@@ -119,6 +121,10 @@ where
119
121
outgoing_amount, request. to. asset_code( ) , request. to. asset_scale( ) , request. to. id( ) ) ;
120
122
}
121
123
Err ( _) => {
124
+ // This branch gets executed when the `Convert` trait
125
+ // returns an error. Happens due to float
126
+ // multiplication overflow .
127
+ // (float overflow in Rust produces +inf)
122
128
return Box :: new ( err ( RejectBuilder {
123
129
code : ErrorCode :: F08_AMOUNT_TOO_LARGE ,
124
130
message : format ! (
@@ -141,3 +147,160 @@ where
141
147
Box :: new ( self . next . send_request ( request) )
142
148
}
143
149
}
150
+
151
+ #[ cfg( test) ]
152
+ mod tests {
153
+ use super :: * ;
154
+ use futures:: { future:: ok, Future } ;
155
+ use interledger_ildcp:: IldcpAccount ;
156
+ use interledger_packet:: { Address , FulfillBuilder , PrepareBuilder } ;
157
+ use interledger_service:: { outgoing_service_fn, Account } ;
158
+ use std:: collections:: HashMap ;
159
+ use std:: str:: FromStr ;
160
+ use std:: {
161
+ sync:: { Arc , Mutex } ,
162
+ time:: SystemTime ,
163
+ } ;
164
+
165
+ #[ test]
166
+ fn exchange_rate_ok ( ) {
167
+ let ret = exchange_rate ( 100 , 1 , 1.0 , 1 , 2.0 ) ;
168
+ assert_eq ! ( ret. 1 [ 0 ] . prepare. amount( ) , 200 ) ;
169
+
170
+ let ret = exchange_rate ( 1_000_000 , 1 , 3.0 , 1 , 2.0 ) ;
171
+ assert_eq ! ( ret. 1 [ 0 ] . prepare. amount( ) , 666_666 ) ;
172
+ }
173
+
174
+ #[ test]
175
+ fn exchange_conversion_error ( ) {
176
+ // rejects f64 that does not fit in u64
177
+ let ret = exchange_rate ( std:: u64:: MAX , 1 , 1.0 , 1 , 2.0 ) ;
178
+ let reject = ret. 0 . unwrap_err ( ) ;
179
+ assert_eq ! ( reject. code( ) , ErrorCode :: F08_AMOUNT_TOO_LARGE ) ;
180
+ assert ! ( reject. message( ) . starts_with( b"Could not cast" ) ) ;
181
+
182
+ // `Convert` errored
183
+ let ret = exchange_rate ( std:: u64:: MAX , 1 , 1.0 , 255 , std:: f64:: MAX ) ;
184
+ let reject = ret. 0 . unwrap_err ( ) ;
185
+ assert_eq ! ( reject. code( ) , ErrorCode :: F08_AMOUNT_TOO_LARGE ) ;
186
+ assert ! ( reject. message( ) . starts_with( b"Could not convert" ) ) ;
187
+ }
188
+
189
+ // Instantiates an exchange rate service and returns the fulfill/reject
190
+ // packet and the outgoing request after performing an asset conversion
191
+ fn exchange_rate (
192
+ amount : u64 ,
193
+ scale1 : u8 ,
194
+ rate1 : f64 ,
195
+ scale2 : u8 ,
196
+ rate2 : f64 ,
197
+ ) -> ( Result < Fulfill , Reject > , Vec < OutgoingRequest < TestAccount > > ) {
198
+ let requests = Arc :: new ( Mutex :: new ( Vec :: new ( ) ) ) ;
199
+ let requests_clone = requests. clone ( ) ;
200
+ let outgoing = outgoing_service_fn ( move |request| {
201
+ requests_clone. lock ( ) . unwrap ( ) . push ( request) ;
202
+ Box :: new ( ok ( FulfillBuilder {
203
+ fulfillment : & [ 0 ; 32 ] ,
204
+ data : b"hello!" ,
205
+ }
206
+ . build ( ) ) )
207
+ } ) ;
208
+ let mut service = test_service ( rate1, rate2, outgoing) ;
209
+ let result = service
210
+ . send_request ( OutgoingRequest {
211
+ from : TestAccount :: new ( "ABC" . to_owned ( ) , scale1) ,
212
+ to : TestAccount :: new ( "XYZ" . to_owned ( ) , scale2) ,
213
+ original_amount : amount,
214
+ prepare : PrepareBuilder {
215
+ destination : Address :: from_str ( "example.destination" ) . unwrap ( ) ,
216
+ amount,
217
+ expires_at : SystemTime :: now ( ) ,
218
+ execution_condition : & [ 1 ; 32 ] ,
219
+ data : b"hello" ,
220
+ }
221
+ . build ( ) ,
222
+ } )
223
+ . wait ( ) ;
224
+
225
+ let reqs = requests. lock ( ) . unwrap ( ) ;
226
+ ( result, reqs. clone ( ) )
227
+ }
228
+
229
+ #[ derive( Debug , Clone ) ]
230
+ struct TestAccount {
231
+ ilp_address : Address ,
232
+ asset_code : String ,
233
+ asset_scale : u8 ,
234
+ }
235
+ impl TestAccount {
236
+ fn new ( asset_code : String , asset_scale : u8 ) -> Self {
237
+ TestAccount {
238
+ ilp_address : Address :: from_str ( "example.alice" ) . unwrap ( ) ,
239
+ asset_code,
240
+ asset_scale,
241
+ }
242
+ }
243
+ }
244
+
245
+ impl Account for TestAccount {
246
+ type AccountId = u64 ;
247
+
248
+ fn id ( & self ) -> u64 {
249
+ 0
250
+ }
251
+ }
252
+
253
+ impl IldcpAccount for TestAccount {
254
+ fn asset_code ( & self ) -> & str {
255
+ & self . asset_code
256
+ }
257
+
258
+ fn asset_scale ( & self ) -> u8 {
259
+ self . asset_scale
260
+ }
261
+
262
+ fn client_address ( & self ) -> & Address {
263
+ & self . ilp_address
264
+ }
265
+ }
266
+
267
+ #[ derive( Debug , Clone ) ]
268
+ struct TestStore {
269
+ rates : HashMap < Vec < String > , ( f64 , f64 ) > ,
270
+ }
271
+
272
+ impl ExchangeRateStore for TestStore {
273
+ fn get_exchange_rates ( & self , asset_codes : & [ & str ] ) -> Result < Vec < f64 > , ( ) > {
274
+ let mut ret = Vec :: new ( ) ;
275
+ let key = vec ! [ asset_codes[ 0 ] . to_owned( ) , asset_codes[ 1 ] . to_owned( ) ] ;
276
+ let v = self . rates . get ( & key) ;
277
+ if let Some ( v) = v {
278
+ ret. push ( v. 0 ) ;
279
+ ret. push ( v. 1 ) ;
280
+ } else {
281
+ return Err ( ( ) ) ;
282
+ }
283
+ Ok ( ret)
284
+ }
285
+ }
286
+
287
+ fn test_store ( rate1 : f64 , rate2 : f64 ) -> TestStore {
288
+ let mut rates = HashMap :: new ( ) ;
289
+ rates. insert ( vec ! [ "ABC" . to_owned( ) , "XYZ" . to_owned( ) ] , ( rate1, rate2) ) ;
290
+ TestStore { rates }
291
+ }
292
+
293
+ fn test_service (
294
+ rate1 : f64 ,
295
+ rate2 : f64 ,
296
+ handler : impl OutgoingService < TestAccount > + Clone + Send + Sync ,
297
+ ) -> ExchangeRateService <
298
+ TestStore ,
299
+ impl OutgoingService < TestAccount > + Clone + Send + Sync ,
300
+ TestAccount ,
301
+ > {
302
+ let store = test_store ( rate1, rate2) ;
303
+ ExchangeRateService :: new ( Address :: from_str ( "example.bob" ) . unwrap ( ) , store, handler)
304
+ }
305
+
306
+ }
0 commit comments