1
use cosmwasm_std::{Addr, Deps, Env, StdError, StdResult};
2
use injective_cosmwasm::{InjectiveQuerier, InjectiveQueryWrapper, MarketId, OrderSide, PriceLevel, SpotMarket};
3
use injective_math::FPDecimal;
4

            
5
use crate::exact_math::{multiply_to_raw, ratio_to_tick, select_liquidity_levels, weighted_average_to_tick, LiquidityTarget, RoundingDirection};
6
use crate::helpers::round_up_to_min_tick;
7
use crate::state::{read_swap_route, CONFIG};
8
use crate::types::{FPCoin, StepExecutionEstimate, SwapEstimationAmount, SwapEstimationResult};
9

            
10
pub enum SwapQuantity {
11
    InputQuantity(FPDecimal),
12
    OutputQuantity(FPDecimal),
13
}
14

            
15
16
pub fn estimate_swap_result(
16
16
    deps: Deps<InjectiveQueryWrapper>,
17
16
    env: &Env,
18
16
    source_denom: String,
19
16
    target_denom: String,
20
16
    swap_quantity: SwapQuantity,
21
16
) -> StdResult<SwapEstimationResult> {
22
16
    match swap_quantity {
23
8
        SwapQuantity::InputQuantity(quantity) => {
24
8
            if quantity.is_zero() || quantity.is_negative() {
25
                return Err(StdError::msg("source_quantity must be positive"));
26
8
            }
27
        }
28
8
        SwapQuantity::OutputQuantity(quantity) => {
29
8
            if quantity.is_zero() || quantity.is_negative() {
30
                return Err(StdError::msg("target_quantity must be positive"));
31
8
            }
32
        }
33
    }
34

            
35
16
    let route = read_swap_route(deps.storage, &source_denom, &target_denom)?;
36

            
37
16
    let (steps, mut current_swap) = match swap_quantity {
38
8
        SwapQuantity::InputQuantity(quantity) => (
39
8
            route.steps_from(&source_denom),
40
8
            FPCoin {
41
8
                amount: quantity,
42
8
                denom: source_denom.clone(),
43
8
            },
44
8
        ),
45
8
        SwapQuantity::OutputQuantity(quantity) => {
46
8
            let mut steps = route.steps_from(&source_denom);
47
8
            steps.reverse();
48
8
            (
49
8
                steps,
50
8
                FPCoin {
51
8
                    amount: quantity,
52
8
                    denom: target_denom,
53
8
                },
54
8
            )
55
        }
56
    };
57

            
58
16
    let mut fees: Vec<FPCoin> = vec![];
59

            
60
40
    for step in steps {
61
24
        let swap_estimate = estimate_single_swap_execution(
62
24
            &deps,
63
24
            env,
64
24
            &step,
65
24
            match swap_quantity {
66
12
                SwapQuantity::InputQuantity(_) => SwapEstimationAmount::InputQuantity(current_swap.clone()),
67
12
                SwapQuantity::OutputQuantity(_) => SwapEstimationAmount::ReceiveQuantity(current_swap.clone()),
68
            },
69
            true,
70
        )?;
71

            
72
24
        current_swap.amount = swap_estimate.result_quantity;
73
24
        current_swap.denom = swap_estimate.result_denom;
74
24

            
75
24
        let step_fee = swap_estimate.fee_estimate.expect("fee estimate should be available");
76
24

            
77
24
        fees.push(step_fee);
78
    }
79

            
80
16
    Ok(SwapEstimationResult {
81
16
        expected_fees: fees,
82
16
        result_quantity: current_swap.amount,
83
16
    })
84
16
}
85

            
86
26
pub fn estimate_single_swap_execution(
87
26
    deps: &Deps<InjectiveQueryWrapper>,
88
26
    env: &Env,
89
26
    market_id: &MarketId,
90
26
    swap_estimation_amount: SwapEstimationAmount,
91
26
    is_simulation: bool,
92
26
) -> StdResult<StepExecutionEstimate> {
93
26
    let querier = InjectiveQuerier::new(&deps.querier);
94

            
95
26
    let balance_in = match swap_estimation_amount.to_owned() {
96
14
        SwapEstimationAmount::InputQuantity(fp) => fp,
97
12
        SwapEstimationAmount::ReceiveQuantity(fp) => fp,
98
    };
99

            
100
26
    let market = querier.query_spot_market(market_id)?.market.expect("market should be available");
101

            
102
26
    let has_invalid_denom = balance_in.denom != market.quote_denom && balance_in.denom != market.base_denom;
103
26
    if has_invalid_denom {
104
        return Err(StdError::msg("Invalid swap denom - neither base nor quote"));
105
26
    }
106

            
107
26
    let config = CONFIG.load(deps.storage)?;
108
26
    let is_self_relayer = config.fee_recipient == env.contract.address;
109

            
110
26
    let fee_multiplier = querier.query_market_atomic_execution_fee_multiplier(market_id)?.multiplier;
111

            
112
24
    let fee_percent = market.taker_fee_rate * fee_multiplier * (FPDecimal::ONE - get_effective_fee_discount_rate(&market, is_self_relayer));
113

            
114
24
    let is_estimating_from_target = matches!(swap_estimation_amount, SwapEstimationAmount::ReceiveQuantity(_));
115

            
116
24
    let is_buy = if is_estimating_from_target {
117
12
        balance_in.denom == market.base_denom
118
    } else {
119
12
        balance_in.denom != market.base_denom
120
    };
121

            
122
24
    if is_buy {
123
12
        estimate_execution_buy(
124
12
            deps,
125
12
            &querier,
126
12
            &env.contract.address,
127
12
            &market,
128
12
            swap_estimation_amount,
129
12
            fee_percent,
130
12
            is_simulation,
131
12
        )
132
    } else {
133
12
        estimate_execution_sell(&querier, &market, swap_estimation_amount, fee_percent)
134
    }
135
26
}
136

            
137
6
fn estimate_execution_buy_from_source(
138
6
    deps: &Deps<InjectiveQueryWrapper>,
139
6
    querier: &InjectiveQuerier,
140
6
    contract_address: &Addr,
141
6
    market: &SpotMarket,
142
6
    input_quote_quantity: FPDecimal,
143
6
    fee_percent: FPDecimal,
144
6
    is_simulation: bool,
145
6
) -> StdResult<StepExecutionEstimate> {
146
6
    let raw_tick = FPDecimal::from(primitive_types::U256::one());
147
6
    let fee_factor = FPDecimal::ONE + fee_percent;
148
6
    let available_swap_quote_funds = ratio_to_tick(input_quote_quantity, fee_factor, raw_tick, RoundingDirection::Down)?;
149

            
150
6
    let orders = querier.query_spot_market_orderbook(&market.market_id, OrderSide::Sell, None, Some(available_swap_quote_funds))?;
151
6
    let top_orders = select_liquidity_levels(
152
6
        &orders.sells_price_level,
153
6
        available_swap_quote_funds,
154
6
        LiquidityTarget::Notional,
155
6
        market.min_quantity_tick_size,
156
6
    )?;
157

            
158
    // lets overestimate amount for buys means rounding average price up -> higher buy price -> worse
159
6
    let average_price = weighted_average_to_tick(&top_orders, market.min_price_tick_size, RoundingDirection::Up)?;
160
6
    let worst_price = get_worst_price_from_orders(&top_orders);
161

            
162
6
    let result_quantity = ratio_to_tick(
163
6
        available_swap_quote_funds,
164
6
        average_price,
165
6
        market.min_quantity_tick_size,
166
6
        RoundingDirection::Down,
167
6
    )?;
168
6
    let fee_estimate = input_quote_quantity - available_swap_quote_funds;
169

            
170
    // check if user funds + contract funds are enough to create order
171
6
    let worst_price_notional = multiply_to_raw(worst_price, result_quantity, RoundingDirection::Up)?;
172
6
    let required_funds = multiply_to_raw(worst_price_notional, fee_factor, RoundingDirection::Up)?;
173
6
    let funds_in_contract = deps
174
6
        .querier
175
6
        .query_balance(contract_address, &market.quote_denom)
176
6
        .expect("query own balance should not fail")
177
6
        .amount
178
6
        .into();
179

            
180
6
    let funds_for_margin = match is_simulation {
181
        false => funds_in_contract, // in execution mode funds_in_contract already contain user funds so we don't want to count them double
182
6
        true => funds_in_contract + input_quote_quantity,
183
    };
184

            
185
6
    if required_funds > funds_for_margin {
186
        return Err(StdError::msg(format!(
187
            "Swap amount too high, required funds: {required_funds}, available funds: {funds_for_margin}",
188
        )));
189
6
    }
190
6

            
191
6
    Ok(StepExecutionEstimate {
192
6
        worst_price,
193
6
        result_quantity,
194
6
        result_denom: market.base_denom.to_string(),
195
6
        is_buy_order: true,
196
6
        fee_estimate: Some(FPCoin {
197
6
            denom: market.quote_denom.clone(),
198
6
            amount: fee_estimate,
199
6
        }),
200
6
    })
201
6
}
202

            
203
6
fn estimate_execution_buy_from_target(
204
6
    deps: &Deps<InjectiveQueryWrapper>,
205
6
    querier: &InjectiveQuerier,
206
6
    contract_address: &Addr,
207
6
    market: &SpotMarket,
208
6
    target_base_output_quantity: FPDecimal,
209
6
    fee_percent: FPDecimal,
210
6
    is_simulation: bool,
211
6
) -> StdResult<StepExecutionEstimate> {
212
6
    let rounded_target_base_output_quantity = round_up_to_min_tick(target_base_output_quantity, market.min_quantity_tick_size);
213

            
214
6
    let orders = querier.query_spot_market_orderbook(&market.market_id, OrderSide::Sell, Some(rounded_target_base_output_quantity), None)?;
215
6
    let top_orders = select_liquidity_levels(
216
6
        &orders.sells_price_level,
217
6
        rounded_target_base_output_quantity,
218
6
        LiquidityTarget::Quantity,
219
6
        market.min_quantity_tick_size,
220
6
    )?;
221

            
222
    // lets overestimate amount for buys means rounding average price up -> higher buy price -> worse
223
6
    let average_price = weighted_average_to_tick(&top_orders, market.min_price_tick_size, RoundingDirection::Up)?;
224
6
    let worst_price = get_worst_price_from_orders(&top_orders);
225

            
226
6
    let expected_exchange_quote_quantity = multiply_to_raw(rounded_target_base_output_quantity, average_price, RoundingDirection::Up)?;
227
6
    let required_input_quote_quantity = multiply_to_raw(expected_exchange_quote_quantity, FPDecimal::ONE + fee_percent, RoundingDirection::Up)?;
228
6
    let fee_estimate = required_input_quote_quantity - expected_exchange_quote_quantity;
229

            
230
    // check if user funds + contract funds are enough to create order
231
6
    let worst_price_notional = multiply_to_raw(worst_price, rounded_target_base_output_quantity, RoundingDirection::Up)?;
232
6
    let required_funds = multiply_to_raw(worst_price_notional, FPDecimal::ONE + fee_percent, RoundingDirection::Up)?;
233

            
234
6
    let funds_in_contract = deps
235
6
        .querier
236
6
        .query_balance(contract_address, &market.quote_denom)
237
6
        .expect("query own balance should not fail")
238
6
        .amount
239
6
        .into();
240

            
241
6
    let funds_for_margin = match is_simulation {
242
        false => funds_in_contract, // in execution mode funds_in_contract already contain user funds so we don't want to count them double
243
6
        true => funds_in_contract + required_input_quote_quantity,
244
    };
245

            
246
6
    if required_funds > funds_for_margin {
247
        return Err(StdError::msg(format!(
248
            "Swap amount too high, required funds: {required_funds}, available funds: {funds_for_margin}",
249
        )));
250
6
    }
251
6

            
252
6
    Ok(StepExecutionEstimate {
253
6
        worst_price,
254
6
        result_quantity: required_input_quote_quantity,
255
6
        result_denom: market.quote_denom.to_string(),
256
6
        is_buy_order: true,
257
6
        fee_estimate: Some(FPCoin {
258
6
            denom: market.quote_denom.clone(),
259
6
            amount: fee_estimate,
260
6
        }),
261
6
    })
262
6
}
263

            
264
12
fn estimate_execution_buy(
265
12
    deps: &Deps<InjectiveQueryWrapper>,
266
12
    querier: &InjectiveQuerier,
267
12
    contract_address: &Addr,
268
12
    market: &SpotMarket,
269
12
    swap_estimation_amount: SwapEstimationAmount,
270
12
    fee_percent: FPDecimal,
271
12
    is_simulation: bool,
272
12
) -> StdResult<StepExecutionEstimate> {
273
12
    let amount_coin = match swap_estimation_amount.to_owned() {
274
6
        SwapEstimationAmount::InputQuantity(fp) => fp,
275
6
        SwapEstimationAmount::ReceiveQuantity(fp) => fp,
276
    };
277

            
278
12
    let is_estimating_from_target = matches!(swap_estimation_amount, SwapEstimationAmount::ReceiveQuantity(_));
279

            
280
12
    if is_estimating_from_target {
281
6
        estimate_execution_buy_from_target(deps, querier, contract_address, market, amount_coin.amount, fee_percent, is_simulation)
282
    } else {
283
6
        estimate_execution_buy_from_source(deps, querier, contract_address, market, amount_coin.amount, fee_percent, is_simulation)
284
    }
285
12
}
286

            
287
6
fn estimate_execution_sell_from_source(
288
6
    querier: &InjectiveQuerier,
289
6
    market: &SpotMarket,
290
6
    input_base_quantity: FPDecimal,
291
6
    fee_percent: FPDecimal,
292
6
) -> StdResult<StepExecutionEstimate> {
293
6
    let orders = querier.query_spot_market_orderbook(&market.market_id, OrderSide::Buy, Some(input_base_quantity), None)?;
294

            
295
6
    let top_orders = select_liquidity_levels(
296
6
        &orders.buys_price_level,
297
6
        input_base_quantity,
298
6
        LiquidityTarget::Quantity,
299
6
        market.min_quantity_tick_size,
300
6
    )?;
301

            
302
    // lets overestimate amount for sells means rounding average price down -> lower sell price -> worse
303
6
    let average_price = weighted_average_to_tick(&top_orders, market.min_price_tick_size, RoundingDirection::Down)?;
304
6
    let worst_price = get_worst_price_from_orders(&top_orders);
305

            
306
6
    let expected_exchange_quantity = multiply_to_raw(input_base_quantity, average_price, RoundingDirection::Down)?;
307
6
    let expected_quantity = multiply_to_raw(expected_exchange_quantity, FPDecimal::ONE - fee_percent, RoundingDirection::Down)?;
308
6
    let fee_estimate = expected_exchange_quantity - expected_quantity;
309
6

            
310
6
    Ok(StepExecutionEstimate {
311
6
        worst_price,
312
6
        result_quantity: expected_quantity,
313
6
        result_denom: market.quote_denom.to_string(),
314
6
        is_buy_order: false,
315
6
        fee_estimate: Some(FPCoin {
316
6
            denom: market.quote_denom.clone(),
317
6
            amount: fee_estimate,
318
6
        }),
319
6
    })
320
6
}
321

            
322
6
fn estimate_execution_sell_from_target(
323
6
    querier: &InjectiveQuerier,
324
6
    market: &SpotMarket,
325
6
    target_quote_output_quantity: FPDecimal,
326
6
    fee_percent: FPDecimal,
327
6
) -> StdResult<StepExecutionEstimate> {
328
6
    let required_swap_quantity_in_quote = ratio_to_tick(
329
6
        target_quote_output_quantity,
330
6
        FPDecimal::ONE - fee_percent,
331
6
        FPDecimal::from(primitive_types::U256::one()),
332
6
        RoundingDirection::Up,
333
6
    )?;
334
6
    let required_fee = required_swap_quantity_in_quote - target_quote_output_quantity;
335

            
336
6
    let orders = querier.query_spot_market_orderbook(&market.market_id, OrderSide::Buy, None, Some(required_swap_quantity_in_quote))?;
337
6
    let top_orders = select_liquidity_levels(
338
6
        &orders.buys_price_level,
339
6
        required_swap_quantity_in_quote,
340
6
        LiquidityTarget::Notional,
341
6
        market.min_quantity_tick_size,
342
6
    )?;
343

            
344
    // lets overestimate amount for sells means rounding average price down -> lower sell price -> worse
345
6
    let average_price = weighted_average_to_tick(&top_orders, market.min_price_tick_size, RoundingDirection::Down)?;
346
6
    let worst_price = get_worst_price_from_orders(&top_orders);
347

            
348
6
    let required_swap_input_quantity_in_base = ratio_to_tick(
349
6
        required_swap_quantity_in_quote,
350
6
        average_price,
351
6
        market.min_quantity_tick_size,
352
6
        RoundingDirection::Up,
353
6
    )?;
354

            
355
6
    Ok(StepExecutionEstimate {
356
6
        worst_price,
357
6
        result_quantity: required_swap_input_quantity_in_base,
358
6
        result_denom: market.base_denom.to_string(),
359
6
        is_buy_order: false,
360
6
        fee_estimate: Some(FPCoin {
361
6
            denom: market.quote_denom.clone(),
362
6
            amount: required_fee,
363
6
        }),
364
6
    })
365
6
}
366

            
367
12
fn estimate_execution_sell(
368
12
    querier: &InjectiveQuerier,
369
12
    market: &SpotMarket,
370
12
    swap_estimation_amount: SwapEstimationAmount,
371
12
    fee_percent: FPDecimal,
372
12
) -> StdResult<StepExecutionEstimate> {
373
12
    let amount_coin = match swap_estimation_amount.to_owned() {
374
6
        SwapEstimationAmount::InputQuantity(fp) => fp,
375
6
        SwapEstimationAmount::ReceiveQuantity(fp) => fp,
376
    };
377

            
378
12
    let is_estimating_from_target = matches!(swap_estimation_amount, SwapEstimationAmount::ReceiveQuantity(_));
379

            
380
12
    if is_estimating_from_target {
381
6
        estimate_execution_sell_from_target(querier, market, amount_coin.amount, fee_percent)
382
    } else {
383
6
        estimate_execution_sell_from_source(querier, market, amount_coin.amount, fee_percent)
384
    }
385
12
}
386

            
387
26
fn get_worst_price_from_orders(levels: &[PriceLevel]) -> FPDecimal {
388
26
    levels.last().unwrap().p // assume there's at least one element
389
26
}
390

            
391
24
fn get_effective_fee_discount_rate(market: &SpotMarket, is_self_relayer: bool) -> FPDecimal {
392
24
    if !is_self_relayer {
393
16
        FPDecimal::ZERO
394
    } else {
395
8
        market.relayer_fee_share_rate
396
    }
397
24
}
398

            
399
#[cfg(test)]
400
mod tests {
401
    use crate::testing::test_utils::create_price_level;
402

            
403
    use super::*;
404

            
405
    #[test]
406
2
    fn test_average_price_simple() {
407
2
        let levels = vec![create_price_level(1, 200), create_price_level(2, 200), create_price_level(3, 200)];
408
2

            
409
2
        let avg = weighted_average_to_tick(&levels, FPDecimal::must_from_str("0.01"), RoundingDirection::Down).unwrap();
410
2
        assert_eq!(avg, FPDecimal::from(2u128));
411
2
    }
412

            
413
    #[test]
414
2
    fn test_average_price_simple_round_down() {
415
2
        let levels = vec![create_price_level(1, 300), create_price_level(2, 200), create_price_level(3, 100)];
416
2

            
417
2
        let avg = weighted_average_to_tick(&levels, FPDecimal::must_from_str("0.01"), RoundingDirection::Down).unwrap();
418
2
        assert_eq!(avg, FPDecimal::must_from_str("1.66")); //we round down
419
2
    }
420

            
421
    #[test]
422
2
    fn test_average_price_simple_round_up() {
423
2
        let levels = vec![create_price_level(1, 300), create_price_level(2, 200), create_price_level(3, 100)];
424
2

            
425
2
        let avg = weighted_average_to_tick(&levels, FPDecimal::must_from_str("0.01"), RoundingDirection::Up).unwrap();
426
2
        assert_eq!(avg, FPDecimal::must_from_str("1.67")); //we round up
427
2
    }
428

            
429
    #[test]
430
2
    fn test_worst_price() {
431
2
        let levels = vec![create_price_level(1, 100), create_price_level(2, 200), create_price_level(3, 300)];
432
2

            
433
2
        let worst = get_worst_price_from_orders(&levels);
434
2
        assert_eq!(worst, FPDecimal::from(3u128));
435
2
    }
436

            
437
    #[test]
438
2
    fn test_find_minimum_orders_not_enough_liquidity() {
439
2
        let levels = vec![create_price_level(1, 100), create_price_level(2, 200)];
440
2

            
441
2
        let result = select_liquidity_levels(
442
2
            &levels,
443
2
            FPDecimal::from(1000u128),
444
2
            LiquidityTarget::Quantity,
445
2
            FPDecimal::must_from_str("0.01"),
446
2
        );
447
2
        assert!(result.is_err());
448
2
        assert_eq!(result.unwrap_err().as_ref().to_string(), "Not enough liquidity to fulfill order");
449
2
    }
450

            
451
    #[test]
452
2
    fn test_find_minimum_orders_with_gaps() {
453
2
        let levels = vec![create_price_level(1, 100), create_price_level(3, 300), create_price_level(5, 500)];
454
2

            
455
2
        let result = select_liquidity_levels(
456
2
            &levels,
457
2
            FPDecimal::from(800u128),
458
2
            LiquidityTarget::Quantity,
459
2
            FPDecimal::must_from_str("0.01"),
460
2
        );
461
2
        assert!(result.is_ok());
462
2
        let min_orders = result.unwrap();
463
2
        assert_eq!(min_orders.len(), 3);
464
2
        assert_eq!(min_orders[0].p, FPDecimal::from(1u128));
465
2
        assert_eq!(min_orders[1].p, FPDecimal::from(3u128));
466
2
        assert_eq!(min_orders[2].p, FPDecimal::from(5u128));
467
2
    }
468

            
469
    #[test]
470
2
    fn test_find_minimum_buy_orders_not_consuming_fully() {
471
2
        let levels = vec![create_price_level(1, 100), create_price_level(3, 300), create_price_level(5, 500)];
472
2

            
473
2
        let result = select_liquidity_levels(
474
2
            &levels,
475
2
            FPDecimal::from(450u128),
476
2
            LiquidityTarget::Quantity,
477
2
            FPDecimal::must_from_str("0.01"),
478
2
        );
479
2
        assert!(result.is_ok());
480
2
        let min_orders = result.unwrap();
481
2
        assert_eq!(min_orders.len(), 3);
482
2
        assert_eq!(min_orders[0].p, FPDecimal::from(1u128));
483
2
        assert_eq!(min_orders[0].q, FPDecimal::from(100u128));
484
2
        assert_eq!(min_orders[1].p, FPDecimal::from(3u128));
485
2
        assert_eq!(min_orders[1].q, FPDecimal::from(300u128));
486
2
        assert_eq!(min_orders[2].p, FPDecimal::from(5u128));
487
2
        assert_eq!(min_orders[2].q, FPDecimal::from(50u128));
488
2
    }
489

            
490
    #[test]
491
2
    fn test_find_minimum_sell_orders_not_consuming_fully() {
492
2
        let buy_levels = vec![create_price_level(5, 500), create_price_level(3, 300), create_price_level(1, 100)];
493
2

            
494
2
        let result = select_liquidity_levels(
495
2
            &buy_levels,
496
2
            FPDecimal::from(3450u128),
497
2
            LiquidityTarget::Notional,
498
2
            FPDecimal::must_from_str("0.01"),
499
2
        );
500
2
        assert!(result.is_ok());
501
2
        let min_orders = result.unwrap();
502
2
        assert_eq!(min_orders.len(), 3);
503
2
        assert_eq!(min_orders[0].p, FPDecimal::from(5u128));
504
2
        assert_eq!(min_orders[0].q, FPDecimal::from(500u128));
505
2
        assert_eq!(min_orders[1].p, FPDecimal::from(3u128));
506
2
        assert_eq!(min_orders[1].q, FPDecimal::from(300u128));
507
2
        assert_eq!(min_orders[2].p, FPDecimal::from(1u128));
508
2
        assert_eq!(min_orders[2].q, FPDecimal::from(50u128));
509
2
    }
510
}