diff --git a/Cargo.lock b/Cargo.lock index 2c6a6ee..cd34bab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adrena-perp-itf" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bytemuck", + "num", + "num-traits", + "solana-program", +] + [[package]] name = "aead" version = "0.4.3" @@ -71,12 +83,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloc-traits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483" - [[package]] name = "anchor-attribute-access-control" version = "0.28.0" @@ -210,8 +216,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78f860599da1c2354e7234c768783049eb42e2f54509ecfc942d2e0076a2da7b" dependencies = [ "anchor-lang", - "mpl-token-metadata 1.13.2", - "serum_dex", "solana-program", "spl-associated-token-account 1.1.3", "spl-token 3.5.0", @@ -899,7 +903,7 @@ dependencies = [ [[package]] name = "data-streams-report" version = "0.0.1" -source = "git+https://github.com/smartcontractkit/data-streams-sdk.git#e0788716cbefd927ff11b23216681593db86470d" +source = "git+https://github.com/smartcontractkit/data-streams-sdk.git?rev=e0788716cbefd927ff11b23216681593db86470d#e0788716cbefd927ff11b23216681593db86470d" dependencies = [ "hex", "num-bigint", @@ -915,7 +919,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cc2736e4f7d82be485e49c721a7031f30e1f57b433bb346d7a6af4c377fd0f" dependencies = [ - "uint 0.9.5", + "uint", ] [[package]] @@ -1015,26 +1019,6 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "enumflags2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" -dependencies = [ - "enumflags2_derive", -] - -[[package]] -name = "enumflags2_derive" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -1069,16 +1053,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1274,15 +1248,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5170c2c8ecda29c1bccb9d95ccbe107bc75fa084dc0c9c6087e719f9d46330e5" -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1358,22 +1323,6 @@ dependencies = [ "decimal-wad", ] -[[package]] -name = "lb_clmm" -version = "0.5.0" -source = "git+ssh://git@github.com/hubbleprotocol/lb-clmm.git#0eabb9945419a1f0f51837823a3505864c7636d3" -dependencies = [ - "anchor-lang", - "anchor-spl", - "bytemuck", - "mpl-token-metadata 3.2.3", - "num-integer", - "num-traits", - "ruint", - "solana-program", - "uint 0.8.5", -] - [[package]] name = "libc" version = "0.2.149" @@ -1519,19 +1468,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "mpl-token-metadata" -version = "3.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba8ee05284d79b367ae8966d558e1a305a781fc80c9df51f37775169117ba64f" -dependencies = [ - "borsh 0.10.3", - "num-derive 0.3.3", - "num-traits", - "solana-program", - "thiserror", -] - [[package]] name = "mpl-token-metadata-context-derive" version = "0.2.1" @@ -2126,16 +2062,16 @@ dependencies = [ [[package]] name = "raydium-amm-v3" version = "0.1.0" -source = "git+https://github.com/raydium-io/raydium-clmm#cea4d49ef5acc643cb04fb4db96af6d73a5f0a1d" +source = "git+https://github.com/raydium-io/raydium-clmm?rev=cea4d49ef5acc643cb04fb4db96af6d73a5f0a1d#cea4d49ef5acc643cb04fb4db96af6d73a5f0a1d" dependencies = [ "anchor-lang", "anchor-spl", "arrayref", "bytemuck", - "mpl-token-metadata 1.13.2", + "mpl-token-metadata", "solana-program", "spl-memo 4.0.0", - "uint 0.9.5", + "uint", ] [[package]] @@ -2262,22 +2198,6 @@ dependencies = [ "serde", ] -[[package]] -name = "ruint" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e1574d439643c8962edf612a888e7cc5581bcdf36cb64e6bc88466b03b2daa" -dependencies = [ - "ruint-macro", - "thiserror", -] - -[[package]] -name = "ruint-macro" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" - [[package]] name = "rust_decimal" version = "1.36.0" @@ -2310,12 +2230,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - [[package]] name = "rustc_version" version = "0.4.0" @@ -2337,12 +2251,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "safe-transmute" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a01dab6acf992653be49205bdd549f32f17cb2803e8eacf1560bf97259aae8" - [[package]] name = "sbod-itf" version = "0.1.0" @@ -2382,6 +2290,7 @@ dependencies = [ name = "scope" version = "0.1.0" dependencies = [ + "adrena-perp-itf", "anchor-lang", "anchor-spl", "arrayref", @@ -2406,28 +2315,16 @@ dependencies = [ "sha2 0.10.8", "solana-program", "static_assertions", - "strum 0.24.0 (git+https://github.com/Kamino-Finance/strum?branch=checked_arithmetics)", + "strum", "switchboard-program", "tracing", - "whirlpool 0.2.0 (git+https://github.com/Kamino-Finance/whirlpools?branch=anchor/0.28.0)", - "yvaults", -] - -[[package]] -name = "scope-types" -version = "1.0.0" -dependencies = [ - "anchor-lang", - "bytemuck", - "cfg-if", - "num_enum 0.7.1", - "solana-program", + "whirlpool", + "yvaults_stub", ] [[package]] name = "scope-types" version = "1.0.0" -source = "git+https://github.com/Kamino-Finance/scope#d5801f596ce18a167afde2f8e68f814ec78d5165" dependencies = [ "anchor-lang", "bytemuck", @@ -2527,30 +2424,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "serum_dex" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02705854bae4622e552346c8edd43ab90c7425da35d63d2c689f39238f8d8b25" -dependencies = [ - "arrayref", - "bincode", - "bytemuck", - "byteorder", - "enumflags2", - "field-offset", - "itertools 0.9.0", - "num-traits", - "num_enum 0.5.11", - "safe-transmute", - "serde", - "solana-program", - "spl-token 3.5.0", - "static_assertions", - "thiserror", - "without-alloc", -] - [[package]] name = "sha2" version = "0.9.9" @@ -2851,12 +2724,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "solana-security-txt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" - [[package]] name = "solana-zk-token-sdk" version = "1.16.27" @@ -3136,18 +3003,6 @@ dependencies = [ "spl-program-error", ] -[[package]] -name = "static-pubkey" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0547d5945c93f55e1b18bf2a67d1a3d0548561f2687645b22c1c1d4fbb9a8e90" -dependencies = [ - "bs58 0.4.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -3160,32 +3015,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.0" -source = "git+https://github.com/hubbleprotocol/strum?branch=checked_arithmetics#95f44367da40546bc94f4ff565268f45fb68ed5e" -dependencies = [ - "strum_macros 0.24.0 (git+https://github.com/hubbleprotocol/strum?branch=checked_arithmetics)", -] - [[package]] name = "strum" version = "0.24.0" source = "git+https://github.com/Kamino-Finance/strum?branch=checked_arithmetics#95f44367da40546bc94f4ff565268f45fb68ed5e" dependencies = [ - "strum_macros 0.24.0 (git+https://github.com/Kamino-Finance/strum?branch=checked_arithmetics)", -] - -[[package]] -name = "strum_macros" -version = "0.24.0" -source = "git+https://github.com/hubbleprotocol/strum?branch=checked_arithmetics#95f44367da40546bc94f4ff565268f45fb68ed5e" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", + "strum_macros", ] [[package]] @@ -3448,18 +3283,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "uint" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177" -dependencies = [ - "byteorder", - "crunchy", - "rustc-hex", - "static_assertions", -] - [[package]] name = "uint" version = "0.9.5" @@ -3503,15 +3326,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa7a7a734c1a5664a662ddcea0b6c9472a21da8888c957c7f1eaa09dba7a939" -dependencies = [ - "autocfg", -] - [[package]] name = "uriparse" version = "0.6.4" @@ -3610,22 +3424,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "whirlpool" -version = "0.2.0" -source = "git+https://github.com/hubbleprotocol/whirlpools?branch=anchor/0.28.0#c77e1e4c87dde54bfdaaaae3b806c9b660cefdc3" -dependencies = [ - "anchor-lang", - "anchor-spl", - "borsh 0.9.3", - "bytemuck", - "mpl-token-metadata 1.13.2", - "solana-program", - "spl-token 3.5.0", - "thiserror", - "uint 0.9.5", -] - [[package]] name = "whirlpool" version = "0.2.0" @@ -3635,11 +3433,11 @@ dependencies = [ "anchor-spl", "borsh 0.9.3", "bytemuck", - "mpl-token-metadata 1.13.2", + "mpl-token-metadata", "solana-program", "spl-token 3.5.0", "thiserror", - "uint 0.9.5", + "uint", ] [[package]] @@ -3739,16 +3537,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "without-alloc" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375db0478b203b950ef10d1cce23cdbe5f30c2454fd9e7673ff56656df23adbb" -dependencies = [ - "alloc-traits", - "unsize", -] - [[package]] name = "wyz" version = "0.5.1" @@ -3758,33 +3546,6 @@ dependencies = [ "tap", ] -[[package]] -name = "yvaults" -version = "0.1.0" -source = "git+ssh://git@github.com/Kamino-Finance/yvaults.git?branch=scope-public-compat#096fe93b90007118980085ba3b6707f252ef1136" -dependencies = [ - "anchor-lang", - "anchor-spl", - "bytemuck", - "decimal-wad", - "lb_clmm", - "mpl-token-metadata 1.13.2", - "num", - "num-derive 0.4.1", - "num-traits", - "num_enum 0.7.1", - "raydium-amm-v3", - "rust_decimal", - "scope-types 1.0.0 (git+https://github.com/Kamino-Finance/scope)", - "sha2 0.10.8", - "solana-security-txt", - "spl-token 3.5.0", - "static-pubkey", - "strum 0.24.0 (git+https://github.com/hubbleprotocol/strum?branch=checked_arithmetics)", - "uint 0.9.5", - "whirlpool 0.2.0 (git+https://github.com/hubbleprotocol/whirlpools?branch=anchor/0.28.0)", -] - [[package]] name = "yvaults_stub" version = "0.1.0" diff --git a/programs/adrena-perp-itf/Cargo.toml b/programs/adrena-perp-itf/Cargo.toml new file mode 100644 index 0000000..38833e6 --- /dev/null +++ b/programs/adrena-perp-itf/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "adrena-perp-itf" +version = "0.1.0" +description = "Interface to interact with Adrena Perpetual Protocol (partial)" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "adrena_perp_itf" + +[features] +no-entrypoint = [] +cpi = ["no-entrypoint"] +test-bpf = [] +debug = [] + +[dependencies] +anchor-lang = "0.28.0" +anchor-spl = "0.28.0" +solana-program = "~1.16.18" +bytemuck = { version = "1.12", default-features = false, features = ["derive"] } +num-traits = "0.2.15" +num = "0.4.0" diff --git a/programs/adrena-perp-itf/src/lib.rs b/programs/adrena-perp-itf/src/lib.rs new file mode 100644 index 0000000..73d9b7c --- /dev/null +++ b/programs/adrena-perp-itf/src/lib.rs @@ -0,0 +1,10 @@ +pub mod state; + +use anchor_lang::prelude::*; + +declare_id!("13gDzEXCdocbj8iAiqrScGo47NiSuYENGsRqi3SEAwet"); + +pub const PRICE_DECIMALS: u8 = 10; + +#[program] +pub mod adrena {} diff --git a/programs/adrena-perp-itf/src/state/limited_string.rs b/programs/adrena-perp-itf/src/state/limited_string.rs new file mode 100644 index 0000000..e990675 --- /dev/null +++ b/programs/adrena-perp-itf/src/state/limited_string.rs @@ -0,0 +1,111 @@ +use { + anchor_lang::prelude::*, + bytemuck::{Pod, Zeroable}, + std::fmt::Display, +}; + +// Storage space must be known in advance, as such all strings are limited to 64 characters. +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Zeroable, Pod)] +#[repr(C)] +pub struct LimitedString { + pub value: [u8; 31], + pub length: u8, +} + +impl std::fmt::Debug for LimitedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +impl PartialEq for LimitedString { + fn eq(&self, other: &Self) -> bool { + self.length == other.length + && self.value[..self.length as usize] == other.value[..other.length as usize] + } +} + +impl From for String { + fn from(limited_string: LimitedString) -> Self { + let mut string = String::new(); + for byte in limited_string.value.iter() { + if *byte == 0 { + break; + } + string.push(*byte as char); + } + string + } +} + +impl LimitedString { + pub fn to_bytes(&self) -> &[u8] { + &self.value[..self.length as usize] + } +} + +impl Display for LimitedString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", String::from(*self)) + } +} + +impl Default for LimitedString { + fn default() -> Self { + Self { + value: [0; Self::MAX_LENGTH], + length: 0, + } + } +} + +impl LimitedString { + pub const MAX_LENGTH: usize = 31; + + pub fn new>(input: S) -> Self { + let input_str = input.as_ref(); + let length = input_str.len() as u8; + let mut array = [0; Self::MAX_LENGTH]; + let bytes = input_str.as_bytes(); + for (index, byte) in bytes.iter().enumerate() { + if index >= Self::MAX_LENGTH { + break; + } + array[index] = *byte; + } + LimitedString { + value: array, + length, + } + } + + pub const fn new_const(input: &'static str) -> Self { + let length = input.len() as u8; + let bytes = input.as_bytes(); + let mut array = [0; Self::MAX_LENGTH]; + let mut i = 0; + + while i < Self::MAX_LENGTH && i < length as usize { + array[i] = bytes[i]; + i += 1; + } + + LimitedString { + value: array, + length, + } + } + + // Converts the LimitedString into a [u8; 32], zero-padded. Useful in tests. + pub fn to_fixed_32(&self) -> [u8; 32] { + let mut buf = [0u8; 32]; + + let len = self.length as usize; + + let copy_len = len.min(31); + + buf[..copy_len].copy_from_slice(&self.value[..copy_len]); + buf[31] = self.length; // mirror the struct layout: 31 bytes + 1 byte len + buf + } +} diff --git a/programs/adrena-perp-itf/src/state/mod.rs b/programs/adrena-perp-itf/src/state/mod.rs new file mode 100644 index 0000000..6ccfef2 --- /dev/null +++ b/programs/adrena-perp-itf/src/state/mod.rs @@ -0,0 +1,7 @@ +pub mod limited_string; +pub mod pool; +pub mod u128_split; + +pub use limited_string::*; +pub use pool::*; +pub use u128_split::*; diff --git a/programs/adrena-perp-itf/src/state/pool.rs b/programs/adrena-perp-itf/src/state/pool.rs new file mode 100644 index 0000000..3366275 --- /dev/null +++ b/programs/adrena-perp-itf/src/state/pool.rs @@ -0,0 +1,44 @@ +use anchor_lang::prelude::*; +use bytemuck::{Pod, Zeroable}; + +use super::{LimitedString, U128Split}; + +pub const MAX_CUSTODIES: usize = 8; + +#[account(zero_copy)] +#[derive(Default, Debug)] +#[repr(C)] +pub struct Pool { + pub bump: u8, + pub lp_token_bump: u8, + pub nb_stable_custody: u8, + pub initialized: u8, + pub allow_trade: u8, + pub allow_swap: u8, + pub liquidity_state: u8, + pub registered_custody_count: u8, + pub name: LimitedString, + pub custodies: [Pubkey; MAX_CUSTODIES], + pub fees_debt_usd: u64, + pub referrers_fee_debt_usd: u64, + pub cumulative_referrer_fee_usd: u64, + pub lp_token_price_usd: u64, // <--- The price + pub whitelisted_swapper: Pubkey, + pub ratios: [TokenRatios; MAX_CUSTODIES], + pub last_aum_and_lp_token_price_usd_update: i64, // <--- The price update time + pub unique_limit_order_id_counter: u64, + pub aum_usd: U128Split, + pub inception_time: i64, + pub aum_soft_cap_usd: u64, +} + +#[derive( + Copy, Clone, PartialEq, AnchorSerialize, AnchorDeserialize, Default, Debug, Zeroable, Pod, +)] +#[repr(C)] +pub struct TokenRatios { + pub target: u16, + pub min: u16, + pub max: u16, + pub _padding: [u8; 2], +} diff --git a/programs/adrena-perp-itf/src/state/u128_split.rs b/programs/adrena-perp-itf/src/state/u128_split.rs new file mode 100644 index 0000000..7a41868 --- /dev/null +++ b/programs/adrena-perp-itf/src/state/u128_split.rs @@ -0,0 +1,145 @@ +use { + anchor_lang::prelude::*, + bytemuck::{Pod, Zeroable}, + std::ops::{AddAssign, SubAssign}, +}; + +// U128Split is a struct that represents a u128 as two u64s for zero_copy needs +#[derive( + Copy, Clone, PartialEq, AnchorSerialize, AnchorDeserialize, Default, Debug, Pod, Zeroable, +)] +#[repr(C)] +pub struct U128Split { + high: u64, + low: u64, +} + +impl U128Split { + // Create a new U128Split from a u128 + pub fn new(value: u128) -> Self { + let high = (value >> 64) as u64; + let low = value as u64; + U128Split { high, low } + } + + // Convert the U128Split back into a u128 + pub fn to_u128(&self) -> u128 { + ((self.high as u128) << 64) | (self.low as u128) + } + + // Add a u128 to this U128Split + pub fn add(&mut self, other: u128) { + let other_split = U128Split::new(other); + let (low, carry) = self.low.overflowing_add(other_split.low); + let high = self.high + other_split.high + (carry as u64); + self.high = high; + self.low = low; + } + + // Method to perform left shift operation + pub fn left_shift(&mut self, shift: u32) { + if shift == 0 { + return; + } + + let total_bits = 128; + let u64_bits = 64; + + if shift >= total_bits { + self.high = 0; + self.low = 0; + } else if shift >= u64_bits { + self.high = self.low << (shift - u64_bits); + self.low = 0; + } else { + self.high = (self.high << shift) | (self.low >> (u64_bits - shift)); + self.low <<= shift; + } + } +} + +impl std::ops::Add for U128Split { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + let (low, carry) = self.low.overflowing_add(other.low); + let high = self.high + other.high + (carry as u64); + U128Split { high, low } + } +} + +impl std::ops::Sub for U128Split { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + let (low, borrow) = self.low.overflowing_sub(other.low); + let high = self.high - other.high - (borrow as u64); + U128Split { high, low } + } +} + +impl From for U128Split { + fn from(item: u128) -> Self { + U128Split::new(item) + } +} + +impl From for U128Split { + fn from(item: u64) -> Self { + U128Split::new(item as u128) + } +} + +impl From for U128Split { + fn from(item: i32) -> Self { + U128Split::new(item as u128) + } +} + +impl AddAssign for U128Split { + fn add_assign(&mut self, other: Self) { + let (low, carry) = self.low.overflowing_add(other.low); + let high = self.high + other.high + (carry as u64); + self.high = high; + self.low = low; + } +} + +impl AddAssign for U128Split { + fn add_assign(&mut self, other: u64) { + let (low, carry) = self.low.overflowing_add(other); + self.high += carry as u64; // Only add to high if there's a carry + self.low = low; + } +} + +impl AddAssign for U128Split { + fn add_assign(&mut self, other: u128) { + let other_split = U128Split::new(other); + self.add_assign(other_split); + } +} + +impl SubAssign for U128Split { + fn sub_assign(&mut self, other: Self) { + let (low, borrow) = self.low.overflowing_sub(other.low); + let high = self.high - other.high - (borrow as u64); + self.high = high; + self.low = low; + } +} + +impl SubAssign for U128Split { + fn sub_assign(&mut self, other: u64) { + let (low, borrow) = self.low.overflowing_sub(other); + self.high -= borrow as u64; // Only subtract from high if there's a borrow + self.low = low; + } +} + +impl SubAssign for U128Split { + fn sub_assign(&mut self, other: u128) { + let other_split = U128Split::new(other); + self.sub_assign(other_split); + } +} diff --git a/programs/scope/Cargo.toml b/programs/scope/Cargo.toml index 3c7aa71..b321269 100644 --- a/programs/scope/Cargo.toml +++ b/programs/scope/Cargo.toml @@ -37,7 +37,9 @@ bytemuck = { version = "1.4.0", features = ["min_const_generics", "derive"] } num_enum = "0.7.0" cfg-if = "1.0.0" serde = { version = "1.0.136", optional = true } -strum = { git = "https://github.com/Kamino-Finance/strum", features = ["derive"], branch = "checked_arithmetics" } +strum = { git = "https://github.com/Kamino-Finance/strum", features = [ + "derive", +], branch = "checked_arithmetics" } pyth-sdk-solana = "0.10.1" switchboard-program = "0.2.0" arrayref = "0.3.6" @@ -56,7 +58,10 @@ whirlpool = { git = "https://github.com/Kamino-Finance/whirlpools", branch = "an "no-entrypoint", "cpi", ] } -raydium-amm-v3 = { git = "https://github.com/raydium-io/raydium-clmm", features = ["no-entrypoint", "cpi"] } +raydium-amm-v3 = { git = "https://github.com/raydium-io/raydium-clmm", features = [ + "no-entrypoint", + "cpi", +] } jup-perp-itf = { path = "../jup-perp-itf", features = ["cpi"] } lb-clmm-itf = { path = "../lb-clmm-itf", features = ["no-entrypoint"] } sbod-itf = { path = "../sbod-itf" } @@ -74,3 +79,4 @@ tracing = { version = "0.1.10", optional = true } chainlink-streams-report = { git = "https://github.com/smartcontractkit/data-streams-sdk.git", package = "data-streams-report" } num-bigint = "0.4" +adrena-perp-itf = { path = "../adrena-perp-itf", features = ["cpi"] } diff --git a/programs/scope/src/oracles/adrena_lp.rs b/programs/scope/src/oracles/adrena_lp.rs new file mode 100644 index 0000000..9c54493 --- /dev/null +++ b/programs/scope/src/oracles/adrena_lp.rs @@ -0,0 +1,54 @@ +pub use adrena_perp_itf as adrena; +use anchor_lang::prelude::*; + +use crate::{ + utils::{math::estimate_slot_update_from_ts, zero_copy_deserialize}, + warn, DatedPrice, Price, Result, ScopeError, +}; + +pub fn validate_adrena_pool(account: &Option) -> Result<()> { + let Some(account) = account else { + warn!("No adrena pool account provided"); + return err!(ScopeError::ExpectedPriceAccount); + }; + + let adrena_pool = zero_copy_deserialize::(account)?; + + if adrena_pool.initialized != 1 { + warn!("Adrena pool account isn't initialized"); + return err!(ScopeError::PriceNotValid); + } + + Ok(()) +} + +/// Get the price of 1 ALP token in USD +/// +/// This function get price from Adrena Pool account +pub fn get_price<'a, 'b>(pool_acc: &AccountInfo<'a>, clock: &Clock) -> Result +where + 'a: 'b, +{ + // 1. Get accounts + let adrena_pool = zero_copy_deserialize::(pool_acc)?; + + if adrena_pool.initialized != 1 { + warn!("Adrena pool account isn't initialized"); + return err!(ScopeError::PriceNotValid); + } + + // 2. Check the price + Ok(DatedPrice { + price: Price { + value: adrena_pool.lp_token_price_usd, + exp: adrena::PRICE_DECIMALS.into(), + }, + last_updated_slot: estimate_slot_update_from_ts( + clock, + adrena_pool.last_aum_and_lp_token_price_usd_update, + ), + unix_timestamp: u64::try_from(adrena_pool.last_aum_and_lp_token_price_usd_update) + .map_err(|_| ScopeError::BadTimestamp)?, + generic_data: [0; 24], // Placeholder for generic data + }) +} diff --git a/programs/scope/src/oracles/mod.rs b/programs/scope/src/oracles/mod.rs index d0c484b..3a5c92f 100644 --- a/programs/scope/src/oracles/mod.rs +++ b/programs/scope/src/oracles/mod.rs @@ -4,6 +4,7 @@ pub mod ktokens; #[cfg(feature = "yvaults")] pub mod ktokens_token_x; +pub mod adrena_lp; pub mod chainlink; pub mod discount_to_maturity; pub mod jito_restaking; @@ -125,6 +126,8 @@ pub enum OracleType { PythLazer = 29, /// RedStone price oracle RedStone = 30, + /// Adrena's perpetual LP token price gathered from onchain pool account + AdrenaLp = 31, } impl OracleType { @@ -166,6 +169,7 @@ impl OracleType { OracleType::DeprecatedPlaceholder1 | OracleType::DeprecatedPlaceholder2 => { panic!("DeprecatedPlaceholder is not a valid oracle type") } + OracleType::AdrenaLp => 20_000, } } } @@ -316,6 +320,7 @@ where OracleType::DeprecatedPlaceholder1 | OracleType::DeprecatedPlaceholder2 => { panic!("DeprecatedPlaceholder is not a valid oracle type") } + OracleType::AdrenaLp => adrena_lp::get_price(base_account, clock), }?; // The price providers above are performing their type-specific validations, but are still free // to return 0, which we can only tolerate in case of explicit fixed price: @@ -398,5 +403,6 @@ pub fn validate_oracle_cfg( OracleType::DeprecatedPlaceholder1 | OracleType::DeprecatedPlaceholder2 => { panic!("DeprecatedPlaceholder is not a valid oracle type") } + OracleType::AdrenaLp => adrena_lp::validate_adrena_pool(price_account), } }