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_ninja_inj_both_side_orders,
6
        create_realistic_atom_usdt_sell_orders_from_spreadsheet, create_realistic_eth_usdt_buy_orders_from_spreadsheet,
7
        create_realistic_eth_usdt_sell_orders_from_spreadsheet, create_realistic_inj_usdt_buy_orders_from_spreadsheet,
8
        create_realistic_inj_usdt_sell_orders_from_spreadsheet, create_realistic_limit_order, create_realistic_usdt_usdc_both_side_orders,
9
        human_to_dec, init_rich_account, init_self_relaying_contract_and_get_address, initial_coin, launch_realistic_atom_usdt_spot_market,
10
        launch_realistic_inj_usdt_spot_market, launch_realistic_ninja_inj_spot_market, launch_realistic_usdt_usdc_spot_market,
11
        launch_realistic_weth_usdt_spot_market, must_init_account_with_funds_and_setting_denoms, query_all_bank_balances, query_bank_balance,
12
        set_route_and_assert_success, str_coin, Decimals, OrderSide, ATOM, ETH, INJ, INJ_2, NINJA, USDC, USDT,
13
    },
14
    types::{FPCoin, SwapEstimationResult},
15
};
16

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

            
21
/*
22
   This test suite focuses on using using realistic values both for spot markets and for orders and
23
   focuses on swaps requesting exact amount. This works as expected apart, when we are converting very
24
   low quantities from a source asset that is orders of magnitude more expensive than the target
25
   asset (as we round up to min quantity tick size).
26

            
27
   ATOM/USDT market parameters was taken from mainnet. ETH/USDT market parameters mirror WETH/USDT
28
   spot market on mainnet. INJ_2/USDT mirrors mainnet's INJ/USDT market (we used a different denom
29
   to avoid mixing balance changes related to gas payments).
30

            
31
   All values used in these tests come from the 2nd, 3rd and 4th tab of this spreadsheet:
32
   https://docs.google.com/spreadsheets/d/1-0epjX580nDO_P2mm1tSjhvjJVppsvrO1BC4_wsBeyA/edit?usp=sharing
33

            
34
   In all tests contract is configured to self-relay trades and thus receive a 60% fee discount.
35
*/
36

            
37
struct Percent<'a>(&'a str);
38

            
39
#[test]
40
2
fn it_swaps_eth_to_get_minimum_exact_amount_of_atom_by_mildly_rounding_up() {
41
2
    exact_two_hop_eth_atom_swap_test_template(human_to_dec("0.01", Decimals::Six), Percent("2200"))
42
2
}
43

            
44
#[test]
45
2
fn it_swaps_eth_to_get_very_low_exact_amount_of_atom_by_heavily_rounding_up() {
46
2
    exact_two_hop_eth_atom_swap_test_template(human_to_dec("0.11", Decimals::Six), Percent("110"))
47
2
}
48

            
49
#[test]
50
2
fn it_swaps_eth_to_get_low_exact_amount_of_atom_by_rounding_up() {
51
2
    exact_two_hop_eth_atom_swap_test_template(human_to_dec("4.12", Decimals::Six), Percent("10"))
52
2
}
53

            
54
#[test]
55
2
fn it_correctly_swaps_eth_to_get_normal_exact_amount_of_atom() {
56
2
    exact_two_hop_eth_atom_swap_test_template(human_to_dec("12.05", Decimals::Six), Percent("1"))
57
2
}
58

            
59
#[test]
60
2
fn it_correctly_swaps_eth_to_get_high_exact_amount_of_atom() {
61
2
    exact_two_hop_eth_atom_swap_test_template(human_to_dec("612", Decimals::Six), Percent("1"))
62
2
}
63

            
64
#[test]
65
2
fn it_correctly_swaps_eth_to_get_very_high_exact_amount_of_atom() {
66
2
    let app = InjectiveTestApp::new();
67
2
    let wasm = Wasm::new(&app);
68
2
    let exchange = Exchange::new(&app);
69
2
    let bank = Bank::new(&app);
70
2

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

            
74
2
    let owner = must_init_account_with_funds_and_setting_denoms(
75
2
        &app,
76
2
        &validator,
77
2
        &[
78
2
            initial_coin("1", ETH, Decimals::Eighteen),
79
2
            initial_coin("1", ATOM, Decimals::Six),
80
2
            initial_coin("1_000", USDT, Decimals::Six),
81
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
82
2
        ],
83
2
    );
84
2

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

            
88
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
89
2
    set_route_and_assert_success(
90
2
        &wasm,
91
2
        &owner,
92
2
        &contr_addr,
93
2
        ETH,
94
2
        ATOM,
95
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
96
2
    );
97
2

            
98
2
    let trader1 = init_rich_account(&app);
99
2
    let trader2 = init_rich_account(&app);
100
2
    let trader3 = init_rich_account(&app);
101
2

            
102
2
    create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
103
2
    create_realistic_limit_order(
104
2
        &app,
105
2
        &trader1,
106
2
        &spot_market_1_id,
107
2
        OrderSide::Buy,
108
2
        "2137.2",
109
2
        "2.78",
110
2
        Decimals::Eighteen,
111
2
        Decimals::Six,
112
2
    ); //order not present in the spreadsheet
113
2

            
114
2
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
115
2
    create_realistic_limit_order(
116
2
        &app,
117
2
        &trader1,
118
2
        &spot_market_2_id,
119
2
        OrderSide::Sell,
120
2
        "9.11",
121
2
        "321.11",
122
2
        Decimals::Six,
123
2
        Decimals::Six,
124
2
    ); //order not present in the spreadsheet
125
2

            
126
2
    app.increase_time(1);
127
2

            
128
2
    let eth_to_swap = "4.4";
129
2

            
130
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
131
2
        &app,
132
2
        &validator,
133
2
        &[
134
2
            initial_coin(eth_to_swap, ETH, Decimals::Eighteen),
135
2
            initial_coin("1", INJ, Decimals::Eighteen),
136
2
        ],
137
2
    );
138
2

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

            
142
2
    let exact_quantity_to_receive = human_to_dec("1014.19", Decimals::Six);
143
2

            
144
2
    let query_result: SwapEstimationResult = wasm
145
2
        .query(
146
2
            &contr_addr,
147
2
            &QueryMsg::GetInputQuantity {
148
2
                source_denom: ETH.to_string(),
149
2
                target_denom: ATOM.to_string(),
150
2
                to_quantity: exact_quantity_to_receive,
151
2
            },
152
2
        )
153
2
        .unwrap();
154
2

            
155
2
    wasm.execute(
156
2
        &contr_addr,
157
2
        &ExecuteMsg::SwapExactOutput {
158
2
            target_denom: ATOM.to_string(),
159
2
            target_output_quantity: exact_quantity_to_receive,
160
2
        },
161
2
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
162
2
        &swapper,
163
2
    )
164
2
    .unwrap();
165
2

            
166
2
    let expected_difference = human_to_dec(eth_to_swap, Decimals::Eighteen) - query_result.result_quantity;
167
2
    let swapper_eth_balance_after = query_bank_balance(&bank, ETH, swapper.address().as_str());
168
2
    let swapper_atom_balance_after = query_bank_balance(&bank, ATOM, swapper.address().as_str());
169
2

            
170
2
    assert_eq!(swapper_eth_balance_after, expected_difference, "wrong amount of ETH was exchanged");
171

            
172
2
    assert!(
173
2
        swapper_atom_balance_after >= exact_quantity_to_receive,
174
        "swapper got less than exact amount required -> expected: {} ATOM, actual: {} ATOM",
175
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
176
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg())
177
    );
178

            
179
2
    let one_percent_diff = exact_quantity_to_receive * FPDecimal::must_from_str("0.01");
180
2

            
181
2
    assert!(
182
2
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
183
        "swapper did not receive expected exact amount +/- 1% -> expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
184
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
185
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg()),
186
        one_percent_diff.scaled(Decimals::Six.get_decimals().neg())
187
    );
188

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

            
192
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
193
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
194
2

            
195
2
    assert!(
196
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
197
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
198
    );
199

            
200
    // contract is allowed to earn extra 0.73 USDT from the swap of ~$8450 worth of ETH
201
2
    let max_diff = human_to_dec("0.8", Decimals::Six);
202
2

            
203
2
    assert!(
204
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
205
        "Contract balance changed too much. Actual balance: {}, previous balance: {}. Max diff: {}",
206
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
207
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
208
        max_diff.scaled(Decimals::Six.get_decimals().neg())
209
    );
210
2
}
211

            
212
#[test]
213
2
fn it_swaps_inj_to_get_minimum_exact_amount_of_atom_by_mildly_rounding_up() {
214
2
    exact_two_hop_inj_atom_swap_test_template(human_to_dec("0.01", Decimals::Six), Percent("0"))
215
2
}
216

            
217
#[test]
218
2
fn it_swaps_inj_to_get_very_low_exact_amount_of_atom() {
219
2
    exact_two_hop_inj_atom_swap_test_template(human_to_dec("0.11", Decimals::Six), Percent("0"))
220
2
}
221

            
222
#[test]
223
2
fn it_swaps_inj_to_get_low_exact_amount_of_atom() {
224
2
    exact_two_hop_inj_atom_swap_test_template(human_to_dec("4.12", Decimals::Six), Percent("0"))
225
2
}
226

            
227
#[test]
228
2
fn it_correctly_swaps_inj_to_get_normal_exact_amount_of_atom() {
229
2
    exact_two_hop_inj_atom_swap_test_template(human_to_dec("12.05", Decimals::Six), Percent("0"))
230
2
}
231

            
232
#[test]
233
2
fn it_correctly_swaps_inj_to_get_high_exact_amount_of_atom() {
234
2
    exact_two_hop_inj_atom_swap_test_template(human_to_dec("612", Decimals::Six), Percent("0.01"))
235
2
}
236

            
237
#[test]
238
2
fn it_correctly_swaps_inj_to_get_very_high_exact_amount_of_atom() {
239
2
    let app = InjectiveTestApp::new();
240
2
    let wasm = Wasm::new(&app);
241
2
    let exchange = Exchange::new(&app);
242
2
    let bank = Bank::new(&app);
243
2

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

            
247
2
    let owner = must_init_account_with_funds_and_setting_denoms(
248
2
        &app,
249
2
        &validator,
250
2
        &[
251
2
            initial_coin("1", ETH, Decimals::Eighteen),
252
2
            initial_coin("1", ATOM, Decimals::Six),
253
2
            initial_coin("1_000", USDT, Decimals::Six),
254
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
255
2
            initial_coin("10_000", INJ_2, Decimals::Eighteen),
256
2
        ],
257
2
    );
258
2

            
259
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
260
2
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
261
2

            
262
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
263
2
    set_route_and_assert_success(
264
2
        &wasm,
265
2
        &owner,
266
2
        &contr_addr,
267
2
        INJ_2,
268
2
        ATOM,
269
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
270
2
    );
271
2

            
272
2
    let trader1 = init_rich_account(&app);
273
2
    let trader2 = init_rich_account(&app);
274
2
    let trader3 = init_rich_account(&app);
275
2

            
276
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
277
2
    create_realistic_limit_order(
278
2
        &app,
279
2
        &trader1,
280
2
        &spot_market_1_id,
281
2
        OrderSide::Buy,
282
2
        "8.99",
283
2
        "280.2",
284
2
        Decimals::Eighteen,
285
2
        Decimals::Six,
286
2
    ); //order not present in the spreadsheet
287
2

            
288
2
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
289
2
    create_realistic_limit_order(
290
2
        &app,
291
2
        &trader1,
292
2
        &spot_market_2_id,
293
2
        OrderSide::Sell,
294
2
        "9.11",
295
2
        "321.11",
296
2
        Decimals::Six,
297
2
        Decimals::Six,
298
2
    ); //order not present in the spreadsheet
299
2

            
300
2
    app.increase_time(1);
301
2

            
302
2
    let inj_to_swap = "1100.1";
303
2

            
304
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
305
2
        &app,
306
2
        &validator,
307
2
        &[
308
2
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
309
2
            initial_coin("1", INJ, Decimals::Eighteen),
310
2
        ],
311
2
    );
312
2

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

            
316
2
    let exact_quantity_to_receive = human_to_dec("1010.12", Decimals::Six);
317
2
    let max_diff_percentage = Percent("0.01");
318
2

            
319
2
    let query_result: SwapEstimationResult = wasm
320
2
        .query(
321
2
            &contr_addr,
322
2
            &QueryMsg::GetInputQuantity {
323
2
                source_denom: INJ_2.to_string(),
324
2
                target_denom: ATOM.to_string(),
325
2
                to_quantity: exact_quantity_to_receive,
326
2
            },
327
2
        )
328
2
        .unwrap();
329
2

            
330
2
    wasm.execute(
331
2
        &contr_addr,
332
2
        &ExecuteMsg::SwapExactOutput {
333
2
            target_denom: ATOM.to_string(),
334
2
            target_output_quantity: exact_quantity_to_receive,
335
2
        },
336
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
337
2
        &swapper,
338
2
    )
339
2
    .unwrap();
340
2

            
341
2
    let expected_difference = human_to_dec(inj_to_swap, Decimals::Eighteen) - query_result.result_quantity;
342
2
    let swapper_inj_balance_after = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
343
2
    let swapper_atom_balance_after = query_bank_balance(&bank, ATOM, swapper.address().as_str());
344
2

            
345
2
    assert_eq!(swapper_inj_balance_after, expected_difference, "wrong amount of INJ was exchanged");
346

            
347
2
    assert!(
348
2
        swapper_atom_balance_after >= exact_quantity_to_receive,
349
        "swapper got less than exact amount required -> expected: {} ATOM, actual: {} ATOM",
350
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
351
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg())
352
    );
353

            
354
2
    let one_percent_diff = exact_quantity_to_receive * (FPDecimal::must_from_str(max_diff_percentage.0) / FPDecimal::from(100u128));
355
2

            
356
2
    assert!(
357
2
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
358
        "swapper did not receive expected exact ATOM amount +/- {}% -> expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
359
        max_diff_percentage.0,
360
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
361
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg()),
362
        one_percent_diff.scaled(Decimals::Six.get_decimals().neg())
363
    );
364

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

            
368
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
369
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
370
2

            
371
2
    assert!(
372
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
373
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
374
    );
375

            
376
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8150 worth of INJ
377
2
    let max_diff = human_to_dec("0.7", Decimals::Six);
378
2

            
379
2
    assert!(
380
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
381
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
382
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
383
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
384
        max_diff.scaled(Decimals::Six.get_decimals().neg())
385
    );
386
2
}
387

            
388
#[test]
389
2
fn it_swaps_inj_to_get_minimum_exact_amount_of_eth() {
390
2
    exact_two_hop_inj_eth_swap_test_template(human_to_dec("0.001", Decimals::Eighteen), Percent("0"))
391
2
}
392

            
393
#[test]
394
2
fn it_swaps_inj_to_get_low_exact_amount_of_eth() {
395
2
    exact_two_hop_inj_eth_swap_test_template(human_to_dec("0.012", Decimals::Eighteen), Percent("0"))
396
2
}
397

            
398
#[test]
399
2
fn it_swaps_inj_to_get_normal_exact_amount_of_eth() {
400
2
    exact_two_hop_inj_eth_swap_test_template(human_to_dec("0.1", Decimals::Eighteen), Percent("0"))
401
2
}
402

            
403
#[test]
404
2
fn it_swaps_inj_to_get_high_exact_amount_of_eth() {
405
2
    exact_two_hop_inj_eth_swap_test_template(human_to_dec("3.1", Decimals::Eighteen), Percent("0"))
406
2
}
407

            
408
#[test]
409
2
fn it_swaps_inj_to_get_very_high_exact_amount_of_eth() {
410
2
    let app = InjectiveTestApp::new();
411
2
    let wasm = Wasm::new(&app);
412
2
    let exchange = Exchange::new(&app);
413
2
    let bank = Bank::new(&app);
414
2

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

            
418
2
    let owner = must_init_account_with_funds_and_setting_denoms(
419
2
        &app,
420
2
        &validator,
421
2
        &[
422
2
            initial_coin("1", ETH, Decimals::Eighteen),
423
2
            initial_coin("1_000", USDT, Decimals::Six),
424
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
425
2
            initial_coin("10_000", INJ_2, Decimals::Eighteen),
426
2
        ],
427
2
    );
428
2

            
429
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
430
2
    let spot_market_2_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
431
2

            
432
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
433
2
    set_route_and_assert_success(
434
2
        &wasm,
435
2
        &owner,
436
2
        &contr_addr,
437
2
        INJ_2,
438
2
        ETH,
439
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
440
2
    );
441
2

            
442
2
    let trader1 = init_rich_account(&app);
443
2
    let trader2 = init_rich_account(&app);
444
2
    let trader3 = init_rich_account(&app);
445
2

            
446
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
447
2
    create_realistic_limit_order(
448
2
        &app,
449
2
        &trader1,
450
2
        &spot_market_1_id,
451
2
        OrderSide::Buy,
452
2
        "8.99",
453
2
        "1882.001",
454
2
        Decimals::Eighteen,
455
2
        Decimals::Six,
456
2
    ); //order not present in the spreadsheet
457
2
    create_realistic_eth_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
458
2
    create_realistic_limit_order(
459
2
        &app,
460
2
        &trader3,
461
2
        &spot_market_2_id,
462
2
        OrderSide::Sell,
463
2
        "2123.1",
464
2
        "18.11",
465
2
        Decimals::Eighteen,
466
2
        Decimals::Six,
467
2
    ); //order not present in the spreadsheet
468
2

            
469
2
    app.increase_time(1);
470
2

            
471
2
    let inj_to_swap = "2855.259";
472
2
    let exact_quantity_to_receive = human_to_dec("11.2", Decimals::Eighteen);
473
2

            
474
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
475
2
        &app,
476
2
        &validator,
477
2
        &[
478
2
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
479
2
            initial_coin("1", INJ, Decimals::Eighteen),
480
2
        ],
481
2
    );
482
2

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

            
486
2
    let query_result: SwapEstimationResult = wasm
487
2
        .query(
488
2
            &contr_addr,
489
2
            &QueryMsg::GetInputQuantity {
490
2
                source_denom: INJ_2.to_string(),
491
2
                target_denom: ETH.to_string(),
492
2
                to_quantity: exact_quantity_to_receive,
493
2
            },
494
2
        )
495
2
        .unwrap();
496
2

            
497
2
    wasm.execute(
498
2
        &contr_addr,
499
2
        &ExecuteMsg::SwapExactOutput {
500
2
            target_denom: ETH.to_string(),
501
2
            target_output_quantity: exact_quantity_to_receive,
502
2
        },
503
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
504
2
        &swapper,
505
2
    )
506
2
    .unwrap();
507
2

            
508
2
    let expected_difference = human_to_dec(inj_to_swap, Decimals::Eighteen) - query_result.result_quantity;
509
2
    let swapper_inj_balance_after = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
510
2
    let swapper_atom_balance_after = query_bank_balance(&bank, ETH, swapper.address().as_str());
511
2

            
512
2
    assert_eq!(swapper_inj_balance_after, expected_difference, "wrong amount of INJ was exchanged");
513

            
514
2
    assert!(
515
2
        swapper_atom_balance_after >= exact_quantity_to_receive,
516
        "swapper got less than exact amount required -> expected: {} ETH, actual: {} ETH",
517
        exact_quantity_to_receive.scaled(Decimals::Eighteen.get_decimals().neg()),
518
        swapper_atom_balance_after.scaled(Decimals::Eighteen.get_decimals().neg())
519
    );
520

            
521
2
    let max_diff_percent = Percent("0");
522
2
    let one_percent_diff = exact_quantity_to_receive * (FPDecimal::must_from_str(max_diff_percent.0) / FPDecimal::from(100u128));
523
2

            
524
2
    assert!(
525
2
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
526
        "swapper did not receive expected exact ETH amount +/- {}% -> expected: {} ETH, actual: {} ETH, max diff: {} ETH",
527
        max_diff_percent.0,
528
        exact_quantity_to_receive.scaled(Decimals::Eighteen.get_decimals().neg()),
529
        swapper_atom_balance_after.scaled(Decimals::Eighteen.get_decimals().neg()),
530
        one_percent_diff.scaled(Decimals::Eighteen.get_decimals().neg())
531
    );
532

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

            
536
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
537
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
538
2

            
539
2
    assert!(
540
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
541
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
542
    );
543

            
544
    // contract is allowed to earn extra 1.6 USDT from the swap of ~$23500 worth of INJ
545
2
    let max_diff = human_to_dec("1.6", Decimals::Six);
546
2

            
547
2
    assert!(
548
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
549
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
550
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
551
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
552
        max_diff.scaled(Decimals::Six.get_decimals().neg())
553
    );
554
2
}
555

            
556
#[test]
557
2
fn it_correctly_swaps_between_markets_using_different_quote_assets_self_relaying() {
558
2
    let app = InjectiveTestApp::new();
559
2
    let wasm = Wasm::new(&app);
560
2
    let exchange = Exchange::new(&app);
561
2
    let bank = Bank::new(&app);
562
2

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

            
566
2
    let owner = must_init_account_with_funds_and_setting_denoms(
567
2
        &app,
568
2
        &validator,
569
2
        &[
570
2
            initial_coin("1_000", USDT, Decimals::Six),
571
2
            initial_coin("1_000", USDC, Decimals::Six),
572
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
573
2
            initial_coin("1", INJ_2, Decimals::Eighteen),
574
2
        ],
575
2
    );
576
2

            
577
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
578
2
    let spot_market_2_id = launch_realistic_usdt_usdc_spot_market(&exchange, &owner);
579
2

            
580
2
    let contr_addr = init_self_relaying_contract_and_get_address(
581
2
        &wasm,
582
2
        &owner,
583
2
        &[str_coin("10", USDC, Decimals::Six), str_coin("500", USDT, Decimals::Six)],
584
2
    );
585
2
    set_route_and_assert_success(
586
2
        &wasm,
587
2
        &owner,
588
2
        &contr_addr,
589
2
        INJ_2,
590
2
        USDC,
591
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
592
2
    );
593
2

            
594
2
    let trader1 = init_rich_account(&app);
595
2
    let trader2 = init_rich_account(&app);
596
2

            
597
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
598
2
    create_realistic_usdt_usdc_both_side_orders(&app, &spot_market_2_id, &trader1);
599
2

            
600
2
    app.increase_time(1);
601
2

            
602
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
603
2
        &app,
604
2
        &validator,
605
2
        &[initial_coin("1", INJ, Decimals::Eighteen), initial_coin("1", INJ_2, Decimals::Eighteen)],
606
2
    );
607
2

            
608
2
    let inj_to_swap = "1";
609
2
    let to_output_quantity = human_to_dec("8", Decimals::Six);
610
2

            
611
2
    let mut query_result: SwapEstimationResult = wasm
612
2
        .query(
613
2
            &contr_addr,
614
2
            &QueryMsg::GetInputQuantity {
615
2
                to_quantity: to_output_quantity,
616
2
                source_denom: INJ_2.to_string(),
617
2
                target_denom: USDC.to_string(),
618
2
            },
619
2
        )
620
2
        .unwrap();
621
2

            
622
2
    let expected_input_quantity = human_to_dec("0.903", Decimals::Eighteen);
623
2
    let max_diff = human_to_dec("0.001", Decimals::Eighteen);
624
2

            
625
2
    assert!(
626
2
        are_fpdecimals_approximately_equal(expected_input_quantity, query_result.result_quantity, max_diff),
627
        "incorrect swap result estimate returned by query. Expected: {} INJ, actual: {} INJ, max diff: {} INJ",
628
        expected_input_quantity.scaled(Decimals::Eighteen.get_decimals().neg()),
629
        query_result.result_quantity.scaled(Decimals::Eighteen.get_decimals().neg()),
630
        max_diff.scaled(Decimals::Eighteen.get_decimals().neg())
631
    );
632

            
633
2
    let mut expected_fees = vec![
634
2
        FPCoin {
635
2
            amount: human_to_dec("0.013365", Decimals::Six),
636
2
            denom: USDT.to_string(),
637
2
        },
638
2
        FPCoin {
639
2
            amount: human_to_dec("0.01332", Decimals::Six),
640
2
            denom: USDC.to_string(),
641
2
        },
642
2
    ];
643
2

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

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

            
650
2
    wasm.execute(
651
2
        &contr_addr,
652
2
        &ExecuteMsg::SwapExactOutput {
653
2
            target_denom: USDC.to_string(),
654
2
            target_output_quantity: to_output_quantity,
655
2
        },
656
2
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
657
2
        &swapper,
658
2
    )
659
2
    .unwrap();
660
2

            
661
2
    let from_balance = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
662
2
    let to_balance = query_bank_balance(&bank, USDC, swapper.address().as_str());
663
2

            
664
2
    let expected_inj_leftover = human_to_dec(inj_to_swap, Decimals::Eighteen) - expected_input_quantity;
665
2
    assert_eq!(from_balance, expected_inj_leftover, "incorrect original amount was left after swap");
666

            
667
2
    let expected_amount = human_to_dec("8.00711", Decimals::Six);
668
2

            
669
2
    assert_eq!(
670
        to_balance,
671
        expected_amount,
672
        "Swapper received less than expected minimum amount. Expected: {} USDC, actual: {} USDC",
673
        expected_amount.scaled(Decimals::Six.get_decimals().neg()),
674
        to_balance.scaled(Decimals::Six.get_decimals().neg()),
675
    );
676

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

            
680
    // let's check contract's USDT balance
681
2
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
682
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
683
2

            
684
2
    assert!(
685
2
        contract_usdt_balance_after >= contract_usdt_balance_before,
686
        "Contract lost some money after swap. Actual balance: {} USDT, previous balance: {} USDT",
687
        contract_usdt_balance_after,
688
        contract_usdt_balance_before
689
    );
690

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

            
694
2
    assert!(
695
2
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
696
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
697
        contract_usdt_balance_after.scaled(Decimals::Six.get_decimals().neg()),
698
        contract_usdt_balance_before.scaled(Decimals::Six.get_decimals().neg()),
699
        max_diff.scaled(Decimals::Six.get_decimals().neg())
700
    );
701

            
702
    // let's check contract's USDC balance
703
2
    let contract_usdc_balance_before = FPDecimal::must_from_str(contract_balances_before[1].amount.as_str());
704
2
    let contract_usdc_balance_after = FPDecimal::must_from_str(contract_balances_after[1].amount.as_str());
705
2

            
706
2
    assert!(
707
2
        contract_usdc_balance_after >= contract_usdc_balance_before,
708
        "Contract lost some money after swap. Actual balance: {} USDC, previous balance: {} USDC",
709
        contract_usdc_balance_after,
710
        contract_usdc_balance_before
711
    );
712

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

            
716
2
    assert!(
717
2
        are_fpdecimals_approximately_equal(contract_usdc_balance_after, contract_usdc_balance_before, max_diff,),
718
        "Contract balance changed too much. Actual balance: {} USDC, previous balance: {} USDC. Max diff: {} USDC",
719
        contract_usdc_balance_after.scaled(Decimals::Six.get_decimals().neg()),
720
        contract_usdc_balance_before.scaled(Decimals::Six.get_decimals().neg()),
721
        max_diff.scaled(Decimals::Six.get_decimals().neg())
722
    );
723
2
}
724

            
725
#[test]
726
2
fn it_correctly_swaps_between_markets_using_different_quote_assets_self_relaying_ninja() {
727
2
    let app = InjectiveTestApp::new();
728
2
    let wasm = Wasm::new(&app);
729
2
    let exchange = Exchange::new(&app);
730
2
    let bank = Bank::new(&app);
731
2

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

            
735
2
    let owner = must_init_account_with_funds_and_setting_denoms(
736
2
        &app,
737
2
        &validator,
738
2
        &[
739
2
            initial_coin("1_000", USDT, Decimals::Six),
740
2
            initial_coin("1_000", USDC, Decimals::Six),
741
2
            initial_coin("1_000", NINJA, Decimals::Six),
742
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
743
2
            initial_coin("101", INJ_2, Decimals::Eighteen),
744
2
        ],
745
2
    );
746
2

            
747
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
748
2
    let spot_market_2_id = launch_realistic_ninja_inj_spot_market(&exchange, &owner);
749
2

            
750
2
    let contr_addr = init_self_relaying_contract_and_get_address(
751
2
        &wasm,
752
2
        &owner,
753
2
        &[
754
2
            str_coin("100", INJ_2, Decimals::Eighteen),
755
2
            str_coin("10", USDC, Decimals::Six),
756
2
            str_coin("500", USDT, Decimals::Six),
757
2
        ],
758
2
    );
759
2
    set_route_and_assert_success(
760
2
        &wasm,
761
2
        &owner,
762
2
        &contr_addr,
763
2
        USDT,
764
2
        NINJA,
765
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
766
2
    );
767
2

            
768
2
    let trader1 = init_rich_account(&app);
769
2

            
770
2
    create_realistic_inj_usdt_sell_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1);
771
2
    create_ninja_inj_both_side_orders(&app, &spot_market_2_id, &trader1);
772
2

            
773
2
    app.increase_time(1);
774
2

            
775
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
776
2
        &app,
777
2
        &validator,
778
2
        &[initial_coin("1", INJ, Decimals::Eighteen), initial_coin("100000", USDT, Decimals::Six)],
779
2
    );
780
2

            
781
2
    let usdt_to_swap = "100000";
782
2
    let to_output_quantity = human_to_dec("501000", Decimals::Six);
783
2

            
784
2
    let from_balance_before = query_bank_balance(&bank, USDT, swapper.address().as_str());
785
2
    let to_balance_before = query_bank_balance(&bank, NINJA, swapper.address().as_str());
786
2

            
787
2
    wasm.execute(
788
2
        &contr_addr,
789
2
        &ExecuteMsg::SwapExactOutput {
790
2
            target_denom: NINJA.to_string(),
791
2
            target_output_quantity: to_output_quantity,
792
2
        },
793
2
        &[str_coin(usdt_to_swap, USDT, Decimals::Six)],
794
2
        &swapper,
795
2
    )
796
2
    .unwrap();
797
2

            
798
2
    let from_balance_after = query_bank_balance(&bank, USDT, swapper.address().as_str());
799
2
    let to_balance_after = query_bank_balance(&bank, NINJA, swapper.address().as_str());
800
2

            
801
2
    // from 100000 USDT -> 96201.062127 USDT = 3798.937872 USDT
802
2
    let expected_from_balance_before = human_to_dec("100000", Decimals::Six);
803
2
    let expected_from_balance_after = human_to_dec("96201.062127", Decimals::Six);
804
2

            
805
2
    // from 0 NINJA to 501000 NINJA
806
2
    let expected_to_balance_before = human_to_dec("0", Decimals::Six);
807
2
    let expected_to_balance_after = human_to_dec("501000", Decimals::Six);
808
2

            
809
2
    assert_eq!(
810
        from_balance_before, expected_from_balance_before,
811
        "incorrect original amount was left after swap"
812
    );
813
2
    assert_eq!(to_balance_before, expected_to_balance_before, "incorrect target amount after swap");
814
2
    assert_eq!(
815
        from_balance_after, expected_from_balance_after,
816
        "incorrect original amount was left after swap"
817
    );
818
2
    assert_eq!(to_balance_after, expected_to_balance_after, "incorrect target amount after swap");
819
2
}
820

            
821
#[test]
822
2
fn it_doesnt_lose_buffer_if_exact_swap_of_eth_to_atom_is_executed_multiple_times() {
823
2
    let app = InjectiveTestApp::new();
824
2
    let wasm = Wasm::new(&app);
825
2
    let exchange = Exchange::new(&app);
826
2
    let bank = Bank::new(&app);
827
2

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

            
831
2
    let owner = must_init_account_with_funds_and_setting_denoms(
832
2
        &app,
833
2
        &validator,
834
2
        &[
835
2
            initial_coin("1", ETH, Decimals::Eighteen),
836
2
            initial_coin("1", ATOM, Decimals::Six),
837
2
            initial_coin("1_000", USDT, Decimals::Six),
838
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
839
2
        ],
840
2
    );
841
2

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

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

            
847
2
    set_route_and_assert_success(
848
2
        &wasm,
849
2
        &owner,
850
2
        &contr_addr,
851
2
        ETH,
852
2
        ATOM,
853
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
854
2
    );
855
2

            
856
2
    let trader1 = init_rich_account(&app);
857
2
    let trader2 = init_rich_account(&app);
858
2
    let trader3 = init_rich_account(&app);
859
2

            
860
2
    let eth_to_swap = "4.08";
861
2
    let iterations = 100i128;
862
2

            
863
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
864
2
        &app,
865
2
        &validator,
866
2
        &[
867
2
            initial_coin(
868
2
                (FPDecimal::must_from_str(eth_to_swap) * FPDecimal::from(iterations)).to_string().as_str(),
869
2
                ETH,
870
2
                Decimals::Eighteen,
871
2
            ),
872
2
            initial_coin("1", INJ, Decimals::Eighteen),
873
2
        ],
874
2
    );
875
2

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

            
879
2
    let mut counter = 0;
880

            
881
202
    while counter < iterations {
882
200
        create_realistic_eth_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
883
200
        create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
884
200

            
885
200
        app.increase_time(1);
886
200

            
887
200
        wasm.execute(
888
200
            &contr_addr,
889
200
            &ExecuteMsg::SwapExactOutput {
890
200
                target_denom: ATOM.to_string(),
891
200
                target_output_quantity: human_to_dec("906", Decimals::Six),
892
200
            },
893
200
            &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
894
200
            &swapper,
895
200
        )
896
200
        .unwrap();
897
200

            
898
200
        counter += 1
899
    }
900

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

            
904
2
    let contract_balance_usdt_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
905
2
    let contract_balance_usdt_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
906
2

            
907
2
    assert!(
908
2
        contract_balance_usdt_after >= contract_balance_usdt_before,
909
        "Contract lost some money after swap. Starting balance: {contract_balance_usdt_after}, Current balance: {contract_balance_usdt_before}",
910
    );
911

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

            
916
2
    assert!(
917
2
        are_fpdecimals_approximately_equal(contract_balance_usdt_after, contract_balance_usdt_before, max_diff,),
918
        "Contract balance changed too much. Starting balance: {}, Current balance: {}. Max diff: {}",
919
        contract_balance_usdt_before.scaled(Decimals::Six.get_decimals().neg()),
920
        contract_balance_usdt_after.scaled(Decimals::Six.get_decimals().neg()),
921
        max_diff.scaled(Decimals::Six.get_decimals().neg())
922
    );
923
2
}
924

            
925
#[test]
926
2
fn it_reverts_when_funds_provided_are_below_required_to_get_exact_amount() {
927
2
    let app = InjectiveTestApp::new();
928
2
    let wasm = Wasm::new(&app);
929
2
    let exchange = Exchange::new(&app);
930
2
    let bank = Bank::new(&app);
931
2

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

            
935
2
    let owner = must_init_account_with_funds_and_setting_denoms(
936
2
        &app,
937
2
        &validator,
938
2
        &[
939
2
            initial_coin("1", ETH, Decimals::Eighteen),
940
2
            initial_coin("1", ATOM, Decimals::Six),
941
2
            initial_coin("1_000", USDT, Decimals::Six),
942
2
            initial_coin("10_000", INJ, Decimals::Eighteen),
943
2
            initial_coin("10_000", INJ_2, Decimals::Eighteen),
944
2
        ],
945
2
    );
946
2

            
947
2
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
948
2
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
949
2

            
950
2
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
951
2
    set_route_and_assert_success(
952
2
        &wasm,
953
2
        &owner,
954
2
        &contr_addr,
955
2
        INJ_2,
956
2
        ATOM,
957
2
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
958
2
    );
959
2

            
960
2
    let trader1 = init_rich_account(&app);
961
2
    let trader2 = init_rich_account(&app);
962
2
    let trader3 = init_rich_account(&app);
963
2

            
964
2
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
965
2
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
966
2

            
967
2
    app.increase_time(1);
968
2

            
969
2
    let inj_to_swap = "608";
970
2

            
971
2
    let swapper = must_init_account_with_funds_and_setting_denoms(
972
2
        &app,
973
2
        &validator,
974
2
        &[
975
2
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
976
2
            initial_coin("1", INJ, Decimals::Eighteen),
977
2
        ],
978
2
    );
979
2

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

            
983
2
    let exact_quantity_to_receive = human_to_dec("600", Decimals::Six);
984
2
    let swapper_inj_balance_before = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
985
2

            
986
2
    let _: SwapEstimationResult = wasm
987
2
        .query(
988
2
            &contr_addr,
989
2
            &QueryMsg::GetInputQuantity {
990
2
                source_denom: INJ_2.to_string(),
991
2
                target_denom: ATOM.to_string(),
992
2
                to_quantity: exact_quantity_to_receive,
993
2
            },
994
2
        )
995
2
        .unwrap();
996
2

            
997
2
    let execute_result = wasm
998
2
        .execute(
999
2
            &contr_addr,
2
            &ExecuteMsg::SwapExactOutput {
2
                target_denom: ATOM.to_string(),
2
                target_output_quantity: exact_quantity_to_receive,
2
            },
2
            &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
2
            &swapper,
2
        )
2
        .unwrap_err();
2

            
2
    assert!(
2
        execute_result
2
            .to_string()
2
            .contains("Provided amount of 608000000000000000000 is below required amount of 609714000000000000000"),
        "wrong error message"
    );
2
    let swapper_inj_balance_after = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
2
    let swapper_atom_balance_after = query_bank_balance(&bank, ATOM, swapper.address().as_str());
2

            
2
    assert_eq!(swapper_inj_balance_before, swapper_inj_balance_after, "some amount of INJ was exchanged");
2
    assert_eq!(FPDecimal::ZERO, swapper_atom_balance_after, "swapper received some ATOM");
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
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
2
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
2

            
2
    assert_eq!(
        contract_usdt_balance_after, contract_usdt_balance_before,
        "Contract's balance changed after failed swap",
    );
2
}
// TEST TEMPLATES
// source much more expensive than target
10
fn exact_two_hop_eth_atom_swap_test_template(exact_quantity_to_receive: FPDecimal, max_diff_percentage: Percent) {
10
    let app = InjectiveTestApp::new();
10
    let wasm = Wasm::new(&app);
10
    let exchange = Exchange::new(&app);
10
    let bank = Bank::new(&app);
10

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

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

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

            
10
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
10
    set_route_and_assert_success(
10
        &wasm,
10
        &owner,
10
        &contr_addr,
10
        ETH,
10
        ATOM,
10
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
10
    );
10

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

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

            
10
    app.increase_time(1);
10

            
10
    let eth_to_swap = "4.08";
10

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

            
10
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
10
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
10
    let query_result: SwapEstimationResult = wasm
10
        .query(
10
            &contr_addr,
10
            &QueryMsg::GetInputQuantity {
10
                source_denom: ETH.to_string(),
10
                target_denom: ATOM.to_string(),
10
                to_quantity: exact_quantity_to_receive,
10
            },
10
        )
10
        .unwrap();
10

            
10
    wasm.execute(
10
        &contr_addr,
10
        &ExecuteMsg::SwapExactOutput {
10
            target_denom: ATOM.to_string(),
10
            target_output_quantity: exact_quantity_to_receive,
10
        },
10
        &[str_coin(eth_to_swap, ETH, Decimals::Eighteen)],
10
        &swapper,
10
    )
10
    .unwrap();
10

            
10
    let expected_difference = human_to_dec(eth_to_swap, Decimals::Eighteen) - query_result.result_quantity;
10
    let swapper_eth_balance_after = query_bank_balance(&bank, ETH, swapper.address().as_str());
10
    let swapper_atom_balance_after = query_bank_balance(&bank, ATOM, swapper.address().as_str());
10

            
10
    assert_eq!(swapper_eth_balance_after, expected_difference, "wrong amount of ETH was exchanged");
10
    let one_percent_diff = exact_quantity_to_receive * (FPDecimal::must_from_str(max_diff_percentage.0) / FPDecimal::from(100u128));
10

            
10
    assert!(
10
        swapper_atom_balance_after >= exact_quantity_to_receive,
        "swapper got less than exact amount required -> expected: {} ATOM, actual: {} ATOM",
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg())
    );
10
    assert!(
10
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
        "swapper did not receive expected exact amount +/- {}% -> expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
        max_diff_percentage.0,
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg()),
        one_percent_diff.scaled(Decimals::Six.get_decimals().neg())
    );
10
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
10
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
10
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
10
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
10

            
10
    assert!(
10
        contract_usdt_balance_after >= contract_usdt_balance_before,
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
    );
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8150 worth of ETH
10
    let max_diff = human_to_dec("0.7", Decimals::Six);
10

            
10
    assert!(
10
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
        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())
    );
10
}
// source more or less similarly priced as target
10
fn exact_two_hop_inj_atom_swap_test_template(exact_quantity_to_receive: FPDecimal, max_diff_percentage: Percent) {
10
    let app = InjectiveTestApp::new();
10
    let wasm = Wasm::new(&app);
10
    let exchange = Exchange::new(&app);
10
    let bank = Bank::new(&app);
10

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

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

            
10
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
10
    let spot_market_2_id = launch_realistic_atom_usdt_spot_market(&exchange, &owner);
10

            
10
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
10
    set_route_and_assert_success(
10
        &wasm,
10
        &owner,
10
        &contr_addr,
10
        INJ_2,
10
        ATOM,
10
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
10
    );
10

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

            
10
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
10
    create_realistic_atom_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
10

            
10
    app.increase_time(1);
10

            
10
    let inj_to_swap = "973.258";
10

            
10
    let swapper = must_init_account_with_funds_and_setting_denoms(
10
        &app,
10
        &validator,
10
        &[
10
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
10
            initial_coin("1", INJ, Decimals::Eighteen),
10
        ],
10
    );
10

            
10
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
10
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
10
    let query_result: SwapEstimationResult = wasm
10
        .query(
10
            &contr_addr,
10
            &QueryMsg::GetInputQuantity {
10
                source_denom: INJ_2.to_string(),
10
                target_denom: ATOM.to_string(),
10
                to_quantity: exact_quantity_to_receive,
10
            },
10
        )
10
        .unwrap();
10

            
10
    wasm.execute(
10
        &contr_addr,
10
        &ExecuteMsg::SwapExactOutput {
10
            target_denom: ATOM.to_string(),
10
            target_output_quantity: exact_quantity_to_receive,
10
        },
10
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
10
        &swapper,
10
    )
10
    .unwrap();
10

            
10
    let expected_difference = human_to_dec(inj_to_swap, Decimals::Eighteen) - query_result.result_quantity;
10
    let swapper_inj_balance_after = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
10
    let swapper_atom_balance_after = query_bank_balance(&bank, ATOM, swapper.address().as_str());
10

            
10
    assert_eq!(swapper_inj_balance_after, expected_difference, "wrong amount of INJ was exchanged");
10
    assert!(
10
        swapper_atom_balance_after >= exact_quantity_to_receive,
        "swapper got less than exact amount required -> expected: {} ATOM, actual: {} ATOM",
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg())
    );
10
    let one_percent_diff = exact_quantity_to_receive * (FPDecimal::must_from_str(max_diff_percentage.0) / FPDecimal::from(100u128));
10

            
10
    assert!(
10
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
        "swapper did not receive expected exact ATOM amount +/- {}% -> expected: {} ATOM, actual: {} ATOM, max diff: {} ATOM",
        max_diff_percentage.0,
        exact_quantity_to_receive.scaled(Decimals::Six.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Six.get_decimals().neg()),
        one_percent_diff.scaled(Decimals::Six.get_decimals().neg())
    );
10
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
10
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
10
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
10
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
10

            
10
    assert!(
10
        contract_usdt_balance_after >= contract_usdt_balance_before,
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
    );
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8150 worth of INJ
10
    let max_diff = human_to_dec("0.7", Decimals::Six);
10

            
10
    assert!(
10
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
        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())
    );
10
}
// source much cheaper than target
8
fn exact_two_hop_inj_eth_swap_test_template(exact_quantity_to_receive: FPDecimal, max_diff_percentage: Percent) {
8
    let app = InjectiveTestApp::new();
8
    let wasm = Wasm::new(&app);
8
    let exchange = Exchange::new(&app);
8
    let bank = Bank::new(&app);
8

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

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

            
8
    let spot_market_1_id = launch_realistic_inj_usdt_spot_market(&exchange, &owner);
8
    let spot_market_2_id = launch_realistic_weth_usdt_spot_market(&exchange, &owner);
8

            
8
    let contr_addr = init_self_relaying_contract_and_get_address(&wasm, &owner, &[str_coin("1_000", USDT, Decimals::Six)]);
8
    set_route_and_assert_success(
8
        &wasm,
8
        &owner,
8
        &contr_addr,
8
        INJ_2,
8
        ETH,
8
        vec![spot_market_1_id.as_str().into(), spot_market_2_id.as_str().into()],
8
    );
8

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

            
8
    create_realistic_inj_usdt_buy_orders_from_spreadsheet(&app, &spot_market_1_id, &trader1, &trader2);
8
    create_realistic_eth_usdt_sell_orders_from_spreadsheet(&app, &spot_market_2_id, &trader1, &trader2, &trader3);
8

            
8
    app.increase_time(1);
8

            
8
    let inj_to_swap = "973.258";
8

            
8
    let swapper = must_init_account_with_funds_and_setting_denoms(
8
        &app,
8
        &validator,
8
        &[
8
            initial_coin(inj_to_swap, INJ_2, Decimals::Eighteen),
8
            initial_coin("1", INJ, Decimals::Eighteen),
8
        ],
8
    );
8

            
8
    let contract_balances_before = query_all_bank_balances(&bank, &contr_addr);
8
    assert_eq!(contract_balances_before.len(), 1, "wrong number of denoms in contract balances");
8
    let query_result: SwapEstimationResult = wasm
8
        .query(
8
            &contr_addr,
8
            &QueryMsg::GetInputQuantity {
8
                source_denom: INJ_2.to_string(),
8
                target_denom: ETH.to_string(),
8
                to_quantity: exact_quantity_to_receive,
8
            },
8
        )
8
        .unwrap();
8

            
8
    wasm.execute(
8
        &contr_addr,
8
        &ExecuteMsg::SwapExactOutput {
8
            target_denom: ETH.to_string(),
8
            target_output_quantity: exact_quantity_to_receive,
8
        },
8
        &[str_coin(inj_to_swap, INJ_2, Decimals::Eighteen)],
8
        &swapper,
8
    )
8
    .unwrap();
8

            
8
    let expected_difference = human_to_dec(inj_to_swap, Decimals::Eighteen) - query_result.result_quantity;
8
    let swapper_inj_balance_after = query_bank_balance(&bank, INJ_2, swapper.address().as_str());
8
    let swapper_atom_balance_after = query_bank_balance(&bank, ETH, swapper.address().as_str());
8

            
8
    assert_eq!(swapper_inj_balance_after, expected_difference, "wrong amount of INJ was exchanged");
8
    assert!(
8
        swapper_atom_balance_after >= exact_quantity_to_receive,
        "swapper got less than exact amount required -> expected: {} ETH, actual: {} ETH",
        exact_quantity_to_receive.scaled(Decimals::Eighteen.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Eighteen.get_decimals().neg())
    );
8
    let one_percent_diff = exact_quantity_to_receive * (FPDecimal::must_from_str(max_diff_percentage.0) / FPDecimal::from(100u128));
8

            
8
    assert!(
8
        are_fpdecimals_approximately_equal(swapper_atom_balance_after, exact_quantity_to_receive, one_percent_diff,),
        "swapper did not receive expected exact ETH amount +/- {}% -> expected: {} ETH, actual: {} ETH, max diff: {} ETH",
        max_diff_percentage.0,
        exact_quantity_to_receive.scaled(Decimals::Eighteen.get_decimals().neg()),
        swapper_atom_balance_after.scaled(Decimals::Eighteen.get_decimals().neg()),
        one_percent_diff.scaled(Decimals::Eighteen.get_decimals().neg())
    );
8
    let contract_balances_after = query_all_bank_balances(&bank, contr_addr.as_str());
8
    assert_eq!(contract_balances_after.len(), 1, "wrong number of denoms in contract balances");
8
    let contract_usdt_balance_before = FPDecimal::must_from_str(contract_balances_before[0].amount.as_str());
8
    let contract_usdt_balance_after = FPDecimal::must_from_str(contract_balances_after[0].amount.as_str());
8

            
8
    assert!(
8
        contract_usdt_balance_after >= contract_usdt_balance_before,
        "Contract lost some money after swap. Actual balance: {contract_usdt_balance_after}, previous balance: {contract_usdt_balance_before}",
    );
    // contract is allowed to earn extra 0.7 USDT from the swap of ~$8500 worth of INJ
8
    let max_diff = human_to_dec("0.82", Decimals::Six);
8

            
8
    assert!(
8
        are_fpdecimals_approximately_equal(contract_usdt_balance_after, contract_usdt_balance_before, max_diff,),
        "Contract balance changed too much. Actual balance: {} USDT, previous balance: {} USDT. Max diff: {} USDT",
        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())
    );
8
}