1
use crate::{
2
    helpers::Scaled,
3
    msg::{ExecuteMsg, QueryMsg},
4
    testing::test_utils::{
5
        are_fpdecimals_approximately_equal, assert_fee_is_as_expected, create_realistic_atom_usdt_sell_orders_from_spreadsheet,
6
        create_realistic_eth_usdt_buy_orders_from_spreadsheet, create_realistic_eth_usdt_sell_orders_from_spreadsheet,
7
        create_realistic_inj_usdt_buy_orders_from_spreadsheet, create_realistic_usdt_usdc_both_side_orders, human_to_dec, init_rich_account,
8
        init_self_relaying_contract_and_get_address, initial_coin, launch_realistic_atom_usdt_spot_market, launch_realistic_inj_usdt_spot_market,
9
        launch_realistic_usdt_usdc_spot_market, launch_realistic_weth_usdt_spot_market, must_init_account_with_funds_and_setting_denoms,
10
        query_all_bank_balances, query_bank_balance, set_route_and_assert_success, str_coin, Decimals, ATOM, DEFAULT_ATOMIC_MULTIPLIER,
11
        DEFAULT_SELF_RELAYING_FEE_PART, DEFAULT_TAKER_FEE, ETH, INJ, INJ_2, USDC, USDT,
12
    },
13
    types::{FPCoin, SwapEstimationResult},
14
};
15

            
16
use injective_math::FPDecimal;
17
use injective_test_tube::{Account, Bank, Exchange, InjectiveTestApp, Module, RunnerResult, SigningAccount, Wasm};
18
use std::ops::Neg;
19

            
20
/*
21
   This test suite focuses on using using realistic values both for spot markets and for orders and
22
   focuses on swaps requesting minimum amount.
23

            
24
   ATOM/USDT market parameters were taken from mainnet. ETH/USDT market parameters mirror WETH/USDT
25
   spot market on mainnet. INJ_2/USDT mirrors mainnet's INJ/USDT market (we used a different denom
26
   to avoid mixing balance changes related to swap with ones related to gas payments).
27

            
28
   Hardcoded values used in these tests come from the second tab of this spreadsheet:
29
   https://docs.google.com/spreadsheets/d/1-0epjX580nDO_P2mm1tSjhvjJVppsvrO1BC4_wsBeyA/edit?usp=sharing
30

            
31
   In all tests contract is configured to self-relay trades and thus receive a 60% fee discount.
32
*/
33

            
34
6
pub fn happy_path_two_hops_test(app: InjectiveTestApp, owner: SigningAccount, contr_addr: String) {
35
6
    let wasm = Wasm::new(&app);
36
6
    let exchange = Exchange::new(&app);
37
6
    let bank = Bank::new(&app);
38
6

            
39
6
    let spot_market_1_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
40
6
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
41
6

            
42
6
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
43
6

            
44
6
    set_route_and_assert_success(
45
6
        &wasm,
46
6
        &owner,
47
6
        &contr_addr,
48
6
        ETH,
49
6
        ATOM,
50
6
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
51
6
    );
52
6

            
53
6
    let trader1 = init_rich_account(&app);
54
6
    let trader2 = init_rich_account(&app);
55
6
    let trader3 = init_rich_account(&app);
56
6

            
57
6
    create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
58
6
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
59
6

            
60
6
    app.increase_time(1);
61
6

            
62
6
    let eth_to_swap = "4.08";
63
6

            
64
6
    let swapper = must_init_account_with_funds_and_setting_denoms(
65
6
        &app,
66
6
        &validator,
67
6
        &[
68
6
            initial_coin(eth_to_swap, ETH, Decimals::Eighteen),
69
6
            initial_coin("1", INJ, Decimals::Eighteen),
70
6
        ],
71
6
    );
72
6

            
73
6
    let mut query_result: SwapEstimationResult = wasm
74
6
        .query(
75
6
            &contr_addr,
76
6
            &QueryMsg::GetOutputQuantity {
77
6
                source_denom: ETH.to_string(),
78
6
                target_denom: ATOM.to_string(),
79
6
                from_quantity: human_to_dec(eth_to_swap, Decimals::Eighteen),
80
6
            },
81
6
        )
82
6
        .unwrap();
83
6

            
84
6
    // it's expected that it is slightly less than what's in the spreadsheet
85
6
    let expected_amount = human_to_dec("906.17", Decimals::Six);
86
6

            
87
6
    assert_eq!(
88
        query_result.result_quantity, expected_amount,
89
        "incorrect swap result estimate returned by query"
90
    );
91

            
92
6
    let mut expected_fees = vec![
93
6
        FPCoin {
94
6
            amount: human_to_dec("12.221313", Decimals::Six),
95
6
            denom: "usdt".to_string(),
96
6
        },
97
6
        FPCoin {
98
6
            amount: human_to_dec("12.184704", Decimals::Six),
99
6
            denom: "usdt".to_string(),
100
6
        },
101
6
    ];
102
6

            
103
6
    // we don't care too much about decimal fraction of the fee
104
6
    assert_fee_is_as_expected(&mut query_result.expected_fees, &mut expected_fees, human_to_dec("0.1", Decimals::Six));
105
6

            
106
6
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
107
6
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
108

            
109
6
    wasm.execute(
110
6
        &contr_addr,
111
6
        &ExecuteMsg::SwapMinOutput {
112
6
            target_denom: ATOM.to_string(),
113
6
            min_output_quantity: FPDecimal::from(906u128),
114
6
        },
115
6
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
116
6
        &swapper,
117
6
    )
118
6
    .unwrap();
119
6

            
120
6
    let from_balance = query_bank_balance(&bank, ETH, swapper.address().as_str());
121
6
    let to_balance = query_bank_balance(&bank, ATOM, swapper.address().as_str());
122
6

            
123
6
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
124

            
125
6
    assert!(
126
6
        to_balance >= expected_amount,
127
        "Swapper received less than expected minimum amount. Expected: {} ATOM, actual: {} ATOM",
128
        expected_amount.scaled(Decimals::Six.get_decimals().neg()),
129
        to_balance.scaled(Decimals::Six.get_decimals().neg()),
130
    );
131

            
132
6
    let max_diff = human_to_dec("0.1", Decimals::Six);
133
6

            
134
6
    assert!(
135
6
        are_fpdecimals_approximately_equal(expected_amount, to_balance, max_diff,),
136
        "Swapper did not receive expected amount. Expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
137
        expected_amount.scaled(Decimals::Six.get_decimals().neg()),
138
        to_balance.scaled(Decimals::Six.get_decimals().neg()),
139
        max_diff.scaled(Decimals::Six.get_decimals().neg())
140
    );
141

            
142
6
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
143
6
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
144

            
145
6
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
146
6
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
147
6

            
148
6
    assert!(
149
6
        contract_usdt_balance_after >= contract_usdt_balance_before,
150
        "Contract lost some money after swap. Actual balance: {} USDT, previous balance: {} USDT",
151
        contract_usdt_balance_after,
152
        contract_usdt_balance_before
153
    );
154

            
155
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8150 worth of ETH
156
6
    let max_diff = human_to_dec("0.7", Decimals::Six);
157
6

            
158
6
    assert!(
159
6
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
160
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
161
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
162
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
163
        max_diff.scaled(Decimals::Six.get_decimals().neg())
164
    );
165
6
}
166

            
167
#[test]
168
2
fn happy_path_two_hops_swap_eth_atom_realistic_values_self_relaying() {
169
2
    let app = InjectiveTestApp::new();
170
2
    let wasm = Wasm::new(&app);
171
2

            
172
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
173
2

            
174
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
175
2

            
176
2
    let owner = must_init_account_with_funds_and_setting_denoms(
177
2
        &app,
178
2
        &validator,
179
2
        &[
180
2
            initial_coin("1", ETH, Decimals::Eighteen),
181
2
            initial_coin("1", ATOM, Decimals::Six),
182
2
            initial_coin("1_000", USDT, Decimals::Six),
183
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
184
2
        ],
185
2
    );
186
2

            
187
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
188
2

            
189
2
    happy_path_two_hops_test(app, owner, contr_addr);
190
2
}
191

            
192
#[test]
193
2
fn happy_path_two_hops_swap_inj_eth_realistic_values_self_relaying() {
194
2
    let app = InjectiveTestApp::new();
195
2
    let wasm = Wasm::new(&app);
196
2
    let exchange = Exchange::new(&app);
197
2
    let bank = Bank::new(&app);
198
2

            
199
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
200
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
201
2

            
202
2
    let owner = must_init_account_with_funds_and_setting_denoms(
203
2
        &app,
204
2
        &validator,
205
2
        &[
206
2
            initial_coin("1", ETH, Decimals::Eighteen),
207
2
            initial_coin("1_000", USDT, Decimals::Six),
208
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
209
2
            initial_coin("1", INJ_2, Decimals::Eighteen),
210
2
        ],
211
2
    );
212
2

            
213
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
214
2
    let spot_market_2_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
215
2

            
216
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
217
2
    set_route_and_assert_success(
218
2
        &wasm,
219
2
        &owner,
220
2
        &contr_addr,
221
2
        INJ_2,
222
2
        ETH,
223
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
224
2
    );
225
2

            
226
2
    let trader1 = init_rich_account(&app);
227
2
    let trader2 = init_rich_account(&app);
228
2
    let trader3 = init_rich_account(&app);
229
2

            
230
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
231
2
    create_realistic_eth_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
232
2

            
233
2
    app.increase_time(1);
234
2

            
235
2
    let inj_to_swap = "973.258";
236
2

            
237
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
238
2
        &app,
239
2
        &validator,
240
2
        &[
241
2
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
242
2
            initial_coin("1", INJ, Decimals::Eighteen),
243
2
        ],
244
2
    );
245
2

            
246
2
    let mut query_result: SwapEstimationResult = wasm
247
2
        .query(
248
2
            &contr_addr,
249
2
            &QueryMsg::GetOutputQuantity {
250
2
                source_denom: INJ_2.to_string(),
251
2
                target_denom: ETH.to_string(),
252
2
                from_quantity: human_to_dec(inj_to_swap, Decimals::Eighteen),
253
2
            },
254
2
        )
255
2
        .unwrap();
256
2

            
257
2
    // it's expected that it is slightly less than what's in the spreadsheet
258
2
    let expected_amount = human_to_dec("3.994", Decimals::Eighteen);
259
2

            
260
2
    assert_eq!(
261
        query_result.result_quantity, expected_amount,
262
        "incorrect swap result estimate returned by query"
263
    );
264

            
265
2
    let mut expected_fees = vec![
266
2
        FPCoin {
267
2
            amount: human_to_dec("12.73828775", Decimals::Six),
268
2
            denom: "usdt".to_string(),
269
2
        },
270
2
        FPCoin {
271
2
            amount: human_to_dec("12.70013012", Decimals::Six),
272
2
            denom: "usdt".to_string(),
273
2
        },
274
2
    ];
275
2

            
276
2
    // we don't care too much about decimal fraction of the fee
277
2
    assert_fee_is_as_expected(&mut query_result.expected_fees, &mut expected_fees, human_to_dec("0.1", Decimals::Six));
278
2

            
279
2
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
280
2
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
281

            
282
2
    wasm.execute(
283
2
        &contr_addr,
284
2
        &ExecuteMsg::SwapMinOutput {
285
2
            target_denom: ETH.to_string(),
286
2
            min_output_quantity: FPDecimal::from(906u128),
287
2
        },
288
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
289
2
        &swapper,
290
2
    )
291
2
    .unwrap();
292
2

            
293
2
    let from_balance = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
294
2
    let to_balance = query_bank_balance(&bank, ETH, swapper.address().as_str());
295
2

            
296
2
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
297

            
298
2
    assert!(
299
2
        to_balance >= expected_amount,
300
        "Swapper received less than expected minimum amount. Expected: {} ETH, actual: {} ETH",
301
        expected_amount.scaled(Decimals::Eighteen.get_decimals().neg()),
302
        to_balance.scaled(Decimals::Eighteen.get_decimals().neg()),
303
    );
304

            
305
2
    let max_diff = human_to_dec("0.1", Decimals::Eighteen);
306
2

            
307
2
    assert!(
308
2
        are_fpdecimals_approximately_equal(expected_amount, to_balance, max_diff,),
309
        "Swapper did not receive expected amount. Expected: {} ETH, actual: {} ETH, max diff: {} ETH",
310
        expected_amount.scaled(Decimals::Eighteen.get_decimals().neg()),
311
        to_balance.scaled(Decimals::Eighteen.get_decimals().neg()),
312
        max_diff.scaled(Decimals::Eighteen.get_decimals().neg())
313
    );
314

            
315
2
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
316
2
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
317

            
318
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
319
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
320
2

            
321
2
    assert!(
322
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
323
        "Contract lost some money after swap. Actual balance: {} USDT, previous balance: {} USDT",
324
        contract_usdt_balance_after,
325
        contract_usdt_balance_before
326
    );
327

            
328
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8150 worth of ETH
329
2
    let max_diff = human_to_dec("0.7", Decimals::Six);
330
2

            
331
2
    assert!(
332
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
333
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
334
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
335
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
336
        max_diff.scaled(Decimals::Six.get_decimals().neg())
337
    );
338
2
}
339

            
340
#[test]
341
2
fn happy_path_two_hops_swap_inj_atom_realistic_values_self_relaying() {
342
2
    let app = InjectiveTestApp::new();
343
2
    let wasm = Wasm::new(&app);
344
2
    let exchange = Exchange::new(&app);
345
2
    let bank = Bank::new(&app);
346
2

            
347
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
348
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
349
2

            
350
2
    let owner = must_init_account_with_funds_and_setting_denoms(
351
2
        &app,
352
2
        &validator,
353
2
        &[
354
2
            initial_coin("1", ETH, Decimals::Eighteen),
355
2
            initial_coin("1", ATOM, Decimals::Six),
356
2
            initial_coin("1_000", USDT, Decimals::Six),
357
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
358
2
            initial_coin("1", INJ_2, Decimals::Eighteen),
359
2
        ],
360
2
    );
361
2

            
362
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
363
2
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
364
2

            
365
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
366
2
    set_route_and_assert_success(
367
2
        &wasm,
368
2
        &owner,
369
2
        &contr_addr,
370
2
        INJ_2,
371
2
        ATOM,
372
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
373
2
    );
374
2

            
375
2
    let trader1 = init_rich_account(&app);
376
2
    let trader2 = init_rich_account(&app);
377
2
    let trader3 = init_rich_account(&app);
378
2

            
379
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
380
2
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
381
2

            
382
2
    app.increase_time(1);
383
2

            
384
2
    let inj_to_swap = "973.258";
385
2

            
386
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
387
2
        &app,
388
2
        &validator,
389
2
        &[
390
2
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
391
2
            initial_coin("1", INJ, Decimals::Eighteen),
392
2
        ],
393
2
    );
394
2

            
395
2
    let mut query_result: SwapEstimationResult = wasm
396
2
        .query(
397
2
            &contr_addr,
398
2
            &QueryMsg::GetOutputQuantity {
399
2
                source_denom: INJ_2.to_string(),
400
2
                target_denom: ATOM.to_string(),
401
2
                from_quantity: human_to_dec(inj_to_swap, Decimals::Eighteen),
402
2
            },
403
2
        )
404
2
        .unwrap();
405
2

            
406
2
    // it's expected that it is slightly less than what's in the spreadsheet
407
2
    let expected_amount = human_to_dec("944.26", Decimals::Six);
408
2

            
409
2
    assert_eq!(
410
        query_result.result_quantity, expected_amount,
411
        "incorrect swap result estimate returned by query"
412
    );
413

            
414
2
    let mut expected_fees = vec![
415
2
        FPCoin {
416
2
            amount: human_to_dec("12.73828775", Decimals::Six),
417
2
            denom: "usdt".to_string(),
418
2
        },
419
2
        FPCoin {
420
2
            amount: human_to_dec("12.70013012", Decimals::Six),
421
2
            denom: "usdt".to_string(),
422
2
        },
423
2
    ];
424
2

            
425
2
    // we don't care too much about decimal fraction of the fee
426
2
    assert_fee_is_as_expected(&mut query_result.expected_fees, &mut expected_fees, human_to_dec("0.1", Decimals::Six));
427
2

            
428
2
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
429
2
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
430

            
431
2
    wasm.execute(
432
2
        &contr_addr,
433
2
        &ExecuteMsg::SwapMinOutput {
434
2
            target_denom: ATOM.to_string(),
435
2
            min_output_quantity: FPDecimal::from(944u128),
436
2
        },
437
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
438
2
        &swapper,
439
2
    )
440
2
    .unwrap();
441
2

            
442
2
    let from_balance = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
443
2
    let to_balance = query_bank_balance(&bank, ATOM, swapper.address().as_str());
444
2

            
445
2
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
446

            
447
2
    assert!(
448
2
        to_balance >= expected_amount,
449
        "Swapper received less than expected minimum amount. Expected: {} ATOM, actual: {} ATOM",
450
        expected_amount.scaled(Decimals::Six.get_decimals().neg()),
451
        to_balance.scaled(Decimals::Six.get_decimals().neg()),
452
    );
453

            
454
2
    let max_diff = human_to_dec("0.1", Decimals::Six);
455
2

            
456
2
    assert!(
457
2
        are_fpdecimals_approximately_equal(expected_amount, to_balance, max_diff,),
458
        "Swapper did not receive expected amount. Expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
459
        expected_amount.scaled(Decimals::Six.get_decimals().neg()),
460
        to_balance.scaled(Decimals::Six.get_decimals().neg()),
461
        max_diff.scaled(Decimals::Six.get_decimals().neg())
462
    );
463

            
464
2
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
465
2
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
466

            
467
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
468
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
469
2

            
470
2
    assert!(
471
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
472
        "Contract lost some money after swap. Actual balance: {} USDT, previous balance: {} USDT",
473
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
474
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg())
475
    );
476

            
477
    // contract is allowed to earn extra 0.82 USDT from the swap of ~$8500 worth of INJ
478
2
    let max_diff = human_to_dec("0.82", Decimals::Six);
479
2

            
480
2
    assert!(
481
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
482
        "Contract balance changed too much. Actual balance: {}, previous balance: {}. Max diff: {}",
483
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
484
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
485
        max_diff.scaled(Decimals::Six.get_decimals().neg())
486
    );
487
2
}
488

            
489
#[test]
490
2
fn it_executes_swap_between_markets_using_different_quote_assets_self_relaying() {
491
2
    let app = InjectiveTestApp::new();
492
2
    let wasm = Wasm::new(&app);
493
2
    let exchange = Exchange::new(&app);
494
2
    let bank = Bank::new(&app);
495
2

            
496
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
497
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
498
2

            
499
2
    let owner = must_init_account_with_funds_and_setting_denoms(
500
2
        &app,
501
2
        &validator,
502
2
        &[
503
2
            initial_coin("1_000", USDT, Decimals::Six),
504
2
            initial_coin("1_000", USDC, Decimals::Six),
505
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
506
2
            initial_coin("1", INJ_2, Decimals::Eighteen),
507
2
        ],
508
2
    );
509
2

            
510
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
511
2
    let spot_market_2_id = launch_realistic_usdt_usdc_spot_market(&exchange, &owner);
512
2

            
513
2
    let contr_addr = init_self_relaying_contract_and_get_address(
514
2
        &wasm,
515
2
        &owner,
516
2
        &[str_coin("10", USDC, Decimals::Six), str_coin("500", USDT, Decimals::Six)],
517
2
    );
518
2
    set_route_and_assert_success(
519
2
        &wasm,
520
2
        &owner,
521
2
        &contr_addr,
522
2
        INJ_2,
523
2
        USDC,
524
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
525
2
    );
526
2

            
527
2
    let trader1 = init_rich_account(&app);
528
2
    let trader2 = init_rich_account(&app);
529
2

            
530
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
531
2
    create_realistic_usdt_usdc_both_side_orders(&app, &spot_market_2_id, &trader1);
532
2

            
533
2
    app.increase_time(1);
534
2

            
535
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
536
2
        &app,
537
2
        &validator,
538
2
        &[initial_coin("1", INJ, Decimals::Eighteen), initial_coin("1", INJ_2, Decimals::Eighteen)],
539
2
    );
540
2

            
541
2
    let inj_to_swap = "1";
542
2

            
543
2
    let mut query_result: SwapEstimationResult = wasm
544
2
        .query(
545
2
            &contr_addr,
546
2
            &QueryMsg::GetOutputQuantity {
547
2
                source_denom: INJ_2.to_string(),
548
2
                target_denom: USDC.to_string(),
549
2
                from_quantity: human_to_dec(inj_to_swap, Decimals::Eighteen),
550
2
            },
551
2
        )
552
2
        .unwrap();
553
2

            
554
2
    let expected_amount = human_to_dec("8.867", Decimals::Six);
555
2
    let max_diff = human_to_dec("0.001", Decimals::Six);
556
2

            
557
2
    assert!(
558
2
        are_fpdecimals_approximately_equal(expected_amount, query_result.result_quantity, max_diff),
559
        "incorrect swap result estimate returned by query"
560
    );
561

            
562
2
    let mut expected_fees = vec![
563
2
        FPCoin {
564
2
            amount: human_to_dec("0.013365", Decimals::Six),
565
2
            denom: USDT.to_string(),
566
2
        },
567
2
        FPCoin {
568
2
            amount: human_to_dec("0.01332", Decimals::Six),
569
2
            denom: USDC.to_string(),
570
2
        },
571
2
    ];
572
2

            
573
2
    // we don't care too much about decimal fraction of the fee
574
2
    assert_fee_is_as_expected(&mut query_result.expected_fees, &mut expected_fees, human_to_dec("0.1", Decimals::Six));
575
2

            
576
2
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
577
2
    assert_eq!(contract_balances_before.len(), 2, "wrong number of denoms in contract balances");
578

            
579
2
    wasm.execute(
580
2
        &contr_addr,
581
2
        &ExecuteMsg::SwapMinOutput {
582
2
            target_denom: USDC.to_string(),
583
2
            min_output_quantity: FPDecimal::from(8u128),
584
2
        },
585
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
586
2
        &swapper,
587
2
    )
588
2
    .unwrap();
589
2

            
590
2
    let from_balance = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
591
2
    let to_balance = query_bank_balance(&bank, USDC, swapper.address().as_str());
592
2

            
593
2
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
594

            
595
2
    assert!(
596
2
        to_balance >= expected_amount,
597
        "Swapper received less than expected minimum amount. Expected: {} USDC, actual: {} USDC",
598
        expected_amount.scaled(Decimals::Eighteen.get_decimals().neg()),
599
        to_balance.scaled(Decimals::Eighteen.get_decimals().neg()),
600
    );
601

            
602
2
    let max_diff = human_to_dec("0.1", Decimals::Eighteen);
603
2

            
604
2
    assert!(
605
2
        are_fpdecimals_approximately_equal(expected_amount, to_balance, max_diff,),
606
        "Swapper did not receive expected amount. Expected: {} USDC, actual: {} USDC, max diff: {} USDC",
607
        expected_amount.scaled(Decimals::Eighteen.get_decimals().neg()),
608
        to_balance.scaled(Decimals::Eighteen.get_decimals().neg()),
609
        max_diff.scaled(Decimals::Eighteen.get_decimals().neg())
610
    );
611

            
612
2
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
613
2
    assert_eq!(contract_balances_after.len(), 2, "wrong number of denoms in contract balances");
614

            
615
    // let's check contract's USDT balance
616
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
617
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
618
2

            
619
2
    assert!(
620
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
621
        "Contract lost some money after swap. Actual balance: {} USDT, previous balance: {} USDT",
622
        contract_usdt_balance_after,
623
        contract_usdt_balance_before
624
    );
625

            
626
    // contract is allowed to earn extra 0.001 USDT from the swap of ~$8 worth of INJ
627
2
    let max_diff = human_to_dec("0.001", Decimals::Six);
628
2

            
629
2
    assert!(
630
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
631
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
632
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
633
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
634
        max_diff.scaled(Decimals::Six.get_decimals().neg())
635
    );
636

            
637
    // let's check contract's USDC balance
638
2
    let contract_usdc_balance_before = FPDecimal::must_from_str(contract_balances_before[1].amount.as_str());
639
2
    let contract_usdc_balance_after = FPDecimal::must_from_str(contract_balances_after[1].amount.as_str());
640
2

            
641
2
    assert!(
642
2
        contract_usdc_balance_after >= contract_usdc_balance_before,
643
        "Contract lost some money after swap. Actual balance: {} USDC, previous balance: {} USDC",
644
        contract_usdc_balance_after,
645
        contract_usdc_balance_before
646
    );
647

            
648
    // contract is allowed to earn extra 0.001 USDC from the swap of ~$8 worth of INJ
649
2
    let max_diff = human_to_dec("0.001", Decimals::Six);
650
2

            
651
2
    assert!(
652
2
        are_fpdecimals_approximately_equal(contract_usdc_balance_after, contract_usdc_balance_before, max_diff,),
653
        "Contract balance changed too much. Actual balance: {} USDC, previous balance: {} USDC. Max diff: {} USDC",
654
        contract_usdc_balance_after.scaled(Decimals::Six.get_decimals().neg()),
655
        contract_usdc_balance_before.scaled(Decimals::Six.get_decimals().neg()),
656
        max_diff.scaled(Decimals::Six.get_decimals().neg())
657
    );
658
2
}
659

            
660
#[test]
661
2
fn it_doesnt_lose_buffer_if_executed_multiple_times() {
662
2
    let app = InjectiveTestApp::new();
663
2
    let wasm = Wasm::new(&app);
664
2
    let exchange = Exchange::new(&app);
665
2
    let bank = Bank::new(&app);
666
2

            
667
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
668
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
669
2

            
670
2
    let owner = must_init_account_with_funds_and_setting_denoms(
671
2
        &app,
672
2
        &validator,
673
2
        &[
674
2
            initial_coin("1", ETH, Decimals::Eighteen),
675
2
            initial_coin("1", ATOM, Decimals::Six),
676
2
            initial_coin("1_000", USDT, Decimals::Six),
677
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
678
2
        ],
679
2
    );
680
2

            
681
2
    let spot_market_1_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
682
2
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
683
2

            
684
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
685
2

            
686
2
    set_route_and_assert_success(
687
2
        &wasm,
688
2
        &owner,
689
2
        &contr_addr,
690
2
        ETH,
691
2
        ATOM,
692
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
693
2
    );
694
2

            
695
2
    let trader1 = init_rich_account(&app);
696
2
    let trader2 = init_rich_account(&app);
697
2
    let trader3 = init_rich_account(&app);
698
2

            
699
2
    let eth_to_swap = "4.08";
700
2

            
701
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
702
2
        &app,
703
2
        &validator,
704
2
        &[
705
2
            initial_coin(
706
2
                (FPDecimal::must_from_str(eth_to_swap) * FPDecimal::from(100u128)).to_string().as_str(),
707
2
                ETH,
708
2
                Decimals::Eighteen,
709
2
            ),
710
2
            initial_coin("1", INJ, Decimals::Eighteen),
711
2
        ],
712
2
    );
713
2

            
714
2
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
715
2
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
716

            
717
2
    let mut counter = 0;
718
2
    let iterations = 100;
719

            
720
202
    while counter < iterations {
721
200
        create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
722
200
        create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
723
200

            
724
200
        app.increase_time(1);
725
200

            
726
200
        wasm.execute(
727
200
            &contr_addr,
728
200
            &ExecuteMsg::SwapMinOutput {
729
200
                target_denom: ATOM.to_string(),
730
200
                min_output_quantity: FPDecimal::from(906u128),
731
200
            },
732
200
            &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
733
200
            &swapper,
734
200
        )
735
200
        .unwrap();
736
200

            
737
200
        counter += 1
738
    }
739

            
740
2
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
741
2
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
742

            
743
2
    let contract_balance_usdt_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
744
2
    let contract_balance_usdt_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
745
2

            
746
2
    assert!(
747
2
        contract_balance_usdt_after >= contract_balance_usdt_before,
748
        "Contract lost some money after swap. Starting balance: {}, Current balance: {}",
749
        contract_balance_usdt_after,
750
        contract_balance_usdt_before
751
    );
752

            
753
    // single swap with the same values results in < 0.7 USDT earning, so we expected that 100 same swaps
754
    // won't change balance by more than 0.7 * 100 = 70 USDT
755
2
    let max_diff = human_to_dec("0.7", Decimals::Six) * FPDecimal::from(iterations as u128);
756
2

            
757
2
    assert!(
758
2
        are_fpdecimals_approximately_equal(contract_balance_usdt_after, contract_balance_usdt_before, max_diff,),
759
        "Contract balance changed too much. Starting balance: {}, Current balance: {}. Max diff: {}",
760
        contract_balance_usdt_before.scaled(Decimals::Six.get_decimals().neg()),
761
        contract_balance_usdt_after.scaled(Decimals::Six.get_decimals().neg()),
762
        max_diff.scaled(Decimals::Six.get_decimals().neg())
763
    );
764
2
}
765

            
766
/*
767
   This test shows that query overestimates the amount of USDT needed to execute the swap. It seems
768
   that in reality we get a better price when selling ETH than the one returned by query and can
769
   execute the swap with less USDT.
770

            
771
   It's easiest to check by commenting out the query_result assert and running the test. It will
772
   pass and amounts will perfectly match our assertions.
773
*/
774
#[ignore]
775
#[test]
776
fn it_correctly_calculates_required_funds_when_querying_buy_with_minimum_buffer_and_realistic_values() {
777
    let app = InjectiveTestApp::new();
778
    let wasm = Wasm::new(&app);
779
    let exchange = Exchange::new(&app);
780
    let bank = Bank::new(&app);
781

            
782
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
783
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
784

            
785
    let owner = must_init_account_with_funds_and_setting_denoms(
786
        &app,
787
        &validator,
788
        &[
789
            initial_coin("1", ETH, Decimals::Eighteen),
790
            initial_coin("1", ATOM, Decimals::Six),
791
            initial_coin("1_000", USDT, Decimals::Six),
792
            initial_coin("10_000", INJ, Decimals::Eighteen),
793
        ],
794
    );
795

            
796
    let spot_market_1_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
797
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
798

            
799
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("51", USDT, Decimals::Six)]);
800
    set_route_and_assert_success(
801
        &wasm,
802
        &owner,
803
        &contr_addr,
804
        ETH,
805
        ATOM,
806
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
807
    );
808

            
809
    let trader1 = init_rich_account(&app);
810
    let trader2 = init_rich_account(&app);
811
    let trader3 = init_rich_account(&app);
812

            
813
    create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
814
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
815

            
816
    app.increase_time(1);
817

            
818
    let eth_to_swap = "4.08";
819

            
820
    let swapper = must_init_account_with_funds_and_setting_denoms(
821
        &app,
822
        &validator,
823
        &[
824
            initial_coin(eth_to_swap, ETH, Decimals::Eighteen),
825
            initial_coin("1", INJ, Decimals::Eighteen),
826
        ],
827
    );
828

            
829
    let query_result: FPDecimal = wasm
830
        .query(
831
            &contr_addr,
832
            &QueryMsg::GetOutputQuantity {
833
                source_denom: ETH.to_string(),
834
                target_denom: ATOM.to_string(),
835
                from_quantity: human_to_dec(eth_to_swap, Decimals::Eighteen),
836
            },
837
        )
838
        .unwrap();
839

            
840
    assert_eq!(
841
        query_result,
842
        human_to_dec("906.195", Decimals::Six),
843
        "incorrect swap result estimate returned by query"
844
    );
845

            
846
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
847
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
848

            
849
    wasm.execute(
850
        &contr_addr,
851
        &ExecuteMsg::SwapMinOutput {
852
            target_denom: ATOM.to_string(),
853
            min_output_quantity: FPDecimal::from(906u128),
854
        },
855
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
856
        &swapper,
857
    )
858
    .unwrap();
859

            
860
    let from_balance = query_bank_balance(&bank, ETH, swapper.address().as_str());
861
    let to_balance = query_bank_balance(&bank, ATOM, swapper.address().as_str());
862
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
863
    assert_eq!(
864
        to_balance,
865
        human_to_dec("906.195", Decimals::Six),
866
        "swapper did not receive expected amount"
867
    );
868

            
869
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
870
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
871

            
872
    let atom_amount_below_min_tick_size = FPDecimal::must_from_str("0.0005463");
873
    let mut dust_value = atom_amount_below_min_tick_size * human_to_dec("8.89", Decimals::Six);
874

            
875
    let fee_refund = dust_value
876
        * FPDecimal::must_from_str(&format!(
877
            "{}",
878
            DEFAULT_TAKER_FEE * DEFAULT_ATOMIC_MULTIPLIER * DEFAULT_SELF_RELAYING_FEE_PART
879
        ));
880

            
881
    dust_value += fee_refund;
882

            
883
    let expected_contract_usdt_balance = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str()) + dust_value;
884
    let actual_contract_balance = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
885
    let contract_balance_diff = expected_contract_usdt_balance - actual_contract_balance;
886

            
887
    // here the actual difference is 0.000067 USDT, which we attribute differences between decimal precision of Rust/Go and Google Sheets
888
    assert!(
889
        human_to_dec("0.0001", Decimals::Six) - contract_balance_diff > FPDecimal::ZERO,
890
        "contract balance has changed too much after swap"
891
    );
892
}
893

            
894
/*
895
   This test shows that in some edge cases we calculate required funds differently than the chain does.
896
   When estimating balance hold for atomic market order chain doesn't take into account whether sender is
897
   also fee recipient, while we do. This leads to a situation where we estimate required funds to be
898
   lower than what's expected by the chain, which makes the swap fail.
899

            
900
   In this test we skip query estimation and go straight to executing swap.
901
*/
902
#[ignore]
903
#[test]
904
fn it_correctly_calculates_required_funds_when_executing_buy_with_minimum_buffer_and_realistic_values() {
905
    let app = InjectiveTestApp::new();
906
    let wasm = Wasm::new(&app);
907
    let exchange = Exchange::new(&app);
908
    let bank = Bank::new(&app);
909

            
910
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
911
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
912

            
913
    let owner = must_init_account_with_funds_and_setting_denoms(
914
        &app,
915
        &validator,
916
        &[
917
            initial_coin("1", ETH, Decimals::Eighteen),
918
            initial_coin("1", ATOM, Decimals::Six),
919
            initial_coin("1_000", USDT, Decimals::Six),
920
            initial_coin("10_000", INJ, Decimals::Eighteen),
921
        ],
922
    );
923

            
924
    let spot_market_1_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
925
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
926

            
927
    // in reality we need to add at least 49 USDT to the buffer, even if according to contract's calculations 42 USDT would be enough to execute the swap
928
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("42", USDT, Decimals::Six)]);
929
    set_route_and_assert_success(
930
        &wasm,
931
        &owner,
932
        &contr_addr,
933
        ETH,
934
        ATOM,
935
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
936
    );
937

            
938
    let trader1 = init_rich_account(&app);
939
    let trader2 = init_rich_account(&app);
940
    let trader3 = init_rich_account(&app);
941

            
942
    create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
943
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
944

            
945
    app.increase_time(1);
946

            
947
    let eth_to_swap = "4.08";
948

            
949
    let swapper = must_init_account_with_funds_and_setting_denoms(
950
        &app,
951
        &validator,
952
        &[
953
            initial_coin(eth_to_swap, ETH, Decimals::Eighteen),
954
            initial_coin("0.01", INJ, Decimals::Eighteen),
955
        ],
956
    );
957

            
958
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
959
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
960

            
961
    wasm.execute(
962
        &contr_addr,
963
        &ExecuteMsg::SwapMinOutput {
964
            target_denom: ATOM.to_string(),
965
            min_output_quantity: FPDecimal::from(906u128),
966
        },
967
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
968
        &swapper,
969
    )
970
    .unwrap();
971

            
972
    let from_balance = query_bank_balance(&bank, ETH, swapper.address().as_str());
973
    let to_balance = query_bank_balance(&bank, ATOM, swapper.address().as_str());
974
    assert_eq!(from_balance, FPDecimal::ZERO, "some of the original amount wasn't swapped");
975
    assert_eq!(
976
        to_balance,
977
        human_to_dec("906.195", Decimals::Six),
978
        "swapper did not receive expected amount"
979
    );
980

            
981
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
982
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
983

            
984
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
985
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
986

            
987
    assert!(
988
        contract_usdt_balance_after >= contract_usdt_balance_before,
989
        "Contract lost some money after swap. Actual balance: {}, previous balance: {}",
990
        contract_usdt_balance_after,
991
        contract_usdt_balance_before
992
    );
993

            
994
    // contract can earn max of 0.7 USDT, when exchanging ETH worth ~$8150
995
    let max_diff = human_to_dec("0.7", Decimals::Six);
996

            
997
    assert!(
998
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
999
        "Contract balance changed too much. Actual balance: {}, previous balance: {}. Max diff: {}",
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
        max_diff.scaled(Decimals::Six.get_decimals().neg())
    );
}
#[test]
2
fn it_returns_all_funds_if_there_is_not_enough_buffer_realistic_values() {
2
    let app = InjectiveTestApp::new();
2
    let wasm = Wasm::new(&app);
2
    let exchange = Exchange::new(&app);
2
    let bank = Bank::new(&app);
2

            
2
    let validator = app.get_first_validator_signing_account(INJ.to_string(), 1.2f64).unwrap();
2

            
2
    let _signer = must_init_account_with_funds_and_setting_denoms(&app, &validator, &[initial_coin("1", INJ, Decimals::Eighteen)]);
2
    let owner = must_init_account_with_funds_and_setting_denoms(
2
        &app,
2
        &validator,
2
        &[
2
            initial_coin("1", ETH, Decimals::Eighteen),
2
            initial_coin("1", ATOM, Decimals::Six),
2
            initial_coin("1_000", USDT, Decimals::Six),
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
2
        ],
2
    );
2

            
2
    let spot_market_1_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
2
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
2

            
2
    // 41 USDT is just below the amount required to buy required ATOM amount
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("41", USDT, Decimals::Six)]);
2
    set_route_and_assert_success(
2
        &wasm,
2
        &owner,
2
        &contr_addr,
2
        ETH,
2
        ATOM,
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
2
    );
2

            
2
    let trader1 = init_rich_account(&app);
2
    let trader2 = init_rich_account(&app);
2
    let trader3 = init_rich_account(&app);
2

            
2
    create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
2
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
2

            
2
    app.increase_time(1);
2

            
2
    let eth_to_swap = "4.08";
2

            
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
2
        &app,
2
        &validator,
2
        &[
2
            initial_coin(eth_to_swap, ETH, Decimals::Eighteen),
2
            initial_coin("1", INJ, Decimals::Eighteen),
2
        ],
2
    );
2

            
2
    let query_result: RunnerResult<FPDecimal> = wasm.query(
2
        &contr_addr,
2
        &QueryMsg::GetOutputQuantity {
2
            source_denom: ETH.to_string(),
2
            target_denom: ATOM.to_string(),
2
            from_quantity: human_to_dec(eth_to_swap, Decimals::Eighteen),
2
        },
2
    );
2

            
2
    assert!(query_result.is_err(), "query should fail");
2
    assert!(
2
        query_result.unwrap_err().to_string().contains("Swap amount too high"),
        "incorrect error message in query result"
    );
2
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
2
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
2
    let execute_result = wasm.execute(
2
        &contr_addr,
2
        &ExecuteMsg::SwapMinOutput {
2
            target_denom: ATOM.to_string(),
2
            min_output_quantity: FPDecimal::from(906u128),
2
        },
2
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
2
        &swapper,
2
    );
2

            
2
    assert!(execute_result.is_err(), "execute should fail");
2
    let from_balance = query_bank_balance(&bank, ETH, swapper.address().as_str());
2
    let to_balance = query_bank_balance(&bank, ATOM, swapper.address().as_str());
2

            
2
    assert_eq!(
2
        from_balance,
2
        human_to_dec(eth_to_swap, Decimals::Eighteen),
        "source balance changed after failed swap"
    );
2
    assert_eq!(to_balance, FPDecimal::ZERO, "target balance changed after failed swap");
2
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
2
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
2
    assert_eq!(
2
        contract_balances_before[0].amount, contract_balances_after[0].amount,
        "contract balance has changed after failed swap"
    );
2
}