From 4ee201dd2a10ffb2a0e3c36b79734a03d8ebeb9c Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Wed, 1 May 2024 12:58:27 +0100 Subject: [PATCH 1/7] feat: added max spot price check [WIP] --- contracts/sumtree-orderbook/src/constants.rs | 2 +- contracts/sumtree-orderbook/src/error.rs | 3 +++ contracts/sumtree-orderbook/src/order.rs | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/contracts/sumtree-orderbook/src/constants.rs b/contracts/sumtree-orderbook/src/constants.rs index 13741b4..c7d5bc4 100644 --- a/contracts/sumtree-orderbook/src/constants.rs +++ b/contracts/sumtree-orderbook/src/constants.rs @@ -11,7 +11,7 @@ pub const MAX_BATCH_CLAIM: u32 = 100; // TODO: optimize this using lazy_static pub fn max_spot_price() -> Decimal256 { - Decimal256::from_str("100000000000000000000000000000000000000").unwrap() + Decimal256::MAX } pub fn min_spot_price() -> Decimal256 { diff --git a/contracts/sumtree-orderbook/src/error.rs b/contracts/sumtree-orderbook/src/error.rs index 672fae7..f381d50 100644 --- a/contracts/sumtree-orderbook/src/error.rs +++ b/contracts/sumtree-orderbook/src/error.rs @@ -103,6 +103,9 @@ pub enum ContractError { #[error("Orderbook is inactive")] Inactive, + + #[error("Max spot price exceeded")] + MaxSpotPriceExceeded, } pub type ContractResult = Result; diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index 19509c2..6fc64af 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -1,4 +1,4 @@ -use crate::constants::{MAX_BATCH_CLAIM, MAX_TICK, MIN_TICK}; +use crate::constants::{max_spot_price, MAX_BATCH_CLAIM, MAX_TICK, MIN_TICK}; use crate::error::{ContractError, ContractResult}; use crate::state::{ add_directional_liquidity, new_order_id, orders, subtract_directional_liquidity, ORDERBOOK, @@ -54,6 +54,21 @@ pub fn place_limit( ); } + let max_spot_price = max_spot_price(); + let tick_price = tick_to_price(tick_id)?; + let claimed_price = amount_to_value( + order_direction.opposite(), + quantity, + tick_price, + RoundingDirection::Down, + )?; + + ensure!( + Decimal256::from_ratio(Uint256::from_u128(claimed_price.u128()), Uint256::one()) + <= max_spot_price, + ContractError::MaxSpotPriceExceeded + ); + // Determine the correct denom based on order direction let expected_denom = orderbook.get_expected_denom(&order_direction); From 7b1c1da6da931c25fb4384020933c83612429f67 Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Wed, 1 May 2024 16:23:29 +0100 Subject: [PATCH 2/7] fix: fixed max spot check --- contracts/sumtree-orderbook/src/order.rs | 5 +++-- .../sumtree-orderbook/src/tests/test_order.rs | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index a02aff0..aa78597 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -64,10 +64,11 @@ pub fn place_limit( quantity, tick_price, RoundingDirection::Down, - )?; + ); ensure!( - Decimal256::from_ratio(claimed_price, Uint256::one()) <= max_spot_price, + claimed_price.is_ok() + && Decimal256::from_ratio(claimed_price.unwrap(), Uint256::one()) <= max_spot_price, ContractError::MaxSpotPriceExceeded ); diff --git a/contracts/sumtree-orderbook/src/tests/test_order.rs b/contracts/sumtree-orderbook/src/tests/test_order.rs index ed2a6a6..96253a3 100644 --- a/contracts/sumtree-orderbook/src/tests/test_order.rs +++ b/contracts/sumtree-orderbook/src/tests/test_order.rs @@ -164,6 +164,24 @@ fn test_place_limit() { required: Uint128::new(100), }), }, + PlaceLimitTestCase { + name: "exceed max spot", + tick_id: MAX_TICK, + quantity: Uint128::MAX, + sent: Uint128::MAX, + order_direction: OrderDirection::Ask, + claim_bounty: None, + expected_error: Some(ContractError::MaxSpotPriceExceeded), + }, + // PlaceLimitTestCase { + // name: "exceed max spot", + // tick_id: MIN_TICK, + // quantity: Uint128::MAX, + // sent: Uint128::MAX, + // order_direction: OrderDirection::Bid, + // claim_bounty: None, + // expected_error: Some(ContractError::MaxSpotPriceExceeded), + // }, ]; for test in test_cases { From a05fa1df9620aa582a28614dc42a1aee8a9277d7 Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Wed, 8 May 2024 14:00:26 +0100 Subject: [PATCH 3/7] test: fixed failing tests --- .../sumtree-orderbook/src/tests/test_tick_math.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs index ddf5e6c..c592b26 100644 --- a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs +++ b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs @@ -26,7 +26,8 @@ fn test_tick_to_price() { let tick_price_test_cases = vec![ TickToPriceTestCase { tick_index: MAX_TICK, - expected_price: max_spot_price(), + expected_price: Decimal256::from_str("100000000000000000000000000000000000000") + .unwrap(), expected_error: None, }, TickToPriceTestCase { @@ -66,7 +67,8 @@ fn test_tick_to_price() { }, TickToPriceTestCase { tick_index: MAX_TICK - 1, - expected_price: max_spot_price() + expected_price: Decimal256::from_str("100000000000000000000000000000000000000") + .unwrap() .checked_sub(min_increment_near_max_price) .unwrap(), expected_error: None, @@ -129,7 +131,11 @@ fn test_tick_to_price() { match test.expected_error { Some(expected_err) => assert_eq!(result.unwrap_err(), expected_err), - None => assert_eq!(test.expected_price, result.unwrap()), + None => assert_eq!( + test.expected_price, + result.unwrap(), + "expected price and result did not match" + ), } } } From 4f54e8cc8846c99d35d4a33a9f7d0684a024eb4f Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Tue, 14 May 2024 22:14:40 +0100 Subject: [PATCH 4/7] fix: updated max tick and max spot price, fixed tests accordingly --- contracts/sumtree-orderbook/src/constants.rs | 4 +- contracts/sumtree-orderbook/src/order.rs | 7 ++- .../sumtree-orderbook/src/tests/test_order.rs | 43 ++++++++----------- .../src/tests/test_tick_math.rs | 8 ++-- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/contracts/sumtree-orderbook/src/constants.rs b/contracts/sumtree-orderbook/src/constants.rs index 0b98695..1afe897 100644 --- a/contracts/sumtree-orderbook/src/constants.rs +++ b/contracts/sumtree-orderbook/src/constants.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Decimal, Decimal256}; use std::str::FromStr; pub const MIN_TICK: i64 = -108000000; -pub const MAX_TICK: i64 = 342000000; +pub const MAX_TICK: i64 = 182402823; pub const EXPONENT_AT_PRICE_ONE: i32 = -6; pub const GEOMETRIC_EXPONENT_INCREMENT_DISTANCE_IN_TICKS: i64 = 9_000_000; // The swap fee expected by this contract @@ -12,7 +12,7 @@ pub const MAX_MAKER_FEE_PERCENTAGE: Decimal256 = Decimal256::percent(5); // TODO: optimize this using lazy_static pub fn max_spot_price() -> Decimal256 { - Decimal256::MAX + Decimal256::from_str("340282300000000000000").unwrap() } pub fn min_spot_price() -> Decimal256 { diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index 7d43c3c..c763412 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -56,17 +56,16 @@ pub fn place_limit( } let max_spot_price = max_spot_price(); - let tick_price = tick_to_price(tick_id)?; let claimed_price = amount_to_value( - order_direction.opposite(), + order_direction, quantity, - tick_price, + max_spot_price, RoundingDirection::Down, ); ensure!( claimed_price.is_ok() - && Decimal256::from_ratio(claimed_price.unwrap(), Uint256::one()) <= max_spot_price, + && Decimal256::from_ratio(claimed_price.unwrap(), Uint256::one()) <= Decimal256::MAX, ContractError::MaxSpotPriceExceeded ); diff --git a/contracts/sumtree-orderbook/src/tests/test_order.rs b/contracts/sumtree-orderbook/src/tests/test_order.rs index bf54df1..16b2e43 100644 --- a/contracts/sumtree-orderbook/src/tests/test_order.rs +++ b/contracts/sumtree-orderbook/src/tests/test_order.rs @@ -1,20 +1,12 @@ use std::str::FromStr; use crate::{ - constants::{MAX_TICK, MIN_TICK}, - error::ContractError, - order::*, - orderbook::*, - state::*, - sumtree::{ + constants::{MAX_TICK, MIN_TICK}, error::ContractError, order::*, orderbook::*, state::*, sumtree::{ node::{NodeType, TreeNode}, tree::get_root_node, - }, - tests::test_utils::{decimal256_from_u128, place_multiple_limit_orders}, - types::{ - FilterOwnerOrders, LimitOrder, MarketOrder, OrderDirection, Orderbook, TickState, TickValues, REPLY_ID_CLAIM, REPLY_ID_CLAIM_BOUNTY, REPLY_ID_MAKER_FEE, REPLY_ID_REFUND, - coin_u256, MsgSend256 - }, + }, tests::test_utils::{decimal256_from_u128, place_multiple_limit_orders}, types::{ + coin_u256, FilterOwnerOrders, LimitOrder, MarketOrder, MsgSend256, OrderDirection, Orderbook, TickState, TickValues, REPLY_ID_CLAIM, REPLY_ID_CLAIM_BOUNTY, REPLY_ID_MAKER_FEE, REPLY_ID_REFUND + } }; use cosmwasm_std::{ coin, testing::mock_dependencies, Addr, BankMsg, Coin, Empty, SubMsg, Uint128, Uint256, @@ -166,23 +158,23 @@ fn test_place_limit() { }), }, PlaceLimitTestCase { - name: "exceed max spot", + name: "max amount on max tick", tick_id: MAX_TICK, quantity: Uint128::MAX, sent: Uint128::MAX, + order_direction: OrderDirection::Bid, + claim_bounty: None, + expected_error: None, + }, + PlaceLimitTestCase { + name: "max amount on min tick", + tick_id: MIN_TICK, + quantity: Uint128::MAX, + sent: Uint128::MAX, order_direction: OrderDirection::Ask, claim_bounty: None, - expected_error: Some(ContractError::MaxSpotPriceExceeded), - }, - // PlaceLimitTestCase { - // name: "exceed max spot", - // tick_id: MIN_TICK, - // quantity: Uint128::MAX, - // sent: Uint128::MAX, - // order_direction: OrderDirection::Bid, - // claim_bounty: None, - // expected_error: Some(ContractError::MaxSpotPriceExceeded), - // }, + expected_error: None, + }, ]; for test in test_cases { @@ -4009,5 +4001,4 @@ fn test_maker_fee() { } } -} - +} \ No newline at end of file diff --git a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs index c592b26..de31a14 100644 --- a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs +++ b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs @@ -19,15 +19,14 @@ fn test_tick_to_price() { // which with an EXPONENT_AT_PRICE_ONE of -6 should be 10^31. let min_increment_near_max_price = Decimal256::from_ratio( Uint256::from(10u8) - .checked_pow((37 + EXPONENT_AT_PRICE_ONE) as u32) + .checked_pow((20 + EXPONENT_AT_PRICE_ONE) as u32) .unwrap(), Uint256::one(), ); let tick_price_test_cases = vec![ TickToPriceTestCase { tick_index: MAX_TICK, - expected_price: Decimal256::from_str("100000000000000000000000000000000000000") - .unwrap(), + expected_price: max_spot_price(), expected_error: None, }, TickToPriceTestCase { @@ -67,8 +66,7 @@ fn test_tick_to_price() { }, TickToPriceTestCase { tick_index: MAX_TICK - 1, - expected_price: Decimal256::from_str("100000000000000000000000000000000000000") - .unwrap() + expected_price: max_spot_price() .checked_sub(min_increment_near_max_price) .unwrap(), expected_error: None, From 019dde40ebe7418c52bcc32e4a8be344f6bbbd3e Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Tue, 14 May 2024 22:16:39 +0100 Subject: [PATCH 5/7] refactor: removed unnecessary check --- contracts/sumtree-orderbook/src/order.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index c763412..29722fe 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -63,11 +63,7 @@ pub fn place_limit( RoundingDirection::Down, ); - ensure!( - claimed_price.is_ok() - && Decimal256::from_ratio(claimed_price.unwrap(), Uint256::one()) <= Decimal256::MAX, - ContractError::MaxSpotPriceExceeded - ); + ensure!(claimed_price.is_ok(), ContractError::MaxSpotPriceExceeded); // Determine the correct denom based on order direction let expected_denom = orderbook.get_expected_denom(&order_direction); From 7d05c11f6b112d358ac83a88ea6640b0e2e00bf7 Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Wed, 15 May 2024 15:50:58 +0100 Subject: [PATCH 6/7] feat: added lazy static to max/min spot price variables --- contracts/sumtree-orderbook/Cargo.toml | 2 +- contracts/sumtree-orderbook/src/constants.rs | 12 +++++------- contracts/sumtree-orderbook/src/order.rs | 5 ++--- .../sumtree-orderbook/src/tests/test_tick_math.rs | 6 +++--- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/contracts/sumtree-orderbook/Cargo.toml b/contracts/sumtree-orderbook/Cargo.toml index 80baa18..2cf1f39 100644 --- a/contracts/sumtree-orderbook/Cargo.toml +++ b/contracts/sumtree-orderbook/Cargo.toml @@ -56,7 +56,7 @@ osmosis-std = "0.1.4" prost = { version = "0.11.2", default-features = false, features = [ "prost-derive", ] } - +lazy_static = "1.4.0" [dev-dependencies] cw-multi-test = "0.18.0" diff --git a/contracts/sumtree-orderbook/src/constants.rs b/contracts/sumtree-orderbook/src/constants.rs index 1afe897..129ad2c 100644 --- a/contracts/sumtree-orderbook/src/constants.rs +++ b/contracts/sumtree-orderbook/src/constants.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Decimal, Decimal256}; +use lazy_static::lazy_static; use std::str::FromStr; pub const MIN_TICK: i64 = -108000000; @@ -10,11 +11,8 @@ pub const EXPECTED_SWAP_FEE: Decimal = Decimal::zero(); pub const MAX_BATCH_CLAIM: u32 = 100; pub const MAX_MAKER_FEE_PERCENTAGE: Decimal256 = Decimal256::percent(5); -// TODO: optimize this using lazy_static -pub fn max_spot_price() -> Decimal256 { - Decimal256::from_str("340282300000000000000").unwrap() -} - -pub fn min_spot_price() -> Decimal256 { - Decimal256::from_str("0.000000000001").unwrap() +lazy_static! { + pub static ref MAX_SPOT_PRICE: Decimal256 = + Decimal256::from_str("340282300000000000000").unwrap(); + pub static ref MIN_SPOT_PRICE: Decimal256 = Decimal256::from_str("0.000000000001").unwrap(); } diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index 29722fe..cd4d7c9 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -1,4 +1,4 @@ -use crate::constants::{max_spot_price, MAX_BATCH_CLAIM, MAX_TICK, MIN_TICK}; +use crate::constants::{MAX_BATCH_CLAIM, MAX_SPOT_PRICE, MAX_TICK, MIN_TICK}; use crate::error::{ContractError, ContractResult}; use crate::state::{ add_directional_liquidity, get_maker_fee, new_order_id, orders, subtract_directional_liquidity, @@ -55,11 +55,10 @@ pub fn place_limit( ); } - let max_spot_price = max_spot_price(); let claimed_price = amount_to_value( order_direction, quantity, - max_spot_price, + *MAX_SPOT_PRICE, RoundingDirection::Down, ); diff --git a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs index de31a14..3bd7b32 100644 --- a/contracts/sumtree-orderbook/src/tests/test_tick_math.rs +++ b/contracts/sumtree-orderbook/src/tests/test_tick_math.rs @@ -26,7 +26,7 @@ fn test_tick_to_price() { let tick_price_test_cases = vec![ TickToPriceTestCase { tick_index: MAX_TICK, - expected_price: max_spot_price(), + expected_price: *MAX_SPOT_PRICE, expected_error: None, }, TickToPriceTestCase { @@ -66,14 +66,14 @@ fn test_tick_to_price() { }, TickToPriceTestCase { tick_index: MAX_TICK - 1, - expected_price: max_spot_price() + expected_price: MAX_SPOT_PRICE .checked_sub(min_increment_near_max_price) .unwrap(), expected_error: None, }, TickToPriceTestCase { tick_index: MIN_TICK, - expected_price: min_spot_price(), + expected_price: *MIN_SPOT_PRICE, expected_error: None, }, TickToPriceTestCase { From 92fc128b48d18289f5d68ed9cb00626cb9e3a98a Mon Sep 17 00:00:00 2001 From: Connor Barr Date: Wed, 15 May 2024 16:18:35 +0100 Subject: [PATCH 7/7] fix: spot price for claimed_price when placing an order is now based on order direction --- contracts/sumtree-orderbook/src/order.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/sumtree-orderbook/src/order.rs b/contracts/sumtree-orderbook/src/order.rs index 29722fe..a8db20c 100644 --- a/contracts/sumtree-orderbook/src/order.rs +++ b/contracts/sumtree-orderbook/src/order.rs @@ -1,4 +1,4 @@ -use crate::constants::{max_spot_price, MAX_BATCH_CLAIM, MAX_TICK, MIN_TICK}; +use crate::constants::{max_spot_price, min_spot_price, MAX_BATCH_CLAIM, MAX_TICK, MIN_TICK}; use crate::error::{ContractError, ContractResult}; use crate::state::{ add_directional_liquidity, get_maker_fee, new_order_id, orders, subtract_directional_liquidity, @@ -55,14 +55,20 @@ pub fn place_limit( ); } - let max_spot_price = max_spot_price(); + // The boundary max/min spot price given order direction + let boundary_spot_price = match order_direction { + OrderDirection::Bid => max_spot_price(), + OrderDirection::Ask => min_spot_price(), + }; + // The epxected price when this order is claimed let claimed_price = amount_to_value( order_direction, quantity, - max_spot_price, + boundary_spot_price, RoundingDirection::Down, ); + // If claimed_price returns an error then the order cannot be claimed ensure!(claimed_price.is_ok(), ContractError::MaxSpotPriceExceeded); // Determine the correct denom based on order direction