From 5b47c276af02ed2856c9561beb69c51444192f78 Mon Sep 17 00:00:00 2001 From: maxrobot Date: Tue, 3 Sep 2024 13:55:12 +0100 Subject: [PATCH] feat: first order and market traits are implemented --- Cargo.lock | 24 +- packages/injective-std/Cargo.toml | 10 +- .../src/traits/exchange/market.rs | 94 +++ .../injective-std/src/traits/exchange/mod.rs | 2 + .../src/traits/exchange/order.rs | 142 ++++ packages/injective-std/src/traits/general.rs | 721 ++++++++++++++++++ packages/injective-std/src/traits/market.rs | 25 - packages/injective-std/src/traits/mod.rs | 3 +- 8 files changed, 985 insertions(+), 36 deletions(-) create mode 100644 packages/injective-std/src/traits/exchange/market.rs create mode 100644 packages/injective-std/src/traits/exchange/mod.rs create mode 100644 packages/injective-std/src/traits/exchange/order.rs create mode 100644 packages/injective-std/src/traits/general.rs delete mode 100644 packages/injective-std/src/traits/market.rs diff --git a/Cargo.lock b/Cargo.lock index c6fff067..45dd8546 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "cw2", "injective-cosmwasm 0.3.0", "injective-math 0.3.0", - "injective-std 1.13.0", + "injective-std 1.13.2", "prost 0.12.6", "schemars", "serde 1.0.204", @@ -1616,7 +1616,7 @@ dependencies = [ "cw2", "injective-cosmwasm 0.3.0", "injective-math 0.3.0", - "injective-std 1.13.0", + "injective-std 1.13.2", "injective-test-tube", "injective-testing", "prost 0.12.6", @@ -1655,10 +1655,12 @@ dependencies = [ [[package]] name = "injective-std" version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0e5193cb9520754f60b9e9af08a662ddf298d2e1a579200b9a447064b64db8b" dependencies = [ "chrono", "cosmwasm-std 2.1.1", - "injective-std-derive 1.13.0", + "injective-std-derive 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "prost 0.12.6", "prost-types 0.12.6", "schemars", @@ -1668,18 +1670,22 @@ dependencies = [ [[package]] name = "injective-std" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e5193cb9520754f60b9e9af08a662ddf298d2e1a579200b9a447064b64db8b" +version = "1.13.2" dependencies = [ "chrono", + "cosmwasm-schema 1.5.5", "cosmwasm-std 2.1.1", - "injective-std-derive 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-storage-plus", + "hex", + "injective-math 0.3.0", + "injective-std-derive 1.13.0", "prost 0.12.6", "prost-types 0.12.6", "schemars", "serde 1.0.204", "serde-cw-value", + "serde_repr", + "serde_test", ] [[package]] @@ -1721,7 +1727,7 @@ dependencies = [ "cosmwasm-std 2.1.1", "hex", "injective-cosmwasm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "injective-std 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "injective-std 1.13.0", "prost 0.12.6", "serde 1.0.204", "serde_json 1.0.122", @@ -1739,7 +1745,7 @@ dependencies = [ "cw-multi-test", "injective-cosmwasm 0.3.0", "injective-math 0.3.0", - "injective-std 1.13.0", + "injective-std 1.13.2", "injective-test-tube", "prost 0.12.6", "rand 0.4.6", diff --git a/packages/injective-std/Cargo.toml b/packages/injective-std/Cargo.toml index a94f6374..a8c4c62b 100644 --- a/packages/injective-std/Cargo.toml +++ b/packages/injective-std/Cargo.toml @@ -5,16 +5,24 @@ license = "MIT OR Apache-2.0" name = "injective-std" readme = "README.md" repository = "https://github.com/InjectiveLabs/cw-injective/tree/dev/packages/injective-std" -version = "1.13.0" +version = "1.13.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chrono = { workspace = true } +cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +hex = { workspace = true } +injective-math = { workspace = true, path = "../injective-math" } injective-std-derive = { workspace = true, path = "../injective-std-derive" } prost = { workspace = true } prost-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde-cw-value = { workspace = true } +serde_repr = { workspace = true } + +[dev-dependencies] +serde_test = { workspace = true } diff --git a/packages/injective-std/src/traits/exchange/market.rs b/packages/injective-std/src/traits/exchange/market.rs new file mode 100644 index 00000000..3e66574b --- /dev/null +++ b/packages/injective-std/src/traits/exchange/market.rs @@ -0,0 +1,94 @@ +use crate::{ + traits::general::MarketId, + types::injective::exchange::v1beta1::{DerivativeMarket, ExchangeQuerier, SpotMarket}, +}; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Deps, Empty, StdError, StdResult}; +use injective_math::FPDecimal; +use schemars::JsonSchema; +use std::fmt; + +impl GenericMarket for DerivativeMarket { + fn get_ticker(&self) -> &str { + &self.ticker + } + + fn get_quote_denom(&self) -> &str { + &self.quote_denom + } + + fn get_maker_fee_rate(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.maker_fee_rate) + } + + fn get_taker_fee_rate(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.taker_fee_rate) + } + + fn get_market_id(&self) -> MarketId { + MarketId::new(self.market_id.clone()).unwrap() + } + + fn get_status(&self) -> i32 { + self.status + } + + fn get_min_price_tick_size(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.min_price_tick_size) + } + + fn min_quantity_tick_size(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.min_quantity_tick_size) + } +} + +impl GenericMarket for SpotMarket { + fn get_ticker(&self) -> &str { + &self.ticker + } + + fn get_quote_denom(&self) -> &str { + &self.quote_denom + } + + fn get_maker_fee_rate(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.maker_fee_rate) + } + + fn get_taker_fee_rate(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.taker_fee_rate) + } + + fn get_market_id(&self) -> MarketId { + MarketId::new(self.market_id.clone()).unwrap() + } + + fn get_status(&self) -> i32 { + self.status + } + + fn get_min_price_tick_size(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.min_price_tick_size) + } + + fn min_quantity_tick_size(&self) -> FPDecimal { + FPDecimal::must_from_str(&self.min_quantity_tick_size) + } +} + +pub trait GenericMarket { + fn get_ticker(&self) -> &str; + fn get_quote_denom(&self) -> &str; + fn get_maker_fee_rate(&self) -> FPDecimal; + fn get_taker_fee_rate(&self) -> FPDecimal; + fn get_market_id(&self) -> MarketId; + fn get_status(&self) -> i32; + fn get_min_price_tick_size(&self) -> FPDecimal; + fn min_quantity_tick_size(&self) -> FPDecimal; +} + +pub enum MarketType { + Spot, + Derivative, +} diff --git a/packages/injective-std/src/traits/exchange/mod.rs b/packages/injective-std/src/traits/exchange/mod.rs new file mode 100644 index 00000000..7b2470cc --- /dev/null +++ b/packages/injective-std/src/traits/exchange/mod.rs @@ -0,0 +1,2 @@ +pub mod market; +pub mod order; diff --git a/packages/injective-std/src/traits/exchange/order.rs b/packages/injective-std/src/traits/exchange/order.rs new file mode 100644 index 00000000..1542789c --- /dev/null +++ b/packages/injective-std/src/traits/exchange/order.rs @@ -0,0 +1,142 @@ +use crate::{ + traits::general::{MarketId, SubaccountId}, + types::injective::exchange::v1beta1::{DerivativeLimitOrder, DerivativeOrder, OrderData, OrderInfo, SpotLimitOrder, SpotOrder}, +}; + +use cosmwasm_std::Addr; +use injective_math::FPDecimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[derive(Serialize_repr, Deserialize_repr, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[repr(u8)] +pub enum OrderSide { + Unspecified = 0, + Buy = 1, + Sell = 2, +} + +#[derive(Serialize_repr, Deserialize_repr, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[repr(u8)] +pub enum OrderType { + Undefined = 0, + Buy = 1, + Sell = 2, + StopBuy = 3, + StopSell = 4, + TakeBuy = 5, + TakeSell = 6, + BuyPo = 7, + SellPo = 8, + BuyAtomic = 9, + SellAtomic = 10, +} + +impl OrderType { + pub fn from_i32(value: i32) -> OrderType { + match value { + 0 => OrderType::Undefined, + 1 => OrderType::Buy, + 2 => OrderType::Sell, + 3 => OrderType::StopBuy, + 4 => OrderType::StopSell, + 5 => OrderType::TakeBuy, + 6 => OrderType::TakeSell, + 7 => OrderType::BuyPo, + 8 => OrderType::SellPo, + 9 => OrderType::BuyAtomic, + 10 => OrderType::SellAtomic, + _ => unimplemented!("Order type not supported!"), + } + } +} + +pub trait GenericOrder { + fn get_order_type(&self) -> OrderType; + fn get_order_info(&self) -> &Option; + fn get_trigger_price(&self) -> Option; + fn is_buy(&self) -> bool; + fn is_sell(&self) -> bool; +} + +impl SpotLimitOrder { + pub fn new(order_info: OrderInfo, order_type: OrderType, fillable: FPDecimal, trigger_price: FPDecimal, order_hash: String) -> Self { + SpotLimitOrder { + order_info: Some(order_info), + order_type: order_type as i32, + fillable: fillable.to_string(), + trigger_price: trigger_price.to_string(), + order_hash: order_hash.into(), + } + } +} + +impl GenericOrder for SpotLimitOrder { + fn is_buy(&self) -> bool { + self.order_type == OrderType::Buy as i32 || self.order_type == OrderType::BuyPo as i32 || self.order_type == OrderType::BuyAtomic as i32 + } + + fn is_sell(&self) -> bool { + self.order_type == OrderType::Sell as i32 || self.order_type == OrderType::SellPo as i32 || self.order_type == OrderType::SellAtomic as i32 + } + + fn get_order_type(&self) -> OrderType { + OrderType::from_i32(self.order_type) + } + + fn get_order_info(&self) -> &Option { + &self.order_info + } + + fn get_trigger_price(&self) -> Option { + Some(FPDecimal::must_from_str(&self.trigger_price)) + } +} + +impl SpotOrder { + pub fn new( + price: FPDecimal, + quantity: FPDecimal, + order_type: OrderType, + market_id: &MarketId, + subaccount_id: SubaccountId, + fee_recipient: String, + cid: Option, + ) -> Self { + SpotOrder { + market_id: market_id.to_string(), + order_info: Some(OrderInfo { + subaccount_id: subaccount_id.to_string(), + fee_recipient, + price: price.to_string(), + quantity: quantity.to_string(), + cid: cid.unwrap_or_default(), + }), + order_type: order_type as i32, + trigger_price: "".to_string(), + } + } +} + +impl GenericOrder for SpotOrder { + fn is_buy(&self) -> bool { + self.order_type == OrderType::Buy as i32 || self.order_type == OrderType::BuyPo as i32 || self.order_type == OrderType::BuyAtomic as i32 + } + + fn is_sell(&self) -> bool { + self.order_type == OrderType::Sell as i32 || self.order_type == OrderType::SellPo as i32 || self.order_type == OrderType::SellAtomic as i32 + } + + fn get_order_type(&self) -> OrderType { + OrderType::from_i32(self.order_type) + } + + fn get_order_info(&self) -> &Option { + &self.order_info + } + + fn get_trigger_price(&self) -> Option { + Some(FPDecimal::must_from_str(&self.trigger_price)) + } +} diff --git a/packages/injective-std/src/traits/general.rs b/packages/injective-std/src/traits/general.rs new file mode 100644 index 00000000..ea93d2a8 --- /dev/null +++ b/packages/injective-std/src/traits/general.rs @@ -0,0 +1,721 @@ +use crate::types::injective::exchange::v1beta1::ExchangeQuerier; + +use cosmwasm_std::{Coin, Deps, Empty, StdError, StdResult}; +use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; +use injective_math::FPDecimal; +use schemars::JsonSchema; +use serde::{de::Error, ser::Error as SerError, Deserialize, Deserializer, Serialize, Serializer}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use std::fmt; + +pub enum MarketType { + Spot, + Derivative, +} + +#[derive(Serialize_repr, Deserialize_repr, Default, Clone, Debug, PartialEq, Eq, JsonSchema, Copy)] +#[repr(i32)] +pub enum AtomicMarketOrderAccessLevel { + #[default] + Nobody = 0, + BeginBlockerSmartContractsOnly = 1, + SmartContractsOnly = 2, + Everyone = 3, +} + +#[derive(Serialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)] +pub struct MarketId(String); + +impl fmt::Display for MarketId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl MarketId { + pub fn new(market_id_s: S) -> StdResult + where + S: Into, + { + let market_id = market_id_s.into(); + + if !market_id.starts_with("0x") { + return Err(StdError::generic_err("Invalid prefix: market_id must start with 0x")); + } + + if market_id.len() != 66 { + return Err(StdError::generic_err("Invalid length: market_id must be exactly 66 characters")); + } + + Ok(Self(market_id.to_lowercase())) + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn validate(self, deps: &Deps, market_type: MarketType) -> StdResult { + let querier = ExchangeQuerier::new(&deps.querier); + + match market_type { + MarketType::Spot => { + let res = querier.spot_market(self.to_string())?; + + if res.market.is_none() { + return Err(StdError::generic_err("Market not found")); + } + } + MarketType::Derivative => { + let res = querier.derivative_market(self.to_string())?; + + if res.market.is_none() { + return Err(StdError::generic_err("Market not found")); + } + } + }; + + Ok(self) + } + + pub fn unchecked(market_id_s: S) -> Self + where + S: Into, + { + Self(market_id_s.into().to_lowercase()) + } +} + +#[allow(clippy::from_over_into)] +impl Into for MarketId { + fn into(self) -> String { + self.0 + } +} + +impl<'a> From<&'a str> for MarketId { + fn from(s: &'a str) -> Self { + MarketId::new(s).unwrap() + } +} + +impl<'de> Deserialize<'de> for MarketId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let market_id = String::deserialize(deserializer)?; + + if !market_id.starts_with("0x") { + let error_message = format!("Invalid prefix in deserialization: market_id must start with 0x, received {}", market_id); + return Err(D::Error::custom(error_message)); + } + + if market_id.len() != 66 { + let error_message = format!( + "Invalid length in deserialization: market_id must be exactly 66 characters, received {}", + market_id + ); + return Err(D::Error::custom(error_message)); + } + + Ok(MarketId::unchecked(market_id)) + } +} + +#[derive(Serialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)] +pub struct SubaccountId(String); + +impl SubaccountId { + pub fn new(subaccount_id_s: S) -> Result + where + S: Into, + { + let subaccount_id = subaccount_id_s.into(); + + if !subaccount_id.starts_with("0x") { + return Err(StdError::generic_err("Invalid prefix: subaccount_id must start with 0x")); + } + + if subaccount_id.len() != 66 { + return Err(StdError::generic_err("Invalid length: subaccount_id must be exactly 66 characters")); + } + + Ok(Self(subaccount_id.to_lowercase())) + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn unchecked(subaccount_id_s: S) -> Self + where + S: Into, + { + Self(subaccount_id_s.into().to_lowercase()) + } + + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl<'de> Deserialize<'de> for SubaccountId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let subaccount_id = String::deserialize(deserializer)?; + + if !subaccount_id.starts_with("0x") { + let error_message = format!( + "Invalid prefix in deserialization: subaccount_id must start with 0x, received {}", + subaccount_id + ); + return Err(D::Error::custom(error_message)); + } + + if subaccount_id.len() != 66 { + let error_message = format!( + "Invalid length in deserialization: subaccount_id must be exactly 66 characters, received {}", + subaccount_id + ); + return Err(D::Error::custom(error_message)); + } + + Ok(SubaccountId::unchecked(subaccount_id)) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)] +pub struct ShortSubaccountId(String); + +const MAX_SHORT_SUBACCOUNT_NONCE: u16 = 999; + +impl ShortSubaccountId { + pub fn new(id_s: S) -> Result + where + S: Into, + { + let id = id_s.into(); + let as_short = Self(id); + as_short.validate() + } + + pub fn must_new(id_s: S) -> ShortSubaccountId + where + S: Into, + { + let id = id_s.into(); + let as_short = Self(id); + as_short.validate().unwrap() + } + + pub fn as_str(&self) -> &str { + self.0.as_str() + } + + pub fn unchecked(id_s: S) -> Self + where + S: Into, + { + Self(id_s.into()) + } + + pub fn validate(&self) -> StdResult { + let as_decimal = match u32::from_str_radix(self.as_str(), 16) { + Ok(dec) => Ok(dec), + Err(_) => Err(StdError::generic_err(format!( + "Invalid value: ShortSubaccountId was not a hexadecimal number: {}", + &self.0 + ))), + }; + + match as_decimal?.to_string().parse::() { + Ok(value) if value <= MAX_SHORT_SUBACCOUNT_NONCE => Ok(self.clone()), + _ => Err(StdError::generic_err(format!( + "Invalid value: ShortSubaccountId must be a number between 0-999, but {} was received", + &self.0 + ))), + } + } + + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl<'de> Deserialize<'de> for ShortSubaccountId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let id = String::deserialize(deserializer)?; + + match id.parse::() { + Ok(value) if value <= MAX_SHORT_SUBACCOUNT_NONCE => Ok(ShortSubaccountId::unchecked(format!("{:03x}", value))), + _ => { + let maybe_long = SubaccountId::unchecked(id); + let maybe_short: ShortSubaccountId = ShortSubaccountId::from(maybe_long); + maybe_short.validate().map_err(|e| D::Error::custom(e.to_string())) + } + } + } +} + +impl Serialize for ShortSubaccountId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.validate().map_err(|e| S::Error::custom(e.to_string()))?; + + let as_decimal = match u32::from_str_radix(self.0.as_str(), 16) { + Ok(dec) => Ok(dec), + Err(_) => Err(S::Error::custom(format!( + "Invalid value: ShortSubaccountId was not a hexadecimal number: {}", + &self.0 + ))), + }; + + serializer.serialize_str(&format!("{:0>3}", as_decimal?.to_string())) + } +} + +impl From for ShortSubaccountId { + fn from(subaccount_id: SubaccountId) -> Self { + let last_three_chars = &subaccount_id.as_str()[subaccount_id.as_str().len() - 3..]; + ShortSubaccountId::unchecked(last_three_chars.to_string()) + } +} + +impl fmt::Display for SubaccountId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +#[allow(clippy::from_over_into)] +impl Into for SubaccountId { + fn into(self) -> String { + self.0 + } +} + +impl KeyDeserialize for SubaccountId { + type Output = SubaccountId; + + const KEY_ELEMS: u16 = 42; + + #[inline(always)] + fn from_vec(value: Vec) -> std::result::Result { + Ok(SubaccountId::unchecked(String::from_vec(value)?)) + } + + fn from_slice(value: &[u8]) -> StdResult { + Self::from_vec(value.to_vec()) + } +} + +impl<'a> PrimaryKey<'a> for SubaccountId { + type Prefix = (); + type SubPrefix = (); + type Suffix = Self; + type SuperSuffix = Self; + + fn key(&self) -> Vec { + // this is simple, we don't add more prefixes + vec![Key::Ref(self.as_bytes())] + } +} + +impl<'a> Prefixer<'a> for SubaccountId { + fn prefix(&self) -> Vec { + vec![Key::Ref(self.as_bytes())] + } +} + +impl KeyDeserialize for &SubaccountId { + type Output = SubaccountId; + + const KEY_ELEMS: u16 = 42; + + #[inline(always)] + fn from_vec(value: Vec) -> StdResult { + Self::Output::from_vec(value) + } +} + +impl AsRef for SubaccountId { + #[inline] + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema)] +pub struct Hash([u8; 32]); + +impl Hash { + pub fn new(bytes: [u8; 32]) -> Hash { + Hash(bytes) + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + pub fn to_hex(&self) -> String { + hex::encode(self.0) + } + + pub fn from_hex>(s: T) -> StdResult { + let mut bytes = [0u8; 32]; + hex::decode_to_slice(s, &mut bytes).map_err(|e| StdError::generic_err(e.to_string()))?; + Ok(Hash::new(bytes)) + } +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{}", self.to_hex()) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::StdError; + use serde_test::{assert_de_tokens, assert_ser_tokens, Token}; + use std::panic::catch_unwind; + + use crate::traits::general::{MarketId, ShortSubaccountId, SubaccountId}; + + #[test] + fn unchecked_subaccount_id_to_lowercase() { + let subaccount_id = SubaccountId::unchecked("0xB5e09b93aCEb70C1711aF078922fA256011D7e56000000000000000000000045"); + let subaccount_id_str: String = subaccount_id.into(); + assert_eq!( + subaccount_id_str, + "0xB5e09b93aCEb70C1711aF078922fA256011D7e56000000000000000000000045".to_lowercase() + ); + } + + #[test] + fn unchecked_market_id_to_lowercase() { + let market_id = MarketId::unchecked("0x01EDFAB47F124748DC89998EB33144AF734484BA07099014594321729A0CA16B"); + let market_id_str: String = market_id.into(); + assert_eq!( + market_id_str, + "0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b".to_lowercase() + ); + } + + #[test] + fn checked_subaccount_id_to_lowercase() { + let subaccount_id = SubaccountId::new("0xB5e09b93aCEb70C1711aF078922fA256011D7e56000000000000000000000045").unwrap(); + let subaccount_id_str: String = subaccount_id.into(); + assert_eq!( + subaccount_id_str, + "0xB5e09b93aCEb70C1711aF078922fA256011D7e56000000000000000000000045".to_lowercase() + ); + } + + #[test] + fn checked_market_id_to_lowercase() { + let market_id = MarketId::new("0x01EDFAB47F124748DC89998EB33144AF734484BA07099014594321729A0CA16B").unwrap(); + let market_id_str: String = market_id.into(); + assert_eq!( + market_id_str, + "0x01edfab47f124748dc89998eb33144af734484ba07099014594321729a0ca16b".to_lowercase() + ); + } + + #[test] + fn subaccount_id_checks() { + let wrong_prefix_err = SubaccountId::new("00B5e09b93aCEb70C1711aF078922fA256011D7e56000000000000000000000045").unwrap_err(); + assert_eq!( + wrong_prefix_err, + StdError::generic_err("Invalid prefix: subaccount_id must start with 0x") + ); + + let wrong_length_err = SubaccountId::new("0xB5e09b93aCEb70C1711aF078922fA256011D7e5600000000000000000000004").unwrap_err(); + assert_eq!( + wrong_length_err, + StdError::generic_err("Invalid length: subaccount_id must be exactly 66 characters") + ); + + let wrong_length_err = SubaccountId::new("0xB5e09b93aCEb70C1711aF078922fA256011D7e560000000000000000000000451").unwrap_err(); + assert_eq!( + wrong_length_err, + StdError::generic_err("Invalid length: subaccount_id must be exactly 66 characters") + ); + } + + #[test] + fn market_id_checks() { + let wrong_prefix_err = MarketId::new("0001EDFAB47F124748DC89998EB33144AF734484BA07099014594321729A0CA16B").unwrap_err(); + assert_eq!(wrong_prefix_err, StdError::generic_err("Invalid prefix: market_id must start with 0x")); + + let wrong_length_err = MarketId::new("0x01EDFAB47F124748DC89998EB33144AF734484BA07099014594321729A0CA16").unwrap_err(); + assert_eq!( + wrong_length_err, + StdError::generic_err("Invalid length: market_id must be exactly 66 characters") + ); + + let wrong_length_err = MarketId::new("0x01EDFAB47F124748DC89998EB33144AF734484BA07099014594321729A0CA16B2").unwrap_err(); + assert_eq!( + wrong_length_err, + StdError::generic_err("Invalid length: market_id must be exactly 66 characters") + ); + } + + #[test] + fn subaccount_id_unchecked_works() { + let a = SubaccountId::unchecked("123"); + let aa = SubaccountId::unchecked(String::from("123")); + let b = SubaccountId::unchecked("be"); + assert_eq!(a, aa); + assert_ne!(a, b); + } + + #[test] + fn subaccount_id_as_str_works() { + let subaccount_id = SubaccountId::unchecked("amazing-id"); + assert_eq!(subaccount_id.as_str(), "amazing-id"); + } + + #[test] + fn subaccount_id_as_bytes_works() { + let subaccount_id = SubaccountId::unchecked("literal-string"); + assert_eq!( + subaccount_id.as_bytes(), + [108, 105, 116, 101, 114, 97, 108, 45, 115, 116, 114, 105, 110, 103] + ); + } + + #[test] + fn subaccount_id_implements_as_ref_for_str() { + let subaccount_id = SubaccountId::unchecked("literal-string"); + assert_eq!(subaccount_id.as_ref(), "literal-string"); + } + + #[test] + fn subaccount_id_implements_display() { + let subaccount_id = SubaccountId::unchecked("literal-string"); + assert_eq!(format!("{}", subaccount_id), "literal-string"); + } + + #[test] + fn smallest_hex_short_subaccount_id_works() { + let as_short = ShortSubaccountId::new("001"); + assert!(as_short.is_ok(), "001 should be a valid short subaccount id"); + assert_eq!(as_short.unwrap().as_str(), "001", "short subaccount id should be 001"); + } + + #[test] + fn hex_short_subaccount_id_works() { + let as_short = ShortSubaccountId::new("00a"); + assert!(as_short.is_ok(), "00a should be a valid short subaccount id"); + assert_eq!(as_short.unwrap().as_str(), "00a", "short subaccount id should be 00a"); + } + + #[test] + fn must_new_hex_short_subaccount_id_works() { + let as_short = ShortSubaccountId::must_new("00a"); + assert_eq!(as_short.as_str(), "00a", "short subaccount id should be 00a"); + } + + #[test] + fn hex_short_subaccount_id_works_2() { + let as_short = ShortSubaccountId::new("a"); + assert!(as_short.is_ok(), "a should be a valid short subaccount id"); + assert_eq!(as_short.unwrap().as_str(), "a", "short subaccount id should be a"); + } + + #[test] + fn hex_short_subaccount_id_works_3() { + let as_short = ShortSubaccountId::new("010"); + assert!(as_short.is_ok(), "010 should be a valid short subaccount id"); + assert_eq!(as_short.unwrap().as_str(), "010", "short subaccount id should be 010"); + } + + #[test] + fn biggest_hex_short_subaccount_id_works() { + let as_short = ShortSubaccountId::new("3E7"); + assert!(as_short.is_ok(), "3E7 should be a valid short subaccount id"); + assert_eq!(as_short.unwrap().as_str(), "3E7", "short subaccount id should be 3E7"); + } + + #[test] + fn too_big_hex_short_subaccount_id_returns_err() { + let as_short = ShortSubaccountId::new("3E8"); + assert!(as_short.is_err(), "3E8 should not be a valid short subaccount id"); + } + + #[test] + #[should_panic] + fn must_new_too_big_hex_short_subaccount_id_panics() { + ShortSubaccountId::must_new("3E8"); + } + + #[test] + fn random_string_short_subaccount_id_returns_err() { + let as_short = ShortSubaccountId::new("1ag"); + assert!(as_short.is_err(), "1ag should not be a valid short subaccount id"); + } + + #[test] + fn smallest_hex_short_subaccount_id_works_unchecked() { + let as_short = ShortSubaccountId::unchecked("001"); + assert_eq!(as_short.as_str(), "001", "unchecked short subaccount id should be 001"); + } + + #[test] + fn hex_short_subaccount_id_works_unchecked() { + let as_short = ShortSubaccountId::unchecked("00a"); + assert_eq!(as_short.as_str(), "00a", "unchecked short subaccount id should be 00a"); + } + + #[test] + fn hex_short_subaccount_id_works_2_unchecked() { + let as_short = ShortSubaccountId::unchecked("a"); + assert_eq!(as_short.as_str(), "a", "unchecked short subaccount id should be a"); + } + + #[test] + fn hex_short_subaccount_id_works_3_unchecked() { + let as_short = ShortSubaccountId::unchecked("010"); + assert_eq!(as_short.as_str(), "010", "unchecked short subaccount id should be 010"); + } + + #[test] + fn biggest_hex_short_subaccount_id_works_unchecked() { + let as_short = ShortSubaccountId::unchecked("3E7"); + assert_eq!(as_short.as_str(), "3E7", "unchecked short subaccount id should be 3E7"); + } + + #[test] + fn too_big_hex_short_subaccount_id_returns_unchecked_input() { + let as_short = ShortSubaccountId::unchecked("3E8"); + assert_eq!(as_short.as_str(), "3E8", "unchecked short subaccount id should be 3E8"); + } + + #[test] + fn random_string_short_subaccount_id_returns_unchecked_input() { + let as_short = ShortSubaccountId::unchecked("1ag"); + assert_eq!(as_short.as_str(), "1ag", "unchecked short subaccount id should be 1ag"); + } + + #[test] + fn smallest_hex_short_subaccount_is_correctly_serialized() { + let short_subaccount = ShortSubaccountId::unchecked("001"); + assert_ser_tokens(&short_subaccount, &[Token::Str("001")]); + } + + #[test] + fn hex_short_subaccount_is_correctly_serialized_as_decimal() { + let short_subaccount = ShortSubaccountId::unchecked("00a"); + assert_ser_tokens(&short_subaccount, &[Token::Str("010")]); + } + + #[test] + fn hex_short_subaccount_is_correctly_serialized_as_decimal_2() { + let short_subaccount = ShortSubaccountId::unchecked("a"); + assert_ser_tokens(&short_subaccount, &[Token::Str("010")]); + } + + #[test] + fn hex_short_subaccount_is_correctly_serialized_as_decimal_3() { + let short_subaccount = ShortSubaccountId::unchecked("010"); + assert_ser_tokens(&short_subaccount, &[Token::Str("016")]); + } + + #[test] + fn biggest_hex_short_subaccount_is_correctly_serialized_as_decimal() { + let short_subaccount = ShortSubaccountId::unchecked("3E7"); + assert_ser_tokens(&short_subaccount, &[Token::Str("999")]); + } + + #[test] + #[should_panic] + fn too_big_hex_short_subaccount_returns_error_when_serialized() { + let short_subaccount = ShortSubaccountId::unchecked("3E8"); + assert_ser_tokens(&short_subaccount, &[Token::Str("1000")]); + } + + #[test] + fn random_string_short_subaccount_returns_error_when_serialized() { + let short_subaccount = ShortSubaccountId::unchecked("ah7"); + let result = catch_unwind(|| assert_ser_tokens(&short_subaccount, &[Token::Str("1000")])); + assert!(result.is_err(), "serializing invalid short subaccount id should fail"); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_short_decimal() { + let short_subaccount = ShortSubaccountId::unchecked("001"); + assert_de_tokens(&short_subaccount, &[Token::Str("1")]); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_long_decimal() { + let short_subaccount = ShortSubaccountId::unchecked("00a"); + assert_de_tokens(&short_subaccount, &[Token::Str("010")]); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_long_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("001"); + assert_de_tokens( + &short_subaccount, + &[Token::Str("0x17dcdb32a51ee1c43c3377349dba7f56bdf48e35000000000000000000000001")], + ); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_hex_long_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("00a"); + assert_de_tokens( + &short_subaccount, + &[Token::Str("0x17dcdb32a51ee1c43c3377349dba7f56bdf48e3500000000000000000000000a")], + ); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_hex_highest_long_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("3e7"); + assert_de_tokens( + &short_subaccount, + &[Token::Str("0x17dcdb32a51ee1c43c3377349dba7f56bdf48e350000000000000000000003E7")], + ); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_highest_hex_short_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("3e7"); + assert_de_tokens(&short_subaccount, &[Token::Str("3e7")]); + } + + #[test] + fn short_subaccount_id_can_be_deserialized_from_valid_highest_decimal_short_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("3e7"); + assert_de_tokens(&short_subaccount, &[Token::Str("999")]); + } + + #[test] + #[should_panic] + fn short_subaccount_id_cannot_be_deserialized_from_too_high_hex_long_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("1000"); + assert_de_tokens( + &short_subaccount, + &[Token::Str("0x17dcdb32a51ee1c43c3377349dba7f56bdf48e3500000000000000000000003E8")], + ); + } + + #[test] + #[should_panic] + fn short_subaccount_id_cannot_be_deserialized_from_too_high_hex_short_subaccount_id() { + let short_subaccount = ShortSubaccountId::unchecked("3E8"); + assert_de_tokens(&short_subaccount, &[Token::Str("3E8")]); + } +} diff --git a/packages/injective-std/src/traits/market.rs b/packages/injective-std/src/traits/market.rs deleted file mode 100644 index 7baf1d46..00000000 --- a/packages/injective-std/src/traits/market.rs +++ /dev/null @@ -1,25 +0,0 @@ -// use injective_math::FPDecimal; -// use schemars::JsonSchema; -// use serde_repr::{Deserialize_repr, Serialize_repr}; - -// #[derive(Serialize_repr, Deserialize_repr, Default, Clone, Debug, PartialEq, Eq, JsonSchema, Copy)] -// #[repr(i32)] -// pub enum MarketStatus { -// #[default] -// Unspecified = 0, -// Active = 1, -// Paused = 2, -// Demolished = 3, -// Expired = 4, -// } - -// pub trait GenericMarket { -// fn get_ticker(&self) -> &str; -// fn get_quote_denom(&self) -> &str; -// fn get_maker_fee_rate(&self) -> FPDecimal; -// fn get_taker_fee_rate(&self) -> FPDecimal; -// fn get_market_id(&self) -> &str; -// fn get_status(&self) -> MarketStatus; -// fn get_min_price_tick_size(&self) -> FPDecimal; -// fn min_quantity_tick_size(&self) -> FPDecimal; -// } diff --git a/packages/injective-std/src/traits/mod.rs b/packages/injective-std/src/traits/mod.rs index 60e5a3fe..32829ae0 100644 --- a/packages/injective-std/src/traits/mod.rs +++ b/packages/injective-std/src/traits/mod.rs @@ -1 +1,2 @@ -pub mod market; +pub mod exchange; +pub mod general;