Skip to content
This repository has been archived by the owner on Dec 29, 2023. It is now read-only.

Commit

Permalink
feat: add a Spot Market Order Type
Browse files Browse the repository at this point in the history
  • Loading branch information
politeWall committed Nov 20, 2023
1 parent 9829fa3 commit fb1b155
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 6 deletions.
67 changes: 61 additions & 6 deletions src/action/execute/create_spot_order.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use cosmwasm_std::{to_json_binary, Int128, StdResult, Storage, SubMsg};

use crate::msg::ReplyType;

use super::*;

pub fn create_spot_order(
Expand All @@ -18,18 +22,19 @@ pub fn create_spot_order(
&order_source_denom,
&order_target_denom,
&order_price,
&order_type,
&info.funds[0].denom,
)?;

let mut order_vec = SPOT_ORDER.load(deps.storage)?;

let new_order: SpotOrder = SpotOrder::new(
order_type,
order_type.clone(),
order_price,
info.funds[0].clone(),
info.sender.clone(),
order_target_denom,
order_amm_routes,
order_amm_routes.clone(),
&order_vec,
);

Expand All @@ -40,20 +45,24 @@ pub fn create_spot_order(

cw_utils::must_pay(&info, &info.funds[0].denom)?;

let resp = Response::new()
.add_attribute("order_id", new_order.order_id.to_string())
.add_message(bank_msg); // information message
let resp = create_resp(
env.contract.address.as_str(),
&new_order,
bank_msg,
deps.storage,
)?;

order_vec.push(new_order);

SPOT_ORDER.save(deps.storage, &order_vec)?;

Ok(resp)
}

fn check_denom_error(
order_source_denom: &str,
order_target_denom: &str,
order_price: &SpotOrderPrice,
order_type: &SpotOrderType,
funds_send_denom: &str,
) -> Result<(), ContractError> {
if order_source_denom != funds_send_denom {
Expand All @@ -64,6 +73,10 @@ fn check_denom_error(
return Err(ContractError::SpotOrderSameDenom);
}

if order_type == &SpotOrderType::Market {
return Ok(());
}

if (order_price.base_denom != order_source_denom
&& order_price.base_denom != order_target_denom)
|| (order_price.quote_denom != order_source_denom
Expand All @@ -74,3 +87,45 @@ fn check_denom_error(

Ok(())
}

fn create_resp(
sender: &str,
new_order: &SpotOrder,
bank_msg: BankMsg,
storage: &mut dyn Storage,
) -> StdResult<Response<ElysMsg>> {
let resp = Response::new()
.add_attribute("order_id", new_order.order_id.to_string())
.add_message(bank_msg); // information message

if new_order.order_type != SpotOrderType::Market {
return Ok(resp);
}

let mut reply_infos = REPLY_INFO.load(storage)?;

let swap_msg = ElysMsg::amm_swap_exact_amount_in(
sender,
&new_order.order_amount,
&new_order.order_amm_routes,
Int128::zero(),
);

let info_id = if let Some(max_info) = reply_infos.iter().max_by_key(|info| info.id) {
max_info.id + 1
} else {
0
};

reply_infos.push(ReplyInfo {
id: info_id,
reply_type: ReplyType::SpotOrder,
data: Some(to_json_binary(&new_order.order_id)?),
});

REPLY_INFO.save(storage, &reply_infos)?;

let sub_msg = SubMsg::reply_always(swap_msg, info_id);

Ok(resp.add_submessage(sub_msg))
}
6 changes: 6 additions & 0 deletions src/action/execute/process_spot_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ fn send_token(
}

fn check_order(order: &SpotOrder, querier: &ElysQuerier) -> bool {
if order.order_type == SpotOrderType::Market {
return true;
}

let amm_swap_estimation =
match querier.amm_swap_estimation(&order.order_amm_routes, &order.order_amount) {
Ok(res) => res,
Expand All @@ -89,6 +93,7 @@ fn check_order(order: &SpotOrder, querier: &ElysQuerier) -> bool {
SpotOrderType::LimitSell => order_token_out <= amm_swap_estimation.token_out.amount,

SpotOrderType::StopLoss => order_token_out >= amm_swap_estimation.token_out.amount,
_ => false,
}
}

Expand All @@ -102,6 +107,7 @@ fn process_order(
SpotOrderType::LimitBuy => calculate_token_out_min_amount(order),
SpotOrderType::LimitSell => calculate_token_out_min_amount(order),
SpotOrderType::StopLoss => Int128::zero(),
SpotOrderType::Market => Int128::zero(),
};

let msg = ElysMsg::amm_swap_exact_amount_in(
Expand Down
129 changes: 129 additions & 0 deletions src/tests/create_spot_order/successful_create_market_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use crate::tests::get_order_id_from_events::get_order_id_from_events;

use super::*;

// This test case verifies the successful creation of a "market" order in the contract.
// A "market" order is designed to execute right after.
#[test]
fn successful_create_stop_loss_order() {
// Create a wallet for the "user" with an initial balance of 2 BTC.
let wallet = vec![("user", coins(2, "btc"))];

let prices = vec![
Price::new(
"btc",
Decimal::from_atomics(Uint128::new(30000), 0).unwrap(),
),
Price::new("usdc", Decimal::from_atomics(Uint128::new(1), 0).unwrap()),
];

// Initialize the ElysApp instance with the specified wallet.
let mut app = ElysApp::new_with_wallets(wallet);

// Set the BTC and USDC prices.
app.init_modules(|router, _, store| router.custom.set_prices(store, &prices))
.unwrap();

// Create a mock message to instantiate the contract with no initial orders.
let instantiate_msg = InstantiateMockMsg {
process_order_executor: "owner".to_string(),
orders: vec![],
};

// Create a contract wrapper and store its code.
let code = ContractWrapper::new(execute, instantiate, query).with_reply(reply);
let code_id = app.store_code(Box::new(code));

// Instantiate the contract with "owner" as the deployer.
let addr = app
.instantiate_contract(
code_id,
Addr::unchecked("owner"),
&instantiate_msg,
&[],
"Contract",
None,
)
.unwrap();

// User "user" creates a "market" order for BTC to USDC.
let resp = app
.execute_contract(
Addr::unchecked("user"),
addr.clone(),
&ExecuteMsg::CreateSpotOrder {
order_type: SpotOrderType::Market,
// Empty order price - not utilized in market orders
order_price: SpotOrderPrice {
base_denom: "".to_string(),
quote_denom: "".to_string(),
rate: Decimal::zero(),
},
order_amm_routes: vec![SwapAmountInRoute::new(1, "usdc")],
order_source_denom: "btc".to_string(),
order_target_denom: "usdc".to_string(),
},
&coins(2, "btc"), // User's BTC balance.
)
.unwrap();

// Verify that the "user" no longer has any BTC after creating the order.
assert_eq!(
app.wrap()
.query_balance("user", "btc")
.unwrap()
.amount
.u128(),
0
);

// Verify that the contract address has swap the 2 BTC for the order (market).
assert_eq!(
app.wrap()
.query_balance(&addr, "btc")
.unwrap()
.amount
.u128(),
0
);

// Verify that the contract address now holds the 60000 usdc.
assert_eq!(
app.wrap()
.query_balance(&addr, "usdc")
.unwrap()
.amount
.u128(),
60000
);

// Verify that an order ID is emitted in the contract's events.
assert!(get_order_id_from_events(&resp.events).is_some());

app.execute_contract(
Addr::unchecked("owner"),
addr.clone(),
&ExecuteMsg::ProcessSpotOrders {},
&[],
)
.unwrap();

// Verify that the user got his swaped token
assert_eq!(
app.wrap()
.query_balance("user", "usdc")
.unwrap()
.amount
.u128(),
60000
);

assert_eq!(
app.wrap()
.query_balance(&addr, "usdc")
.unwrap()
.amount
.u128(),
0
);
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod create_spot_order {
mod order_wrong_fund;
mod successful_create_limit_buy_order;
mod successful_create_limit_sell_order;
mod successful_create_market_order;
mod successful_create_stop_loss_order;
}

Expand Down
1 change: 1 addition & 0 deletions src/types/spot_order_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ pub enum SpotOrderType {
StopLoss,
LimitSell,
LimitBuy,
Market,
}

0 comments on commit fb1b155

Please sign in to comment.