Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Orderbook]: Tick to price conversion and tests #41

Merged
merged 6 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions contracts/orderbook/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use cosmwasm_std::Decimal256;
use std::str::FromStr;

pub const MIN_TICK: i64 = -108000000;
pub const MAX_TICK: i64 = 342000000;
pub const EXPONENT_AT_PRICE_ONE: i32 = -6;
pub const GEOMETRIC_EXPONENT_INCREMENT_DISTANCE_IN_TICKS: i64 = 9_000_000;

// TODO: optimize this using lazy_static
pub fn max_spot_price() -> Decimal256 {
Decimal256::from_str("100000000000000000000000000000000000000").unwrap()
}

pub fn min_spot_price() -> Decimal256 {
Decimal256::from_str("0.000000000001").unwrap()
}
26 changes: 25 additions & 1 deletion contracts/orderbook/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use cosmwasm_std::{CoinsError, OverflowError, StdError, Uint128};
use cosmwasm_std::{
CheckedFromRatioError, CheckedMultiplyRatioError, CoinsError, ConversionOverflowError,
DecimalRangeExceeded, DivideByZeroError, OverflowError, StdError, Uint128,
};
use cw_utils::PaymentError;
use thiserror::Error;

Expand Down Expand Up @@ -41,6 +44,25 @@ pub enum ContractError {
#[error("Reply error: {id:?}, {error:?}")]
ReplyError { id: u64, error: String },

// Decimal-related errors
#[error("{0}")]
ConversionOverflow(#[from] ConversionOverflowError),

#[error("{0}")]
CheckedMultiplyRatio(#[from] CheckedMultiplyRatioError),

#[error("{0}")]
CheckedFromRatio(#[from] CheckedFromRatioError),

#[error("{0}")]
DivideByZero(#[from] DivideByZeroError),

#[error("{0}")]
DecimalRangeExceeded(#[from] DecimalRangeExceeded),

// Tick out of bounds error
#[error("Tick out of bounds: {tick_id:?}")]
TickOutOfBounds { tick_id: i64 },
#[error("Cannot fulfill order. Order ID: {order_id:?}, Book ID: {book_id:?}, Amount Required: {amount_required:?}, Amount Remaining: {amount_remaining:?} {reason:?}")]
InvalidFulfillment {
order_id: u64,
Expand All @@ -53,3 +75,5 @@ pub enum ContractError {
#[error("Mismatched order direction")]
MismatchedOrderDirection {},
}

pub type ContractResult<T> = Result<T, ContractError>;
2 changes: 2 additions & 0 deletions contracts/orderbook/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub mod constants;
pub mod contract;
mod error;
pub mod msg;
mod order;
mod orderbook;
pub mod state;
pub mod tick_math;
pub mod types;

#[cfg(test)]
Expand Down
4 changes: 4 additions & 0 deletions contracts/orderbook/src/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
pub mod types;

pub mod constants;
pub mod order;
pub mod orderbook;
pub mod tick_math;

pub use constants::*;
pub use order::*;
pub use orderbook::*;
pub use tick_math::*;
3 changes: 2 additions & 1 deletion contracts/orderbook/src/order.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::constants::{MAX_TICK, MIN_TICK};
use crate::error::ContractError;
use crate::state::ORDERBOOKS;
use crate::state::*;
use crate::state::{MAX_TICK, MIN_TICK, ORDERBOOKS};
use crate::types::{Fulfillment, LimitOrder, MarketOrder, OrderDirection, REPLY_ID_REFUND};
use cosmwasm_std::{
coin, ensure, ensure_eq, ensure_ne, BankMsg, Decimal, DepsMut, Env, MessageInfo, Order,
Expand Down
3 changes: 2 additions & 1 deletion contracts/orderbook/src/orderbook.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::constants::{MAX_TICK, MIN_TICK};
use crate::error::ContractError;
use crate::state::{new_orderbook_id, MAX_TICK, MIN_TICK, ORDERBOOKS};
use crate::state::{new_orderbook_id, ORDERBOOKS};
use crate::types::Orderbook;
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};

Expand Down
3 changes: 0 additions & 3 deletions contracts/orderbook/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ use crate::ContractError;
use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128};
use cw_storage_plus::{Bound, Index, IndexList, IndexedMap, Item, Map, MultiIndex};

pub const MIN_TICK: i64 = -108000000;
pub const MAX_TICK: i64 = 342000000;

// Counters for ID tracking
pub const ORDER_ID: Item<u64> = Item::new("order_id");
pub const ORDERBOOK_ID: Item<u64> = Item::new("orderbook_id");
Expand Down
1 change: 1 addition & 0 deletions contracts/orderbook/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod test_order;
pub mod test_orderbook;
pub mod test_state;
pub mod test_tick_math;
1 change: 1 addition & 0 deletions contracts/orderbook/src/tests/test_order.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
constants::{MAX_TICK, MIN_TICK},
error::ContractError,
order::*,
orderbook::*,
Expand Down
3 changes: 2 additions & 1 deletion contracts/orderbook/src/tests/test_orderbook.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
constants::{MAX_TICK, MIN_TICK},
orderbook::*,
state::{MAX_TICK, MIN_TICK, ORDERBOOKS},
state::ORDERBOOKS,
};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};

Expand Down
205 changes: 205 additions & 0 deletions contracts/orderbook/src/tests/test_tick_math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use crate::constants::*;
use crate::error::ContractError;
use crate::tick_math::{pow_ten, tick_to_price};
use cosmwasm_std::{Decimal256, Uint256};
use std::str::FromStr;

struct TickToPriceTestCase {
tick_index: i64,
expected_price: Decimal256,
expected_error: Option<ContractError>,
}

#[test]
fn test_tick_to_price() {
// This constant is used to test price iterations near max tick.
// It essentially derives the amount we expect price to increment by,
// 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)
.unwrap(),
Uint256::one(),
);
let tick_price_test_cases = vec![
TickToPriceTestCase {
tick_index: MAX_TICK,
expected_price: max_spot_price(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: MIN_TICK,
expected_price: Decimal256::from_str("0.000000000001").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 40000000,
expected_price: Decimal256::from_str("50000").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 4010000,
expected_price: Decimal256::from_str("5.01").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 40000001,
expected_price: Decimal256::from_str("50000.01").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -9999900,
expected_price: Decimal256::from_str("0.090001").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -2000,
expected_price: Decimal256::from_str("0.9998").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 40303000,
expected_price: Decimal256::from_str("53030").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: MAX_TICK - 1,
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_error: None,
},
TickToPriceTestCase {
tick_index: MIN_TICK + 1,
expected_price: Decimal256::from_str("0.000000000001000001").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -17765433,
expected_price: Decimal256::from_str("0.012345670000000000").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -17765432,
expected_price: Decimal256::from_str("0.012345680000000000").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -107765433,
expected_price: Decimal256::from_str("0.000000000001234567").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: -107765432,
expected_price: Decimal256::from_str("0.000000000001234568").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 81234567,
expected_price: Decimal256::from_str("1234567000").unwrap(),
expected_error: None,
},
// This case involves truncation in the previous case, so the expected price is adjusted accordingly
TickToPriceTestCase {
tick_index: 81234567, // Same tick index as the previous case due to truncation
expected_price: Decimal256::from_str("1234567000").unwrap(), // Expected price matches the truncated price
expected_error: None,
},
TickToPriceTestCase {
tick_index: 81234568,
expected_price: Decimal256::from_str("1234568000").unwrap(),
expected_error: None,
},
TickToPriceTestCase {
tick_index: 0,
expected_price: Decimal256::from_str("1").unwrap(),
expected_error: None,
},
];

for test in tick_price_test_cases {
let result = tick_to_price(test.tick_index);

match test.expected_error {
Some(expected_err) => assert_eq!(result.unwrap_err(), expected_err),
None => assert_eq!(test.expected_price, result.unwrap()),
}
}
}

#[test]
fn test_tick_to_price_error_cases() {
let test_cases = vec![
TickToPriceTestCase {
tick_index: MAX_TICK + 1,
expected_price: Decimal256::zero(),
expected_error: Some(ContractError::TickOutOfBounds {
tick_id: MAX_TICK + 1,
}),
},
TickToPriceTestCase {
tick_index: MIN_TICK - 1,
expected_price: Decimal256::zero(),
expected_error: Some(ContractError::TickOutOfBounds {
tick_id: MIN_TICK - 1,
}),
},
];

for test in test_cases {
let result = tick_to_price(test.tick_index);
assert!(result.is_err());
if let Some(expected_err) = test.expected_error {
assert_eq!(result.unwrap_err(), expected_err);
}
}
}

#[test]
fn test_pow_ten() {
struct PowTenTestCase {
exponent: i32,
expected_result: Decimal256,
}

let test_cases = vec![
PowTenTestCase {
exponent: 0,
expected_result: Decimal256::from_str("1").unwrap(),
},
PowTenTestCase {
exponent: 1,
expected_result: Decimal256::from_str("10").unwrap(),
},
PowTenTestCase {
exponent: -1,
expected_result: Decimal256::from_str("0.1").unwrap(),
},
PowTenTestCase {
exponent: 5,
expected_result: Decimal256::from_str("100000").unwrap(),
},
PowTenTestCase {
exponent: -5,
expected_result: Decimal256::from_str("0.00001").unwrap(),
},
PowTenTestCase {
exponent: 10,
expected_result: Decimal256::from_str("10000000000").unwrap(),
},
PowTenTestCase {
exponent: -10,
expected_result: Decimal256::from_str("0.0000000001").unwrap(),
},
];

for test in test_cases {
let result = pow_ten(test.exponent).unwrap();
assert_eq!(test.expected_result, result);
}
}
Loading
Loading