1
use crate::{
2
    contract::ATOMIC_ORDER_REPLY_ID,
3
    error::ContractError,
4
    helpers::{dec_scale_factor, round_up_to_min_tick},
5
    queries::{estimate_single_swap_execution, estimate_swap_result, SwapQuantity},
6
    state::{read_swap_route, CONFIG, STEP_STATE, SWAP_OPERATION_STATE, SWAP_RESULTS},
7
    types::{CurrentSwapOperation, CurrentSwapStep, FPCoin, SwapEstimationAmount, SwapQuantityMode, SwapResults},
8
};
9

            
10
use cosmwasm_std::{BankMsg, Deps, DepsMut, Env, Event, MessageInfo, Reply, Response, StdError, StdResult, SubMsg};
11
use injective_cosmwasm::{
12
    create_spot_market_order_msg, get_default_subaccount_id_for_checked_address, InjectiveMsgWrapper, InjectiveQuerier, InjectiveQueryWrapper,
13
    OrderType, SpotOrder,
14
};
15
use injective_math::{round_to_min_tick, FPDecimal};
16
use injective_std::types::injective::exchange::v1beta1::MsgCreateSpotMarketOrderResponse;
17
use prost::Message;
18
use std::str::FromStr;
19

            
20
pub fn start_swap_flow(
21
    deps: DepsMut<InjectiveQueryWrapper>,
22
    env: Env,
23
    info: MessageInfo,
24
    target_denom: String,
25
    swap_quantity_mode: SwapQuantityMode,
26
) -> Result<Response<InjectiveMsgWrapper>, ContractError> {
27
    if info.funds.len() != 1 {
28
        return Err(ContractError::CustomError {
29
            val: "Only one denom can be passed in funds".to_string(),
30
        });
31
    }
32
    let quantity = match swap_quantity_mode {
33
        SwapQuantityMode::MinOutputQuantity(q) => q,
34
        SwapQuantityMode::ExactOutputQuantity(q) => q,
35
    };
36

            
37
    if quantity.is_negative() || quantity.is_zero() {
38
        return Err(ContractError::CustomError {
39
            val: "Output quantity must be positive!".to_string(),
40
        });
41
    }
42

            
43
    let source_denom = &info.funds[0].denom;
44
    let route = read_swap_route(deps.storage, source_denom, &target_denom)?;
45
    let steps = route.steps_from(source_denom);
46

            
47
    let sender_address = info.sender;
48
    let coin_provided = &info.funds[0];
49

            
50
    let mut current_balance = coin_provided.to_owned().into();
51

            
52
    let refund_amount = if matches!(swap_quantity_mode, SwapQuantityMode::ExactOutputQuantity(..)) {
53
        let target_output_quantity = quantity;
54

            
55
        let estimation = estimate_swap_result(
56
            deps.as_ref(),
57
            &env,
58
            source_denom.to_owned(),
59
            target_denom,
60
            SwapQuantity::OutputQuantity(target_output_quantity),
61
        )?;
62

            
63
        let querier = InjectiveQuerier::new(&deps.querier);
64
        let first_market_id = steps[0].to_owned();
65
        let first_market = querier.query_spot_market(&first_market_id)?.market.expect("market should be available");
66

            
67
        let is_input_quote = first_market.quote_denom == *source_denom;
68

            
69
        let required_input = if is_input_quote {
70
            estimation.result_quantity.int() + FPDecimal::ONE
71
        } else {
72
            round_up_to_min_tick(estimation.result_quantity, first_market.min_quantity_tick_size)
73
        };
74

            
75
        let fp_coins: FPDecimal = coin_provided.amount.into();
76

            
77
        if required_input > fp_coins {
78
            return Err(ContractError::InsufficientFundsProvided(fp_coins, required_input));
79
        }
80

            
81
        current_balance = FPCoin {
82
            amount: required_input,
83
            denom: source_denom.to_owned(),
84
        };
85

            
86
        FPDecimal::from(coin_provided.amount) - required_input
87
    } else {
88
        FPDecimal::ZERO
89
    };
90

            
91
    let swap_operation = CurrentSwapOperation {
92
        sender_address,
93
        swap_steps: steps,
94
        swap_quantity_mode,
95
        refund: FPCoin {
96
            amount: refund_amount,
97
            denom: source_denom.to_owned(),
98
        }
99
        .to_coin_floor()?,
100
        input_funds: coin_provided.to_owned(),
101
    };
102

            
103
    SWAP_RESULTS.save(deps.storage, &Vec::new())?;
104
    SWAP_OPERATION_STATE.save(deps.storage, &swap_operation)?;
105

            
106
    execute_swap_step(deps, env, swap_operation, 0, current_balance).map_err(ContractError::Std)
107
}
108

            
109
pub fn execute_swap_step(
110
    deps: DepsMut<InjectiveQueryWrapper>,
111
    env: Env,
112
    swap_operation: CurrentSwapOperation,
113
    step_idx: u16,
114
    current_balance: FPCoin,
115
) -> StdResult<Response<InjectiveMsgWrapper>> {
116
    let market_id = swap_operation.swap_steps[usize::from(step_idx)].clone();
117
    let contract = &env.contract.address;
118
    let subaccount_id = get_default_subaccount_id_for_checked_address(contract);
119

            
120
    let estimation = estimate_single_swap_execution(
121
        &deps.as_ref(),
122
        &env,
123
        &market_id,
124
        SwapEstimationAmount::InputQuantity(current_balance.clone()),
125
        false,
126
    )?;
127

            
128
    let fee_recipient = &CONFIG.load(deps.storage)?.fee_recipient;
129

            
130
    let input_spendable_before = query_contract_spendable_balance(&deps.as_ref(), &env, &current_balance.denom)?;
131
    if current_balance.amount > input_spendable_before {
132
        return Err(StdError::msg(format!(
133
            "Authorized swap input exceeds spendable balance for {}: authorized {}, spendable {}",
134
            current_balance.denom, current_balance.amount, input_spendable_before
135
        )));
136
    }
137
    let minimum_input_spendable_after = input_spendable_before - current_balance.amount;
138

            
139
    let order = SpotOrder::new(
140
        estimation.worst_price,
141
        if estimation.is_buy_order {
142
            estimation.result_quantity
143
        } else {
144
            current_balance.amount
145
        },
146
        if estimation.is_buy_order {
147
            OrderType::BuyAtomic
148
        } else {
149
            OrderType::SellAtomic
150
        },
151
        &market_id,
152
        subaccount_id,
153
        Some(fee_recipient.to_owned()),
154
        None,
155
    );
156

            
157
    let order_message = SubMsg::reply_on_success(create_spot_market_order_msg(contract.to_owned(), order), ATOMIC_ORDER_REPLY_ID);
158

            
159
    let current_step = CurrentSwapStep {
160
        step_idx,
161
        current_balance,
162
        step_target_denom: estimation.result_denom,
163
        is_buy: estimation.is_buy_order,
164
        minimum_input_spendable_after,
165
    };
166
    STEP_STATE.save(deps.storage, &current_step)?;
167

            
168
    let response = Response::new().add_submessage(order_message);
169
    Ok(response)
170
}
171

            
172
pub fn handle_atomic_order_reply(deps: DepsMut<InjectiveQueryWrapper>, env: Env, msg: Reply) -> Result<Response<InjectiveMsgWrapper>, ContractError> {
173
    let dec_scale_factor = dec_scale_factor(); // protobuf serializes Dec values with extra 10^18 factor
174

            
175
    let order_response = parse_market_order_response(msg)?;
176

            
177
    let trade_data = match order_response.results {
178
        Some(trade_data) => Ok(trade_data),
179
        None => Err(ContractError::CustomError {
180
            val: "No trade data in order response".to_string(),
181
        }),
182
    }?;
183

            
184
    // need to remove protobuf scale factor to get real values
185
    let average_price = FPDecimal::from_str(&trade_data.price)? / dec_scale_factor;
186
    let quantity = FPDecimal::from_str(&trade_data.quantity)? / dec_scale_factor;
187
    let fee = FPDecimal::from_str(&trade_data.fee)? / dec_scale_factor;
188

            
189
    let mut swap_results = SWAP_RESULTS.load(deps.storage)?;
190

            
191
    let current_step = STEP_STATE.load(deps.storage).map_err(ContractError::Std)?;
192

            
193
    let input_spendable_after = query_contract_spendable_balance(&deps.as_ref(), &env, &current_step.current_balance.denom)?;
194
    ensure_support_funds_preserved(
195
        &current_step.current_balance.denom,
196
        current_step.minimum_input_spendable_after,
197
        input_spendable_after,
198
    )?;
199

            
200
    let new_quantity = if current_step.is_buy { quantity } else { quantity * average_price - fee };
201

            
202
    let swap = SWAP_OPERATION_STATE.load(deps.storage)?;
203

            
204
    let has_next_market = swap.swap_steps.len() > (current_step.step_idx + 1) as usize;
205

            
206
    let new_rounded_quantity = if has_next_market {
207
        let querier = InjectiveQuerier::new(&deps.querier);
208
        let next_market_id = swap.swap_steps[(current_step.step_idx + 1) as usize].to_owned();
209
        let next_market = querier.query_spot_market(&next_market_id)?.market.expect("market should be available");
210

            
211
        let is_next_swap_sell = next_market.base_denom == current_step.step_target_denom;
212

            
213
        if is_next_swap_sell {
214
            round_to_min_tick(new_quantity, next_market.min_quantity_tick_size)
215
        } else {
216
            new_quantity
217
        }
218
    } else {
219
        new_quantity
220
    };
221

            
222
    let new_balance = FPCoin {
223
        amount: new_rounded_quantity,
224
        denom: current_step.step_target_denom,
225
    };
226

            
227
    swap_results.push(SwapResults {
228
        market_id: swap.swap_steps[(current_step.step_idx) as usize].to_owned(),
229
        price: average_price,
230
        quantity: new_rounded_quantity,
231
        fee,
232
    });
233

            
234
    if current_step.step_idx < (swap.swap_steps.len() - 1) as u16 {
235
        SWAP_RESULTS.save(deps.storage, &swap_results)?;
236
        return execute_swap_step(deps, env, swap, current_step.step_idx + 1, new_balance).map_err(ContractError::Std);
237
    }
238

            
239
    let min_output_quantity = match swap.swap_quantity_mode {
240
        SwapQuantityMode::MinOutputQuantity(q) => q,
241
        SwapQuantityMode::ExactOutputQuantity(q) => q,
242
    };
243

            
244
    let output_coin = new_balance.to_coin_floor()?;
245
    ensure_minimum_output(&output_coin, min_output_quantity)?;
246

            
247
    // last step, finalize and send back funds to a caller
248
    let send_message = BankMsg::Send {
249
        to_address: swap.sender_address.to_string(),
250
        amount: vec![output_coin.clone()],
251
    };
252

            
253
    let swap_results_json = serde_json_wasm::to_string(&swap_results).unwrap();
254
    let swap_event = Event::new("atomic_swap_execution")
255
        .add_attribute("sender", swap.sender_address.to_owned())
256
        .add_attribute("swap_input_amount", swap.input_funds.amount)
257
        .add_attribute("swap_input_denom", swap.input_funds.denom)
258
        .add_attribute("refund_amount", swap.refund.amount.to_owned())
259
        .add_attribute("swap_final_amount", output_coin.amount)
260
        .add_attribute("swap_final_denom", new_balance.denom)
261
        .add_attribute("swap_results", swap_results_json);
262

            
263
    SWAP_OPERATION_STATE.remove(deps.storage);
264
    STEP_STATE.remove(deps.storage);
265
    SWAP_RESULTS.remove(deps.storage);
266

            
267
    let mut response = Response::new().add_message(send_message).add_event(swap_event);
268

            
269
    if !swap.refund.amount.is_zero() {
270
        let refund_message = BankMsg::Send {
271
            to_address: swap.sender_address.to_string(),
272
            amount: vec![swap.refund],
273
        };
274
        response = response.add_message(refund_message)
275
    }
276

            
277
    Ok(response)
278
}
279

            
280
fn query_contract_spendable_balance(deps: &Deps<InjectiveQueryWrapper>, env: &Env, denom: &str) -> StdResult<FPDecimal> {
281
    let bank_balance: FPDecimal = deps.querier.query_balance(&env.contract.address, denom)?.amount.into();
282
    let subaccount_id = get_default_subaccount_id_for_checked_address(&env.contract.address);
283
    let denom = denom.to_owned();
284
    let available_deposit = InjectiveQuerier::new(&deps.querier)
285
        .query_subaccount_deposit(&subaccount_id, &denom)?
286
        .deposits
287
        .available_balance;
288

            
289
    Ok(bank_balance + available_deposit)
290
}
291

            
292
6
pub(crate) fn ensure_support_funds_preserved(denom: &str, minimum_remaining: FPDecimal, actual_remaining: FPDecimal) -> Result<(), ContractError> {
293
6
    if actual_remaining < minimum_remaining {
294
2
        return Err(ContractError::SupportFundsConsumed {
295
2
            denom: denom.to_owned(),
296
2
            minimum_remaining,
297
2
            actual_remaining,
298
2
        });
299
4
    }
300
4

            
301
4
    Ok(())
302
6
}
303

            
304
4
pub(crate) fn ensure_minimum_output(output: &cosmwasm_std::Coin, minimum: FPDecimal) -> Result<(), ContractError> {
305
4
    if FPDecimal::from(output.amount) < minimum {
306
2
        return Err(ContractError::MinOutputAmountNotReached(minimum));
307
2
    }
308
2

            
309
2
    Ok(())
310
4
}
311

            
312
pub fn parse_market_order_response(msg: Reply) -> StdResult<MsgCreateSpotMarketOrderResponse> {
313
    let binding = msg.result.into_result().map_err(ContractError::SubMsgFailure).unwrap();
314

            
315
    let first_message = binding.msg_responses.first();
316
    let order_response = MsgCreateSpotMarketOrderResponse::decode(first_message.unwrap().value.as_slice())
317
        .map_err(|err| ContractError::ReplyParseFailure {
318
            id: msg.id,
319
            err: err.to_string(),
320
        })
321
        .unwrap();
322

            
323
    Ok(order_response)
324
}