diff --git a/Cargo.lock b/Cargo.lock index eeffdabd0f..b7d0c4be4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,17 @@ dependencies = [ "subtle 2.6.1", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -497,7 +508,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff 0.5.0", "ark-poly 0.5.0", "ark-serialize 0.5.0", @@ -732,7 +743,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ahash", + "ahash 0.8.12", "ark-ff 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", @@ -1669,6 +1680,29 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases 0.2.1", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "bounded-collections" version = "0.1.9" @@ -1944,6 +1978,28 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.24.0" @@ -2464,6 +2520,28 @@ dependencies = [ "libc", ] +[[package]] +name = "crabtime" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81605e11aed454fb3838bc408f091d17f2f6d31613fb897f561a45bc8fb98378" +dependencies = [ + "crabtime-internal", +] + +[[package]] +name = "crabtime-internal" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49bfb43e6dfb9026c045fc1fd5cde79f4927ea6d1d89a2d346c55879e85900c" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.106", + "toml 0.8.23", +] + [[package]] name = "cranelift-bforest" version = "0.95.1" @@ -5691,6 +5769,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -5698,7 +5779,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.12", ] [[package]] @@ -5707,7 +5788,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.12", "allocator-api2", "serde", ] @@ -10837,6 +10918,9 @@ dependencies = [ "log", "pallet-subtensor-swap-runtime-api", "parity-scale-codec", + "rand 0.8.5", + "rayon", + "safe-bigmath", "safe-math", "scale-info", "serde", @@ -13472,6 +13556,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quanta" version = "0.12.6" @@ -13580,6 +13684,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoth" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3fcb67d48b8ef70eda1c84514f4ba26e4904c40c36940cec7aa2c45cd8d114b" +dependencies = [ + "quoth-macros", + "regex", + "rust_decimal", + "safe-string", +] + +[[package]] +name = "quoth-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e5bab2cb57954711cf4165382479a56c3b9d2c94f513ab2a634aea00baed4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "r-efi" version = "5.3.0" @@ -13858,6 +13985,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "resolv-conf" version = "0.7.5" @@ -13912,6 +14048,35 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rlp" version = "0.5.2" @@ -14147,6 +14312,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust_decimal" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +dependencies = [ + "arrayvec 0.7.6", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -14402,6 +14583,18 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe-bigmath" +version = "0.2.0" +source = "git+https://github.com/sam0x17/safe-math?rev=fd8e816#fd8e8167ff95761d9b6407e2feec47032c2311ca" +dependencies = [ + "crabtime", + "num-bigint", + "num-integer", + "num-traits", + "quoth", +] + [[package]] name = "safe-math" version = "0.1.0" @@ -14421,6 +14614,12 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "safe-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "828a62cafa42d2d1b5ad4592c47946c156972e1e8d6e929e566256d841177b59" + [[package]] name = "safe_arch" version = "0.7.4" @@ -14842,7 +15041,7 @@ name = "sc-consensus-grandpa" version = "0.36.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=81fa2c54e94f824eba7dabe9dffd063481cb2d80#81fa2c54e94f824eba7dabe9dffd063481cb2d80" dependencies = [ - "ahash", + "ahash 0.8.12", "array-bytes 6.2.3", "async-trait", "dyn-clone", @@ -15145,7 +15344,7 @@ name = "sc-network-gossip" version = "0.51.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=81fa2c54e94f824eba7dabe9dffd063481cb2d80#81fa2c54e94f824eba7dabe9dffd063481cb2d80" dependencies = [ - "ahash", + "ahash 0.8.12", "futures", "futures-timer", "log", @@ -15858,7 +16057,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ - "ahash", + "ahash 0.8.12", "cfg-if", "hashbrown 0.13.2", ] @@ -15922,6 +16121,12 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.3" @@ -16353,6 +16558,12 @@ dependencies = [ "wide", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "simple-dns" version = "0.9.3" @@ -17433,7 +17644,7 @@ name = "sp-trie" version = "40.0.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=81fa2c54e94f824eba7dabe9dffd063481cb2d80#81fa2c54e94f824eba7dabe9dffd063481cb2d80" dependencies = [ - "ahash", + "ahash 0.8.12", "foldhash 0.1.5", "hash-db", "hashbrown 0.15.5", @@ -18081,7 +18292,7 @@ dependencies = [ name = "subtensor-macros" version = "0.1.0" dependencies = [ - "ahash", + "ahash 0.8.12", "proc-macro2", "quote", "syn 2.0.106", @@ -19674,7 +19885,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" dependencies = [ - "ahash", + "ahash 0.8.12", "hashbrown 0.14.5", "string-interner", ] diff --git a/Cargo.toml b/Cargo.toml index 1d65a3cd5e..4b93e54e23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ pallet-subtensor-swap-runtime-api = { path = "pallets/swap/runtime-api", default pallet-subtensor-swap-rpc = { path = "pallets/swap/rpc", default-features = false } procedural-fork = { path = "support/procedural-fork", default-features = false } safe-math = { path = "primitives/safe-math", default-features = false } +safe-bigmath = { package = "safe-bigmath", git = "https://github.com/sam0x17/safe-math", rev = "fd8e816", default-features = false } share-pool = { path = "primitives/share-pool", default-features = false } subtensor-macros = { path = "support/macros", default-features = false } subtensor-custom-rpc = { default-features = false, path = "pallets/subtensor/rpc" } diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index eaaee70d27..2e2eb807fa 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -18,7 +18,7 @@ use pallet_subtensor_proxy as pallet_proxy; use pallet_subtensor_proxy::WeightInfo; use sp_runtime::{DispatchError, Weight, traits::StaticLookup}; use sp_std::marker::PhantomData; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUid, ProxyType, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -520,7 +520,7 @@ where netuid.into(), ); - let price = current_alpha_price.saturating_mul(U96F32::from_num(1_000_000_000)); + let price = current_alpha_price.saturating_mul(U64F64::from_num(1_000_000_000)); let price: u64 = price.saturating_to_num(); let encoded_result = price.encode(); diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index bd6f46c8ab..f3abfa2c91 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -10,7 +10,7 @@ use pallet_subtensor::DefaultMinStake; use sp_core::Get; use sp_core::U256; use sp_runtime::DispatchError; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyTrait, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -985,7 +985,7 @@ fn get_alpha_price_returns_encoded_price() { as SwapHandler>::current_alpha_price( netuid.into(), ); - let expected_price_scaled = expected_price.saturating_mul(U96F32::from_num(1_000_000_000)); + let expected_price_scaled = expected_price.saturating_mul(U64F64::from_num(1_000_000_000)); let expected_price_u64: u64 = expected_price_scaled.saturating_to_num(); let mut env = MockEnv::new(FunctionId::GetAlphaPriceV1, caller, netuid.encode()); diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index 064bab4d2a..c499fe3117 100644 --- a/pallets/subtensor/src/coinbase/block_emission.rs +++ b/pallets/subtensor/src/coinbase/block_emission.rs @@ -3,7 +3,7 @@ use frame_support::traits::Get; use safe_math::*; use substrate_fixed::{ transcendental::log2, - types::{I96F32, U96F32}, + types::{I96F32, U64F64}, }; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -36,15 +36,15 @@ impl Pallet { alpha_block_emission: u64, ) -> (u64, u64, u64) { // Init terms. - let mut tao_in_emission: U96F32 = U96F32::saturating_from_num(tao_emission); - let float_alpha_block_emission: U96F32 = U96F32::saturating_from_num(alpha_block_emission); + let mut tao_in_emission: U64F64 = U64F64::saturating_from_num(tao_emission); + let float_alpha_block_emission: U64F64 = U64F64::saturating_from_num(alpha_block_emission); // Get alpha price for subnet. let alpha_price = T::SwapInterface::current_alpha_price(netuid.into()); log::debug!("{netuid:?} - alpha_price: {alpha_price:?}"); // Get initial alpha_in - let mut alpha_in_emission: U96F32 = U96F32::saturating_from_num(tao_emission) + let mut alpha_in_emission: U64F64 = U64F64::saturating_from_num(tao_emission) .checked_div(alpha_price) .unwrap_or(float_alpha_block_emission); @@ -62,11 +62,11 @@ impl Pallet { } // Avoid rounding errors. - if tao_in_emission < U96F32::saturating_from_num(1) - || alpha_in_emission < U96F32::saturating_from_num(1) - { - alpha_in_emission = U96F32::saturating_from_num(0); - tao_in_emission = U96F32::saturating_from_num(0); + let zero = U64F64::saturating_from_num(0); + let one = U64F64::saturating_from_num(1); + if tao_in_emission < one || alpha_in_emission < one { + alpha_in_emission = zero; + tao_in_emission = zero; } // Set Alpha in emission. diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 2091946598..2e7153e407 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -140,7 +140,8 @@ impl Pallet { log::debug!("alpha_emission_i: {alpha_emission_i:?}"); // Get subnet price. - let price_i: U96F32 = T::SwapInterface::current_alpha_price(netuid_i.into()); + let price_i: U96F32 = + U96F32::saturating_from_num(T::SwapInterface::current_alpha_price(netuid_i.into())); log::debug!("price_i: {price_i:?}"); let mut tao_in_i: U96F32 = tao_emission_i; diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 1176064e36..b16e3a79d6 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -6,7 +6,7 @@ use frame_support::traits::{ }, }; use safe_math::*; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; @@ -48,15 +48,13 @@ impl Pallet { Self::get_all_subnet_netuids() .into_iter() .map(|netuid| { - let alpha = U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( + let alpha = U64F64::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( hotkey, netuid, )); - let alpha_price = U96F32::saturating_from_num( - T::SwapInterface::current_alpha_price(netuid.into()), - ); + let alpha_price = T::SwapInterface::current_alpha_price(netuid.into()); alpha.saturating_mul(alpha_price) }) - .sum::() + .sum::() .saturating_to_num::() .into() } @@ -76,7 +74,7 @@ impl Pallet { let order = GetTaoForAlpha::::with_amount(alpha_stake); T::SwapInterface::sim_swap(netuid.into(), order) .map(|r| { - let fee: u64 = U96F32::saturating_from_num(r.fee_paid) + let fee: u64 = U64F64::saturating_from_num(r.fee_paid) .saturating_mul(T::SwapInterface::current_alpha_price( netuid.into(), )) @@ -186,7 +184,7 @@ impl Pallet { let alpha_stake = Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid); let min_alpha_stake = - U96F32::saturating_from_num(Self::get_nominator_min_required_stake()) + U64F64::saturating_from_num(Self::get_nominator_min_required_stake()) .safe_div(T::SwapInterface::current_alpha_price(netuid)) .saturating_to_num::(); if alpha_stake > 0.into() && alpha_stake < min_alpha_stake.into() { diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 423cb97493..01427545b4 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -463,7 +463,9 @@ impl Pallet { .saturating_to_num::(); owner_emission_tao = if owner_alpha_u64 > 0 { - let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let cur_price: U96F32 = U96F32::saturating_from_num( + T::SwapInterface::current_alpha_price(netuid.into()), + ); let val_u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index f61a8a6ce2..13fd286edb 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -63,10 +63,10 @@ impl Pallet { // Because alpha = b / (b + h), where b and h > 0, alpha < 1, so 1 - alpha > 0. // We can use unsigned type here: U96F32 let one_minus_alpha: U96F32 = U96F32::saturating_from_num(1.0).saturating_sub(alpha); - let current_price: U96F32 = alpha.saturating_mul( + let current_price: U96F32 = alpha.saturating_mul(U96F32::saturating_from_num( T::SwapInterface::current_alpha_price(netuid.into()) - .min(U96F32::saturating_from_num(1.0)), - ); + .min(U64F64::saturating_from_num(1.0)), + )); let current_moving: U96F32 = one_minus_alpha.saturating_mul(Self::get_moving_alpha_price(netuid)); // Convert batch to signed I96F32 to avoid migration of SubnetMovingPrice for now`` @@ -876,7 +876,7 @@ impl Pallet { let current_price = ::SwapInterface::current_alpha_price(netuid.into()); let tao_equivalent: TaoCurrency = current_price - .saturating_mul(U96F32::saturating_from_num(actual_alpha_moved)) + .saturating_mul(U64F64::saturating_from_num(actual_alpha_moved)) .saturating_to_num::() .into(); diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index fd5c1deaf0..55ea2593e0 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -5,10 +5,26 @@ use crate::tests::mock::{ RuntimeOrigin, SubtensorModule, Test, add_dynamic_network, new_test_ext, run_to_block, }; use crate::{ - DefaultMinRootClaimAmount, Error, MAX_NUM_ROOT_CLAIMS, MAX_ROOT_CLAIM_THRESHOLD, NetworksAdded, - NumRootClaim, NumStakingColdkeys, PendingRootAlphaDivs, RootClaimable, RootClaimableThreshold, - StakingColdkeys, StakingColdkeysByIndex, SubnetAlphaIn, SubnetMechanism, SubnetMovingPrice, - SubnetTAO, SubnetTaoFlow, SubtokenEnabled, Tempo, pallet, + DefaultMinRootClaimAmount, + Error, + MAX_NUM_ROOT_CLAIMS, + MAX_ROOT_CLAIM_THRESHOLD, + NetworksAdded, + NumRootClaim, + NumStakingColdkeys, + //PendingRootAlphaDivs, + RootClaimable, + RootClaimableThreshold, + StakingColdkeys, + StakingColdkeysByIndex, + SubnetAlphaIn, + SubnetMechanism, + // SubnetMovingPrice, + SubnetTAO, + // SubnetTaoFlow, + SubtokenEnabled, + //Tempo, + pallet, }; use crate::{RootClaimType, RootClaimTypeEnum, RootClaimed}; use approx::assert_abs_diff_eq; @@ -19,7 +35,10 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{H256, U256}; use sp_runtime::DispatchError; use std::collections::BTreeSet; -use substrate_fixed::types::{I96F32, U64F64, U96F32}; +use substrate_fixed::types::{ + I96F32, + //U64F64, U96F32 +}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -760,79 +779,81 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { #[test] fn test_claim_root_with_run_coinbase() { - new_test_ext(1).execute_with(|| { - let owner_coldkey = U256::from(1001); - let hotkey = U256::from(1002); - let coldkey = U256::from(1003); - let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - - Tempo::::insert(netuid, 1); - SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 - - let root_stake = 200_000_000u64; - SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); - - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - NetUid::ROOT, - root_stake.into(), - ); - - let initial_total_hotkey_alpha = 10_000_000u64; - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &owner_coldkey, - netuid, - initial_total_hotkey_alpha.into(), - ); - - // Set moving price > 1.0 and price > 1.0 - // So we turn ON root sell - SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(10.0), - ); - - // Make sure we are root selling, so we have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(root_sell_flag, "Root sell flag should be true"); - - // Distribute pending root alpha - - let initial_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - assert_eq!(initial_stake, 0u64); - - let block_emissions = 1_000_000u64; - SubtensorModule::run_coinbase(U96F32::from(block_emissions)); - - // Claim root alpha - - let initial_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - assert_eq!(initial_stake, 0u64); - - assert_ok!(SubtensorModule::set_root_claim_type( - RuntimeOrigin::signed(coldkey), - RootClaimTypeEnum::Keep - ),); - assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - - assert_ok!(SubtensorModule::claim_root( - RuntimeOrigin::signed(coldkey), - BTreeSet::from([netuid]) - )); - - let new_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - - assert!(new_stake > 0); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let owner_coldkey = U256::from(1001); + // let hotkey = U256::from(1002); + // let coldkey = U256::from(1003); + // let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + // Tempo::::insert(netuid, 1); + // SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + // let root_stake = 200_000_000u64; + // SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); + + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &coldkey, + // NetUid::ROOT, + // root_stake.into(), + // ); + + // let initial_total_hotkey_alpha = 10_000_000u64; + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &owner_coldkey, + // netuid, + // initial_total_hotkey_alpha.into(), + // ); + + // // Set moving price > 1.0 and price > 1.0 + // // So we turn ON root sell + // SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(10.0), + // ); + + // // Make sure we are root selling, so we have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(root_sell_flag, "Root sell flag should be true"); + + // // Distribute pending root alpha + + // let initial_stake: u64 = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + // .into(); + // assert_eq!(initial_stake, 0u64); + + // let block_emissions = 1_000_000u64; + // SubtensorModule::run_coinbase(U96F32::from(block_emissions)); + + // // Claim root alpha + + // let initial_stake: u64 = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + // .into(); + // assert_eq!(initial_stake, 0u64); + + // assert_ok!(SubtensorModule::set_root_claim_type( + // RuntimeOrigin::signed(coldkey), + // RootClaimTypeEnum::Keep + // ),); + // assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + // assert_ok!(SubtensorModule::claim_root( + // RuntimeOrigin::signed(coldkey), + // BTreeSet::from([netuid]) + // )); + + // let new_stake: u64 = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + // .into(); + + // assert!(new_stake > 0); + // }); } #[test] @@ -878,69 +899,71 @@ fn test_claim_root_block_hash_indices() { #[test] fn test_claim_root_with_block_emissions() { - new_test_ext(0).execute_with(|| { - let owner_coldkey = U256::from(1001); - let hotkey = U256::from(1002); - let coldkey = U256::from(1003); - let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - - Tempo::::insert(netuid, 1); - SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 - - let root_stake = 200_000_000u64; - SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); - - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - NetUid::ROOT, - root_stake.into(), - ); - SubtensorModule::maybe_add_coldkey_index(&coldkey); - - // Set moving price > 1.0 and price > 1.0 - // So we turn ON root sell - SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(10.0), - ); - - // Make sure we are root selling, so we have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(root_sell_flag, "Root sell flag should be true"); - - let initial_total_hotkey_alpha = 10_000_000u64; - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &owner_coldkey, - netuid, - initial_total_hotkey_alpha.into(), - ); - - assert_ok!(SubtensorModule::set_root_claim_type( - RuntimeOrigin::signed(coldkey), - RootClaimTypeEnum::Keep - ),); - assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); - - // Distribute pending root alpha - - let initial_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - assert_eq!(initial_stake, 0u64); - - run_to_block(2); - - // Check stake after block emissions - - let new_stake: u64 = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) - .into(); - - assert!(new_stake > 0); - }); + todo!(); + + // new_test_ext(0).execute_with(|| { + // let owner_coldkey = U256::from(1001); + // let hotkey = U256::from(1002); + // let coldkey = U256::from(1003); + // let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + // Tempo::::insert(netuid, 1); + // SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + // let root_stake = 200_000_000u64; + // SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(root_stake)); + + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &coldkey, + // NetUid::ROOT, + // root_stake.into(), + // ); + // SubtensorModule::maybe_add_coldkey_index(&coldkey); + + // // Set moving price > 1.0 and price > 1.0 + // // So we turn ON root sell + // SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(10.0), + // ); + + // // Make sure we are root selling, so we have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(root_sell_flag, "Root sell flag should be true"); + + // let initial_total_hotkey_alpha = 10_000_000u64; + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &owner_coldkey, + // netuid, + // initial_total_hotkey_alpha.into(), + // ); + + // assert_ok!(SubtensorModule::set_root_claim_type( + // RuntimeOrigin::signed(coldkey), + // RootClaimTypeEnum::Keep + // ),); + // assert_eq!(RootClaimType::::get(coldkey), RootClaimTypeEnum::Keep); + + // // Distribute pending root alpha + + // let initial_stake: u64 = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + // .into(); + // assert_eq!(initial_stake, 0u64); + + // run_to_block(2); + + // // Check stake after block emissions + + // let new_stake: u64 = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid) + // .into(); + + // assert!(new_stake > 0); + // }); } #[test] fn test_populate_staking_maps() { @@ -992,94 +1015,96 @@ fn test_populate_staking_maps() { #[test] fn test_claim_root_coinbase_distribution() { - new_test_ext(1).execute_with(|| { - let owner_coldkey = U256::from(1001); - let hotkey = U256::from(1002); - let coldkey = U256::from(1003); - let netuid = add_dynamic_network(&hotkey, &owner_coldkey); - - Tempo::::insert(netuid, 1); - SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 - - let root_stake = 200_000_000u64; - let initial_tao = 200_000_000u64; - SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(initial_tao)); - - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - NetUid::ROOT, - root_stake.into(), - ); - - let initial_total_hotkey_alpha = 10_000_000u64; - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &owner_coldkey, - netuid, - initial_total_hotkey_alpha.into(), - ); - - let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); - let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); - - // Set moving price > 1.0 and price > 1.0 - // So we turn ON root sell - SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(10.0), - ); - - // Make sure we are root selling, so we have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(root_sell_flag, "Root sell flag should be true"); - - // Set TAOFlow > 0 - SubnetTaoFlow::::insert(netuid, 2222_i64); - - // Check total issuance (saved to pending alpha divs) - run_to_block(2); - - let alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); - // We went two blocks so we should have 2x the alpha emissions - assert_eq!( - initial_alpha_issuance + alpha_emissions.saturating_mul(2.into()), - alpha_issuance - ); - - let root_prop = initial_tao as f64 / (u64::from(alpha_issuance) + initial_tao) as f64; - let root_validators_share = 0.5f64; - - let expected_pending_root_alpha_divs = - u64::from(alpha_emissions) as f64 * root_prop * root_validators_share; - assert_abs_diff_eq!( - u64::from(PendingRootAlphaDivs::::get(netuid)) as f64, - expected_pending_root_alpha_divs, - epsilon = 100f64 - ); - - // Epoch pending alphas divs is distributed - - run_to_block(3); - - assert_eq!(u64::from(PendingRootAlphaDivs::::get(netuid)), 0u64); - - let claimable = *RootClaimable::::get(hotkey) - .get(&netuid) - .expect("claimable must exist at this point"); - - let validator_take_percent = 0.18f64; - let calculated_rate = (expected_pending_root_alpha_divs * 2f64) - * (1f64 - validator_take_percent) - / (root_stake as f64); - - assert_abs_diff_eq!( - claimable.saturating_to_num::(), - calculated_rate, - epsilon = 0.001f64, - ); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let owner_coldkey = U256::from(1001); + // let hotkey = U256::from(1002); + // let coldkey = U256::from(1003); + // let netuid = add_dynamic_network(&hotkey, &owner_coldkey); + + // Tempo::::insert(netuid, 1); + // SubtensorModule::set_tao_weight(u64::MAX); // Set TAO weight to 1.0 + + // let root_stake = 200_000_000u64; + // let initial_tao = 200_000_000u64; + // SubnetTAO::::insert(NetUid::ROOT, TaoCurrency::from(initial_tao)); + + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &coldkey, + // NetUid::ROOT, + // root_stake.into(), + // ); + + // let initial_total_hotkey_alpha = 10_000_000u64; + // SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + // &hotkey, + // &owner_coldkey, + // netuid, + // initial_total_hotkey_alpha.into(), + // ); + + // let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); + // let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); + + // // Set moving price > 1.0 and price > 1.0 + // // So we turn ON root sell + // SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(10.0), + // ); + + // // Make sure we are root selling, so we have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(root_sell_flag, "Root sell flag should be true"); + + // // Set TAOFlow > 0 + // SubnetTaoFlow::::insert(netuid, 2222_i64); + + // // Check total issuance (saved to pending alpha divs) + // run_to_block(2); + + // let alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); + // // We went two blocks so we should have 2x the alpha emissions + // assert_eq!( + // initial_alpha_issuance + alpha_emissions.saturating_mul(2.into()), + // alpha_issuance + // ); + + // let root_prop = initial_tao as f64 / (u64::from(alpha_issuance) + initial_tao) as f64; + // let root_validators_share = 0.5f64; + + // let expected_pending_root_alpha_divs = + // u64::from(alpha_emissions) as f64 * root_prop * root_validators_share; + // assert_abs_diff_eq!( + // u64::from(PendingRootAlphaDivs::::get(netuid)) as f64, + // expected_pending_root_alpha_divs, + // epsilon = 100f64 + // ); + + // // Epoch pending alphas divs is distributed + + // run_to_block(3); + + // assert_eq!(u64::from(PendingRootAlphaDivs::::get(netuid)), 0u64); + + // let claimable = *RootClaimable::::get(hotkey) + // .get(&netuid) + // .expect("claimable must exist at this point"); + + // let validator_take_percent = 0.18f64; + // let calculated_rate = (expected_pending_root_alpha_divs * 2f64) + // * (1f64 - validator_take_percent) + // / (root_stake as f64); + + // assert_abs_diff_eq!( + // claimable.saturating_to_num::(), + // calculated_rate, + // epsilon = 0.001f64, + // ); + // }); } #[test] diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index f6c92c8079..e8d4e0c85f 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -2733,48 +2733,50 @@ fn test_run_coinbase_not_started_start_after() { /// cargo test --package pallet-subtensor --lib -- tests::coinbase::test_coinbase_v3_liquidity_update --exact --show-output #[test] fn test_coinbase_v3_liquidity_update() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(1); - let owner_coldkey = U256::from(2); - - // add network - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - - // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha( - netuid, - TaoCurrency::ZERO, - 1_000_000_000_000.into(), - false, - ) - .unwrap(); - - let protocol_account_id = pallet_subtensor_swap::Pallet::::protocol_account_id(); - let position = pallet_subtensor_swap::Positions::::get(( - netuid, - protocol_account_id, - PositionId::from(1), - )) - .unwrap(); - let liquidity_before = position.liquidity; - - // Enable emissions and run coinbase (which will increase position liquidity) - let emission: u64 = 1_234_567; - // Set the TAO flow to non-zero - SubnetTaoFlow::::insert(netuid, 8348383_i64); - FirstEmissionBlockNumber::::insert(netuid, 0); - SubtensorModule::run_coinbase(U96F32::from_num(emission)); - - let position_after = pallet_subtensor_swap::Positions::::get(( - netuid, - protocol_account_id, - PositionId::from(1), - )) - .unwrap(); - let liquidity_after = position_after.liquidity; - - assert!(liquidity_before < liquidity_after); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let owner_hotkey = U256::from(1); + // let owner_coldkey = U256::from(2); + + // // add network + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // // Force the swap to initialize + // SubtensorModule::swap_tao_for_alpha( + // netuid, + // TaoCurrency::ZERO, + // 1_000_000_000_000.into(), + // false, + // ) + // .unwrap(); + + // let protocol_account_id = pallet_subtensor_swap::Pallet::::protocol_account_id(); + // let position = pallet_subtensor_swap::Positions::::get(( + // netuid, + // protocol_account_id, + // PositionId::from(1), + // )) + // .unwrap(); + // let liquidity_before = position.liquidity; + + // // Enable emissions and run coinbase (which will increase position liquidity) + // let emission: u64 = 1_234_567; + // // Set the TAO flow to non-zero + // SubnetTaoFlow::::insert(netuid, 8348383_i64); + // FirstEmissionBlockNumber::::insert(netuid, 0); + // SubtensorModule::run_coinbase(U96F32::from_num(emission)); + + // let position_after = pallet_subtensor_swap::Positions::::get(( + // netuid, + // protocol_account_id, + // PositionId::from(1), + // )) + // .unwrap(); + // let liquidity_after = position_after.liquidity; + + // assert!(liquidity_before < liquidity_after); + // }); } // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_drain_alpha_childkey_parentkey_with_burn --exact --show-output --nocapture @@ -2978,368 +2980,372 @@ fn test_zero_shares_zero_emission() { #[test] fn test_mining_emission_distribution_with_no_root_sell() { - new_test_ext(1).execute_with(|| { - let validator_coldkey = U256::from(1); - let validator_hotkey = U256::from(2); - let validator_miner_coldkey = U256::from(3); - let validator_miner_hotkey = U256::from(4); - let miner_coldkey = U256::from(5); - let miner_hotkey = U256::from(6); - let netuid = NetUid::from(1); - let subnet_tempo = 10; - let stake: u64 = 100_000_000_000; - let root_stake: u64 = 200_000_000_000; // 200 TAO - - // Create root network - SubtensorModule::set_tao_weight(0); // Start tao weight at 0 - SubtokenEnabled::::insert(NetUid::ROOT, true); - NetworksAdded::::insert(NetUid::ROOT, true); - - // Add network, register hotkeys, and setup network parameters - add_network(netuid, subnet_tempo, 0); - SubnetMechanism::::insert(netuid, 1); // Set mechanism to 1 - - // Setup large LPs to prevent slippage - SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); - - register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); - register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); - register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); - SubtensorModule::add_balance_to_coldkey_account( - &validator_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::add_balance_to_coldkey_account( - &validator_miner_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::add_balance_to_coldkey_account( - &miner_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::set_weights_set_rate_limit(netuid, 0); - step_block(subnet_tempo); - SubnetOwnerCut::::set(u16::MAX / 10); - // There are two validators and three neurons - MaxAllowedUids::::set(netuid, 3); - SubtensorModule::set_max_allowed_validators(netuid, 2); - - // Setup stakes: - // Stake from validator - // Stake from valiminer - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_coldkey), - validator_hotkey, - netuid, - stake.into() - )); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_miner_coldkey), - validator_miner_hotkey, - netuid, - stake.into() - )); - - // Setup YUMA so that it creates emissions - Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); - Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); - BlockAtRegistration::::set(netuid, 0, 1); - BlockAtRegistration::::set(netuid, 1, 1); - BlockAtRegistration::::set(netuid, 2, 1); - LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); - Kappa::::set(netuid, u16::MAX / 5); - ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active - ValidatorPermit::::insert(netuid, vec![true, true, false]); - - // Run run_coinbase until emissions are drained - step_block(subnet_tempo); - - // Add stake to validator so it has root stake - SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); - // init root - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_coldkey), - validator_hotkey, - NetUid::ROOT, - root_stake.into() - )); - // Set tao weight non zero - SubtensorModule::set_tao_weight(u64::MAX / 10); - - // Make root sell NOT happen - // set price very low, e.g. a lot of alpha in - //SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(0.01), - ); - - // Make sure we ARE NOT root selling, so we do not have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(!root_sell_flag, "Root sell flag should be false"); - - // Run run_coinbase until emissions are drained - step_block(subnet_tempo); - - let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); - let per_block_emission = SubtensorModule::get_block_emission_for_issuance( - SubtensorModule::get_alpha_issuance(netuid).into(), - ) - .unwrap_or(0); - - // step by one block - step_block(1); - // Verify that root alpha divs - let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); - // Check that we are indeed NOT root selling, i.e. that root alpha divs are NOT increasing - assert_eq!( - new_root_alpha_divs, old_root_alpha_divs, - "Root alpha divs should not increase" - ); - // Check root divs are zero - assert_eq!( - new_root_alpha_divs, - AlphaCurrency::ZERO, - "Root alpha divs should be zero" - ); - let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &miner_hotkey, - &miner_coldkey, - netuid, - ); - // Run again but with some root stake - step_block(subnet_tempo - 2); - assert_abs_diff_eq!( - PendingServerEmission::::get(netuid).to_u64(), - U96F32::saturating_from_num(per_block_emission) - .saturating_mul(U96F32::saturating_from_num(subnet_tempo as u64)) - .saturating_mul(U96F32::saturating_from_num(0.5)) // miner cut - .saturating_mul(U96F32::saturating_from_num(0.90)) - .saturating_to_num::(), - epsilon = 100_000_u64.into() - ); - step_block(1); - assert!( - BlocksSinceLastStep::::get(netuid) == 0, - "Blocks since last step should be 0" - ); - - let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); - log::info!("Miner uid: {miner_uid:?}"); - let miner_incentive: AlphaCurrency = { - let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) - .get(miner_uid as usize) - .copied(); - - assert!(miner_incentive.is_some()); - - (miner_incentive.unwrap_or_default() as u64).into() - }; - log::info!("Miner incentive: {miner_incentive:?}"); - - // Miner emissions - let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &miner_hotkey, - &miner_coldkey, - netuid, - ) - .to_u64() - - miner_stake_before_epoch.to_u64(); - - assert_abs_diff_eq!( - Incentive::::get(NetUidStorageIndex::from(netuid)) - .iter() - .sum::(), - u16::MAX, - epsilon = 10 - ); - - assert_abs_diff_eq!( - miner_emission_1, - U96F32::saturating_from_num(miner_incentive) - .saturating_div(u16::MAX.into()) - .saturating_mul(U96F32::saturating_from_num(per_block_emission)) - .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) - .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut - .saturating_to_num::(), - epsilon = 1_000_000_u64 - ); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let validator_coldkey = U256::from(1); + // let validator_hotkey = U256::from(2); + // let validator_miner_coldkey = U256::from(3); + // let validator_miner_hotkey = U256::from(4); + // let miner_coldkey = U256::from(5); + // let miner_hotkey = U256::from(6); + // let netuid = NetUid::from(1); + // let subnet_tempo = 10; + // let stake: u64 = 100_000_000_000; + // let root_stake: u64 = 200_000_000_000; // 200 TAO + + // // Create root network + // SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + // SubtokenEnabled::::insert(NetUid::ROOT, true); + // NetworksAdded::::insert(NetUid::ROOT, true); + + // // Add network, register hotkeys, and setup network parameters + // add_network(netuid, subnet_tempo, 0); + // SubnetMechanism::::insert(netuid, 1); // Set mechanism to 1 + + // // Setup large LPs to prevent slippage + // SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + // SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + // register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + // register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + // register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + // SubtensorModule::add_balance_to_coldkey_account( + // &validator_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::add_balance_to_coldkey_account( + // &validator_miner_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::add_balance_to_coldkey_account( + // &miner_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::set_weights_set_rate_limit(netuid, 0); + // step_block(subnet_tempo); + // SubnetOwnerCut::::set(u16::MAX / 10); + // // There are two validators and three neurons + // MaxAllowedUids::::set(netuid, 3); + // SubtensorModule::set_max_allowed_validators(netuid, 2); + + // // Setup stakes: + // // Stake from validator + // // Stake from valiminer + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_coldkey), + // validator_hotkey, + // netuid, + // stake.into() + // )); + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_miner_coldkey), + // validator_miner_hotkey, + // netuid, + // stake.into() + // )); + + // // Setup YUMA so that it creates emissions + // Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + // Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + // BlockAtRegistration::::set(netuid, 0, 1); + // BlockAtRegistration::::set(netuid, 1, 1); + // BlockAtRegistration::::set(netuid, 2, 1); + // LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + // Kappa::::set(netuid, u16::MAX / 5); + // ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + // ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // // Run run_coinbase until emissions are drained + // step_block(subnet_tempo); + + // // Add stake to validator so it has root stake + // SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // // init root + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_coldkey), + // validator_hotkey, + // NetUid::ROOT, + // root_stake.into() + // )); + // // Set tao weight non zero + // SubtensorModule::set_tao_weight(u64::MAX / 10); + + // // Make root sell NOT happen + // // set price very low, e.g. a lot of alpha in + // //SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(0.01), + // ); + + // // Make sure we ARE NOT root selling, so we do not have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(!root_sell_flag, "Root sell flag should be false"); + + // // Run run_coinbase until emissions are drained + // step_block(subnet_tempo); + + // let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + // SubtensorModule::get_alpha_issuance(netuid).into(), + // ) + // .unwrap_or(0); + + // // step by one block + // step_block(1); + // // Verify that root alpha divs + // let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // // Check that we are indeed NOT root selling, i.e. that root alpha divs are NOT increasing + // assert_eq!( + // new_root_alpha_divs, old_root_alpha_divs, + // "Root alpha divs should not increase" + // ); + // // Check root divs are zero + // assert_eq!( + // new_root_alpha_divs, + // AlphaCurrency::ZERO, + // "Root alpha divs should be zero" + // ); + // let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &miner_hotkey, + // &miner_coldkey, + // netuid, + // ); + // // Run again but with some root stake + // step_block(subnet_tempo - 2); + // assert_abs_diff_eq!( + // PendingServerEmission::::get(netuid).to_u64(), + // U96F32::saturating_from_num(per_block_emission) + // .saturating_mul(U96F32::saturating_from_num(subnet_tempo as u64)) + // .saturating_mul(U96F32::saturating_from_num(0.5)) // miner cut + // .saturating_mul(U96F32::saturating_from_num(0.90)) + // .saturating_to_num::(), + // epsilon = 100_000_u64.into() + // ); + // step_block(1); + // assert!( + // BlocksSinceLastStep::::get(netuid) == 0, + // "Blocks since last step should be 0" + // ); + + // let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + // log::info!("Miner uid: {miner_uid:?}"); + // let miner_incentive: AlphaCurrency = { + // let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + // .get(miner_uid as usize) + // .copied(); + + // assert!(miner_incentive.is_some()); + + // (miner_incentive.unwrap_or_default() as u64).into() + // }; + // log::info!("Miner incentive: {miner_incentive:?}"); + + // // Miner emissions + // let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &miner_hotkey, + // &miner_coldkey, + // netuid, + // ) + // .to_u64() + // - miner_stake_before_epoch.to_u64(); + + // assert_abs_diff_eq!( + // Incentive::::get(NetUidStorageIndex::from(netuid)) + // .iter() + // .sum::(), + // u16::MAX, + // epsilon = 10 + // ); + + // assert_abs_diff_eq!( + // miner_emission_1, + // U96F32::saturating_from_num(miner_incentive) + // .saturating_div(u16::MAX.into()) + // .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + // .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + // .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + // .saturating_to_num::(), + // epsilon = 1_000_000_u64 + // ); + // }); } // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_mining_emission_distribution_with_root_sell --exact --show-output --nocapture #[test] fn test_mining_emission_distribution_with_root_sell() { - new_test_ext(1).execute_with(|| { - let validator_coldkey = U256::from(1); - let validator_hotkey = U256::from(2); - let validator_miner_coldkey = U256::from(3); - let validator_miner_hotkey = U256::from(4); - let miner_coldkey = U256::from(5); - let miner_hotkey = U256::from(6); - let subnet_tempo = 10; - let stake: u64 = 100_000_000_000; - let root_stake: u64 = 200_000_000_000; // 200 TAO - - // Create root network - SubtensorModule::set_tao_weight(0); // Start tao weight at 0 - SubtokenEnabled::::insert(NetUid::ROOT, true); - NetworksAdded::::insert(NetUid::ROOT, true); - - // Add network, register hotkeys, and setup network parameters - let owner_hotkey = U256::from(10); - let owner_coldkey = U256::from(11); - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - Tempo::::insert(netuid, 1); - FirstEmissionBlockNumber::::insert(netuid, 0); - - // Setup large LPs to prevent slippage - SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); - - register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); - register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); - register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); - SubtensorModule::add_balance_to_coldkey_account( - &validator_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::add_balance_to_coldkey_account( - &validator_miner_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::add_balance_to_coldkey_account( - &miner_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::set_weights_set_rate_limit(netuid, 0); - step_block(subnet_tempo); - SubnetOwnerCut::::set(u16::MAX / 10); - // There are two validators and three neurons - MaxAllowedUids::::set(netuid, 3); - SubtensorModule::set_max_allowed_validators(netuid, 2); - - // Setup stakes: - // Stake from validator - // Stake from valiminer - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_coldkey), - validator_hotkey, - netuid, - stake.into() - )); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_miner_coldkey), - validator_miner_hotkey, - netuid, - stake.into() - )); - - // Setup YUMA so that it creates emissions - Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); - Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); - BlockAtRegistration::::set(netuid, 0, 1); - BlockAtRegistration::::set(netuid, 1, 1); - BlockAtRegistration::::set(netuid, 2, 1); - LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); - Kappa::::set(netuid, u16::MAX / 5); - ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active - ValidatorPermit::::insert(netuid, vec![true, true, false]); - - // Run run_coinbase until emissions are drained - step_block(subnet_tempo); - - // Add stake to validator so it has root stake - SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); - // init root - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_coldkey), - validator_hotkey, - NetUid::ROOT, - root_stake.into() - )); - // Set tao weight non zero - SubtensorModule::set_tao_weight(u64::MAX / 10); - - // Make root sell happen - // Set moving price > 1.0 - // Set price > 1.0 - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(10.0), - ); - - SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - - // Make sure we are root selling, so we have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(root_sell_flag, "Root sell flag should be true"); - - // Run run_coinbase until emissions are drained - step_block(subnet_tempo); - - let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); - let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &miner_hotkey, - &miner_coldkey, - netuid, - ); - - // step by one block - step_block(1); - // Verify root alpha divs - let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); - // Check that we ARE root selling, i.e. that root alpha divs are changing - assert_ne!( - new_root_alpha_divs, old_root_alpha_divs, - "Root alpha divs should be changing" - ); - assert!( - new_root_alpha_divs > AlphaCurrency::ZERO, - "Root alpha divs should be greater than 0" - ); - - // Run again but with some root stake - step_block(subnet_tempo - 1); - - let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); - let miner_incentive: AlphaCurrency = { - let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) - .get(miner_uid as usize) - .copied(); - - assert!(miner_incentive.is_some()); - - (miner_incentive.unwrap_or_default() as u64).into() - }; - log::info!("Miner incentive: {miner_incentive:?}"); - - let per_block_emission = SubtensorModule::get_block_emission_for_issuance( - SubtensorModule::get_alpha_issuance(netuid).into(), - ) - .unwrap_or(0); - - // Miner emissions - let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &miner_hotkey, - &miner_coldkey, - netuid, - ) - .to_u64() - - miner_stake_before_epoch.to_u64(); - - assert_abs_diff_eq!( - miner_emission_1, - U96F32::saturating_from_num(miner_incentive) - .saturating_div(u16::MAX.into()) - .saturating_mul(U96F32::saturating_from_num(per_block_emission)) - .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) - .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut - .saturating_to_num::(), - epsilon = 1_000_000_u64 - ); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let validator_coldkey = U256::from(1); + // let validator_hotkey = U256::from(2); + // let validator_miner_coldkey = U256::from(3); + // let validator_miner_hotkey = U256::from(4); + // let miner_coldkey = U256::from(5); + // let miner_hotkey = U256::from(6); + // let subnet_tempo = 10; + // let stake: u64 = 100_000_000_000; + // let root_stake: u64 = 200_000_000_000; // 200 TAO + + // // Create root network + // SubtensorModule::set_tao_weight(0); // Start tao weight at 0 + // SubtokenEnabled::::insert(NetUid::ROOT, true); + // NetworksAdded::::insert(NetUid::ROOT, true); + + // // Add network, register hotkeys, and setup network parameters + // let owner_hotkey = U256::from(10); + // let owner_coldkey = U256::from(11); + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // Tempo::::insert(netuid, 1); + // FirstEmissionBlockNumber::::insert(netuid, 0); + + // // Setup large LPs to prevent slippage + // SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_000)); + // SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000)); + + // register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + // register_ok_neuron(netuid, validator_miner_hotkey, validator_miner_coldkey, 1); + // register_ok_neuron(netuid, miner_hotkey, miner_coldkey, 2); + // SubtensorModule::add_balance_to_coldkey_account( + // &validator_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::add_balance_to_coldkey_account( + // &validator_miner_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::add_balance_to_coldkey_account( + // &miner_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::set_weights_set_rate_limit(netuid, 0); + // step_block(subnet_tempo); + // SubnetOwnerCut::::set(u16::MAX / 10); + // // There are two validators and three neurons + // MaxAllowedUids::::set(netuid, 3); + // SubtensorModule::set_max_allowed_validators(netuid, 2); + + // // Setup stakes: + // // Stake from validator + // // Stake from valiminer + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_coldkey), + // validator_hotkey, + // netuid, + // stake.into() + // )); + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_miner_coldkey), + // validator_miner_hotkey, + // netuid, + // stake.into() + // )); + + // // Setup YUMA so that it creates emissions + // Weights::::insert(NetUidStorageIndex::from(netuid), 0, vec![(1, 0xFFFF)]); + // Weights::::insert(NetUidStorageIndex::from(netuid), 1, vec![(2, 0xFFFF)]); + // BlockAtRegistration::::set(netuid, 0, 1); + // BlockAtRegistration::::set(netuid, 1, 1); + // BlockAtRegistration::::set(netuid, 2, 1); + // LastUpdate::::set(NetUidStorageIndex::from(netuid), vec![2, 2, 2]); + // Kappa::::set(netuid, u16::MAX / 5); + // ActivityCutoff::::set(netuid, u16::MAX); // makes all stake active + // ValidatorPermit::::insert(netuid, vec![true, true, false]); + + // // Run run_coinbase until emissions are drained + // step_block(subnet_tempo); + + // // Add stake to validator so it has root stake + // SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // // init root + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_coldkey), + // validator_hotkey, + // NetUid::ROOT, + // root_stake.into() + // )); + // // Set tao weight non zero + // SubtensorModule::set_tao_weight(u64::MAX / 10); + + // // Make root sell happen + // // Set moving price > 1.0 + // // Set price > 1.0 + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(10.0), + // ); + + // SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // // Make sure we are root selling, so we have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(root_sell_flag, "Root sell flag should be true"); + + // // Run run_coinbase until emissions are drained + // step_block(subnet_tempo); + + // let old_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // let miner_stake_before_epoch = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &miner_hotkey, + // &miner_coldkey, + // netuid, + // ); + + // // step by one block + // step_block(1); + // // Verify root alpha divs + // let new_root_alpha_divs = PendingRootAlphaDivs::::get(netuid); + // // Check that we ARE root selling, i.e. that root alpha divs are changing + // assert_ne!( + // new_root_alpha_divs, old_root_alpha_divs, + // "Root alpha divs should be changing" + // ); + // assert!( + // new_root_alpha_divs > AlphaCurrency::ZERO, + // "Root alpha divs should be greater than 0" + // ); + + // // Run again but with some root stake + // step_block(subnet_tempo - 1); + + // let miner_uid = Uids::::get(netuid, miner_hotkey).unwrap_or(0); + // let miner_incentive: AlphaCurrency = { + // let miner_incentive = Incentive::::get(NetUidStorageIndex::from(netuid)) + // .get(miner_uid as usize) + // .copied(); + + // assert!(miner_incentive.is_some()); + + // (miner_incentive.unwrap_or_default() as u64).into() + // }; + // log::info!("Miner incentive: {miner_incentive:?}"); + + // let per_block_emission = SubtensorModule::get_block_emission_for_issuance( + // SubtensorModule::get_alpha_issuance(netuid).into(), + // ) + // .unwrap_or(0); + + // // Miner emissions + // let miner_emission_1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &miner_hotkey, + // &miner_coldkey, + // netuid, + // ) + // .to_u64() + // - miner_stake_before_epoch.to_u64(); + + // assert_abs_diff_eq!( + // miner_emission_1, + // U96F32::saturating_from_num(miner_incentive) + // .saturating_div(u16::MAX.into()) + // .saturating_mul(U96F32::saturating_from_num(per_block_emission)) + // .saturating_mul(U96F32::saturating_from_num(subnet_tempo + 1)) + // .saturating_mul(U96F32::saturating_from_num(0.45)) // miner cut + // .saturating_to_num::(), + // epsilon = 1_000_000_u64 + // ); + // }); } #[test] @@ -3385,78 +3391,80 @@ fn test_coinbase_subnets_with_no_reg_get_no_emission() { // Tests for the excess TAO condition #[test] fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { - new_test_ext(1).execute_with(|| { - let zero = U96F32::saturating_from_num(0); - let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); - mock::setup_reserves( - netuid0, - TaoCurrency::from(1_000_000_000_000_000), - AlphaCurrency::from(1_000_000_000_000_000), - ); - // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); - - // Set netuid0 to have price tao_emission / price > alpha_emission - let alpha_emission = U96F32::saturating_from_num( - SubtensorModule::get_block_emission_for_issuance( - SubtensorModule::get_alpha_issuance(netuid0).into(), - ) - .unwrap_or(0), - ); - let price_to_set: U64F64 = U64F64::saturating_from_num(0.01); - let price_to_set_fixed: U96F32 = U96F32::saturating_from_num(price_to_set); - let sqrt_price_to_set: U64F64 = sqrt(price_to_set).unwrap(); - - let tao_emission: U96F32 = U96F32::saturating_from_num(alpha_emission) - .saturating_mul(price_to_set_fixed) - .saturating_add(U96F32::saturating_from_num(0.01)); - - // Set the price - pallet_subtensor_swap::AlphaSqrtPrice::::insert(netuid0, sqrt_price_to_set); - // Check the price is set - assert_abs_diff_eq!( - pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), - price_to_set.to_num::(), - epsilon = 0.001 - ); - - let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); - - let (tao_in, alpha_in, alpha_out, excess_tao) = - SubtensorModule::get_subnet_terms(&subnet_emissions); - - // Check our condition is met - assert!(tao_emission / price_to_set_fixed > alpha_emission); - - // alpha_out should be the alpha_emission, always - assert_abs_diff_eq!( - alpha_out[&netuid0].to_num::(), - alpha_emission.to_num::(), - epsilon = 0.01 - ); - - // alpha_in should equal the alpha_emission - assert_abs_diff_eq!( - alpha_in[&netuid0].to_num::(), - alpha_emission.to_num::(), - epsilon = 0.01 - ); - // tao_in should be the alpha_in at the ratio of the price - assert_abs_diff_eq!( - tao_in[&netuid0].to_num::(), - alpha_in[&netuid0] - .saturating_mul(price_to_set_fixed) - .to_num::(), - epsilon = 0.01 - ); - - // excess_tao should be the difference between the tao_emission and the tao_in - assert_abs_diff_eq!( - excess_tao[&netuid0].to_num::(), - tao_emission.to_num::() - tao_in[&netuid0].to_num::(), - epsilon = 0.01 - ); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let zero = U96F32::saturating_from_num(0); + // let netuid0 = add_dynamic_network(&U256::from(1), &U256::from(2)); + // mock::setup_reserves( + // netuid0, + // TaoCurrency::from(1_000_000_000_000_000), + // AlphaCurrency::from(1_000_000_000_000_000), + // ); + // // Initialize swap v3 + // Swap::maybe_initialize_palswap(netuid0); + + // // Set netuid0 to have price tao_emission / price > alpha_emission + // let alpha_emission = U96F32::saturating_from_num( + // SubtensorModule::get_block_emission_for_issuance( + // SubtensorModule::get_alpha_issuance(netuid0).into(), + // ) + // .unwrap_or(0), + // ); + // let price_to_set: U64F64 = U64F64::saturating_from_num(0.01); + // let price_to_set_fixed: U96F32 = U96F32::saturating_from_num(price_to_set); + // let sqrt_price_to_set: U64F64 = sqrt(price_to_set).unwrap(); + + // let tao_emission: U96F32 = U96F32::saturating_from_num(alpha_emission) + // .saturating_mul(price_to_set_fixed) + // .saturating_add(U96F32::saturating_from_num(0.01)); + + // // Set the price + // pallet_subtensor_swap::AlphaSqrtPrice::::insert(netuid0, sqrt_price_to_set); + // // Check the price is set + // assert_abs_diff_eq!( + // pallet_subtensor_swap::Pallet::::current_alpha_price(netuid0).to_num::(), + // price_to_set.to_num::(), + // epsilon = 0.001 + // ); + + // let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); + + // let (tao_in, alpha_in, alpha_out, excess_tao) = + // SubtensorModule::get_subnet_terms(&subnet_emissions); + + // // Check our condition is met + // assert!(tao_emission / price_to_set_fixed > alpha_emission); + + // // alpha_out should be the alpha_emission, always + // assert_abs_diff_eq!( + // alpha_out[&netuid0].to_num::(), + // alpha_emission.to_num::(), + // epsilon = 0.01 + // ); + + // // alpha_in should equal the alpha_emission + // assert_abs_diff_eq!( + // alpha_in[&netuid0].to_num::(), + // alpha_emission.to_num::(), + // epsilon = 0.01 + // ); + // // tao_in should be the alpha_in at the ratio of the price + // assert_abs_diff_eq!( + // tao_in[&netuid0].to_num::(), + // alpha_in[&netuid0] + // .saturating_mul(price_to_set_fixed) + // .to_num::(), + // epsilon = 0.01 + // ); + + // // excess_tao should be the difference between the tao_emission and the tao_in + // assert_abs_diff_eq!( + // excess_tao[&netuid0].to_num::(), + // tao_emission.to_num::() - tao_in[&netuid0].to_num::(), + // epsilon = 0.01 + // ); + // }); } #[test] @@ -3470,7 +3478,7 @@ fn test_coinbase_subnet_terms_with_alpha_in_lte_alpha_emission() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); + Swap::maybe_initialize_palswap(netuid0); let alpha_emission = U96F32::saturating_from_num( SubtensorModule::get_block_emission_for_issuance( @@ -3480,7 +3488,7 @@ fn test_coinbase_subnet_terms_with_alpha_in_lte_alpha_emission() { ); let tao_emission = U96F32::saturating_from_num(34566756_u64); - let price: U96F32 = Swap::current_alpha_price(netuid0); + let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3533,7 +3541,7 @@ fn test_coinbase_inject_and_maybe_swap_does_not_skew_reserves() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); + Swap::maybe_initialize_palswap(netuid0); let tao_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(123))]); let alpha_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(456))]); @@ -3667,7 +3675,7 @@ fn test_coinbase_emit_to_subnets_with_no_root_sell() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); + Swap::maybe_initialize_palswap(netuid0); let tao_emission = U96F32::saturating_from_num(12345678); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3681,7 +3689,7 @@ fn test_coinbase_emit_to_subnets_with_no_root_sell() { ) .unwrap_or(0), ); - let price: U96F32 = Swap::current_alpha_price(netuid0); + let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); let (tao_in, alpha_in, alpha_out, excess_tao) = SubtensorModule::get_subnet_terms(&subnet_emissions); // Based on the price, we should have NO excess TAO @@ -3758,7 +3766,7 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_v3(netuid0); + Swap::maybe_initialize_palswap(netuid0); let tao_emission = U96F32::saturating_from_num(12345678); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3772,7 +3780,7 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { ) .unwrap_or(0), ); - let price: U96F32 = Swap::current_alpha_price(netuid0); + let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); let (tao_in, alpha_in, alpha_out, excess_tao) = SubtensorModule::get_subnet_terms(&subnet_emissions); // Based on the price, we should have NO excess TAO @@ -3840,84 +3848,86 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_pending_emission_start_call_not_done --exact --show-output --nocapture #[test] fn test_pending_emission_start_call_not_done() { - new_test_ext(1).execute_with(|| { - let validator_coldkey = U256::from(1); - let validator_hotkey = U256::from(2); - let subnet_tempo = 10; - let stake: u64 = 100_000_000_000; - let root_stake: u64 = 200_000_000_000; // 200 TAO - - // Create root network - NetworksAdded::::insert(NetUid::ROOT, true); - // enabled root - SubtokenEnabled::::insert(NetUid::ROOT, true); - - // Add network, register hotkeys, and setup network parameters - let owner_hotkey = U256::from(10); - let owner_coldkey = U256::from(11); - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Remove FirstEmissionBlockNumber - FirstEmissionBlockNumber::::remove(netuid); - Tempo::::insert(netuid, subnet_tempo); - - register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account( - &validator_coldkey, - stake + ExistentialDeposit::get(), - ); - SubtensorModule::set_weights_set_rate_limit(netuid, 0); - step_block(subnet_tempo); - SubnetOwnerCut::::set(u16::MAX / 10); - // There are two validators and three neurons - MaxAllowedUids::::set(netuid, 3); - SubtensorModule::set_max_allowed_validators(netuid, 2); - - // Add stake to validator so it has root stake - SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); - // init root - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(validator_coldkey), - validator_hotkey, - NetUid::ROOT, - root_stake.into() - )); - // Set tao weight non zero - SubtensorModule::set_tao_weight(u64::MAX / 10); - - // Make root sell happen - // Set moving price > 1.0 - // Set price > 1.0 - pallet_subtensor_swap::AlphaSqrtPrice::::insert( - netuid, - U64F64::saturating_from_num(10.0), - ); - - SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - - // Make sure we are root selling, so we have root alpha divs. - let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); - assert!(root_sell_flag, "Root sell flag should be true"); - - // !!! Check that the subnet FirstEmissionBlockNumber is None -- no entry - assert!(FirstEmissionBlockNumber::::get(netuid).is_none()); - - // Run run_coinbase until emissions are accumulated - step_block(subnet_tempo - 2); - - // Verify that all pending emissions are zero - assert_eq!( - PendingServerEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - PendingValidatorEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - PendingRootAlphaDivs::::get(netuid), - AlphaCurrency::ZERO - ); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let validator_coldkey = U256::from(1); + // let validator_hotkey = U256::from(2); + // let subnet_tempo = 10; + // let stake: u64 = 100_000_000_000; + // let root_stake: u64 = 200_000_000_000; // 200 TAO + + // // Create root network + // NetworksAdded::::insert(NetUid::ROOT, true); + // // enabled root + // SubtokenEnabled::::insert(NetUid::ROOT, true); + + // // Add network, register hotkeys, and setup network parameters + // let owner_hotkey = U256::from(10); + // let owner_coldkey = U256::from(11); + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // // Remove FirstEmissionBlockNumber + // FirstEmissionBlockNumber::::remove(netuid); + // Tempo::::insert(netuid, subnet_tempo); + + // register_ok_neuron(netuid, validator_hotkey, validator_coldkey, 0); + // SubtensorModule::add_balance_to_coldkey_account( + // &validator_coldkey, + // stake + ExistentialDeposit::get(), + // ); + // SubtensorModule::set_weights_set_rate_limit(netuid, 0); + // step_block(subnet_tempo); + // SubnetOwnerCut::::set(u16::MAX / 10); + // // There are two validators and three neurons + // MaxAllowedUids::::set(netuid, 3); + // SubtensorModule::set_max_allowed_validators(netuid, 2); + + // // Add stake to validator so it has root stake + // SubtensorModule::add_balance_to_coldkey_account(&validator_coldkey, root_stake.into()); + // // init root + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(validator_coldkey), + // validator_hotkey, + // NetUid::ROOT, + // root_stake.into() + // )); + // // Set tao weight non zero + // SubtensorModule::set_tao_weight(u64::MAX / 10); + + // // Make root sell happen + // // Set moving price > 1.0 + // // Set price > 1.0 + // pallet_subtensor_swap::AlphaSqrtPrice::::insert( + // netuid, + // U64F64::saturating_from_num(10.0), + // ); + + // SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); + + // // Make sure we are root selling, so we have root alpha divs. + // let root_sell_flag = SubtensorModule::get_network_root_sell_flag(&[netuid]); + // assert!(root_sell_flag, "Root sell flag should be true"); + + // // !!! Check that the subnet FirstEmissionBlockNumber is None -- no entry + // assert!(FirstEmissionBlockNumber::::get(netuid).is_none()); + + // // Run run_coinbase until emissions are accumulated + // step_block(subnet_tempo - 2); + + // // Verify that all pending emissions are zero + // assert_eq!( + // PendingServerEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // PendingValidatorEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // PendingRootAlphaDivs::::get(netuid), + // AlphaCurrency::ZERO + // ); + // }); } #[test] diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 1d78b4dffd..dc36e75014 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2657,147 +2657,149 @@ fn test_migrate_reset_unactive_sn_get_unactive_netuids() { #[test] fn test_migrate_reset_unactive_sn() { - new_test_ext(1).execute_with(|| { - let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); - - let initial_tao = Pallet::::get_network_min_lock(); - let initial_alpha: AlphaCurrency = initial_tao.to_u64().into(); - - // Run the migration - let w = crate::migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::(); - assert!(!w.is_zero(), "weight must be non-zero"); - - // Verify the results - for netuid in &inactive_netuids { - let actual_tao_lock_amount = SubnetLocked::::get(*netuid); - let actual_tao_lock_amount_less_pool_tao = if (actual_tao_lock_amount < initial_tao) { - TaoCurrency::ZERO - } else { - actual_tao_lock_amount - initial_tao - }; - assert_eq!( - PendingServerEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - PendingValidatorEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - PendingRootAlphaDivs::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - // not modified - RAORecycledForRegistration::::get(netuid), - actual_tao_lock_amount_less_pool_tao - ); - assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( - *netuid - )); - assert_eq!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); - assert_ne!(SubnetTAO::::get(netuid), initial_tao); - assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); - assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); - assert_eq!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); - assert_eq!( - SubnetAlphaInEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - SubnetAlphaOutEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_ne!(SubnetVolume::::get(netuid), 0u128); - for hotkey in 0..10 { - let hk = U256::from(hotkey); - assert_ne!( - TotalHotkeyAlpha::::get(hk, netuid), - AlphaCurrency::ZERO - ); - assert_ne!( - TotalHotkeyShares::::get(hk, netuid), - U64F64::from_num(0.0) - ); - assert_ne!( - TotalHotkeyAlphaLastEpoch::::get(hk, netuid), - AlphaCurrency::ZERO - ); - assert_ne!(RootClaimable::::get(hk).get(netuid), None); - for coldkey in 0..10 { - let ck = U256::from(coldkey); - assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); - assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); - } - } - - // Don't touch SubnetLocked - assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); - } - - // !!! Make sure the active subnets were not reset - for netuid in &active_netuids { - let actual_tao_lock_amount = SubnetLocked::::get(*netuid); - let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount - initial_tao; - assert_ne!( - PendingServerEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_ne!( - PendingValidatorEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_ne!( - PendingRootAlphaDivs::::get(netuid), - AlphaCurrency::ZERO - ); - assert_eq!( - // not modified - RAORecycledForRegistration::::get(netuid), - actual_tao_lock_amount_less_pool_tao - ); - assert_ne!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); - assert_ne!( - SubnetAlphaInEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert_ne!( - SubnetAlphaOutEmission::::get(netuid), - AlphaCurrency::ZERO - ); - assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( - *netuid - )); - assert_ne!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); - assert_ne!(SubnetTAO::::get(netuid), initial_tao); - assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); - assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); - assert_ne!(SubnetVolume::::get(netuid), 0u128); - for hotkey in 0..10 { - let hk = U256::from(hotkey); - assert_ne!( - TotalHotkeyAlpha::::get(hk, netuid), - AlphaCurrency::ZERO - ); - assert_ne!( - TotalHotkeyShares::::get(hk, netuid), - U64F64::from_num(0.0) - ); - assert_ne!( - TotalHotkeyAlphaLastEpoch::::get(hk, netuid), - AlphaCurrency::ZERO - ); - assert!(RootClaimable::::get(hk).contains_key(netuid)); - for coldkey in 0..10 { - let ck = U256::from(coldkey); - assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); - assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); - } - } - // Don't touch SubnetLocked - assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); - } - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let (active_netuids, inactive_netuids) = do_setup_unactive_sn(); + + // let initial_tao = Pallet::::get_network_min_lock(); + // let initial_alpha: AlphaCurrency = initial_tao.to_u64().into(); + + // // Run the migration + // let w = crate::migrations::migrate_reset_unactive_sn::migrate_reset_unactive_sn::(); + // assert!(!w.is_zero(), "weight must be non-zero"); + + // // Verify the results + // for netuid in &inactive_netuids { + // let actual_tao_lock_amount = SubnetLocked::::get(*netuid); + // let actual_tao_lock_amount_less_pool_tao = if (actual_tao_lock_amount < initial_tao) { + // TaoCurrency::ZERO + // } else { + // actual_tao_lock_amount - initial_tao + // }; + // assert_eq!( + // PendingServerEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // PendingValidatorEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // PendingRootAlphaDivs::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // // not modified + // RAORecycledForRegistration::::get(netuid), + // actual_tao_lock_amount_less_pool_tao + // ); + // assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( + // *netuid + // )); + // assert_eq!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); + // assert_ne!(SubnetTAO::::get(netuid), initial_tao); + // assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); + // assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); + // assert_eq!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); + // assert_eq!( + // SubnetAlphaInEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // SubnetAlphaOutEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!(SubnetVolume::::get(netuid), 0u128); + // for hotkey in 0..10 { + // let hk = U256::from(hotkey); + // assert_ne!( + // TotalHotkeyAlpha::::get(hk, netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!( + // TotalHotkeyShares::::get(hk, netuid), + // U64F64::from_num(0.0) + // ); + // assert_ne!( + // TotalHotkeyAlphaLastEpoch::::get(hk, netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!(RootClaimable::::get(hk).get(netuid), None); + // for coldkey in 0..10 { + // let ck = U256::from(coldkey); + // assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); + // assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); + // } + // } + + // // Don't touch SubnetLocked + // assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); + // } + + // // !!! Make sure the active subnets were not reset + // for netuid in &active_netuids { + // let actual_tao_lock_amount = SubnetLocked::::get(*netuid); + // let actual_tao_lock_amount_less_pool_tao = actual_tao_lock_amount - initial_tao; + // assert_ne!( + // PendingServerEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!( + // PendingValidatorEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!( + // PendingRootAlphaDivs::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_eq!( + // // not modified + // RAORecycledForRegistration::::get(netuid), + // actual_tao_lock_amount_less_pool_tao + // ); + // assert_ne!(SubnetTaoInEmission::::get(netuid), TaoCurrency::ZERO); + // assert_ne!( + // SubnetAlphaInEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!( + // SubnetAlphaOutEmission::::get(netuid), + // AlphaCurrency::ZERO + // ); + // assert!(pallet_subtensor_swap::AlphaSqrtPrice::::contains_key( + // *netuid + // )); + // assert_ne!(PendingOwnerCut::::get(netuid), AlphaCurrency::ZERO); + // assert_ne!(SubnetTAO::::get(netuid), initial_tao); + // assert_ne!(SubnetAlphaIn::::get(netuid), initial_alpha); + // assert_ne!(SubnetAlphaOut::::get(netuid), AlphaCurrency::ZERO); + // assert_ne!(SubnetVolume::::get(netuid), 0u128); + // for hotkey in 0..10 { + // let hk = U256::from(hotkey); + // assert_ne!( + // TotalHotkeyAlpha::::get(hk, netuid), + // AlphaCurrency::ZERO + // ); + // assert_ne!( + // TotalHotkeyShares::::get(hk, netuid), + // U64F64::from_num(0.0) + // ); + // assert_ne!( + // TotalHotkeyAlphaLastEpoch::::get(hk, netuid), + // AlphaCurrency::ZERO + // ); + // assert!(RootClaimable::::get(hk).contains_key(netuid)); + // for coldkey in 0..10 { + // let ck = U256::from(coldkey); + // assert_ne!(Alpha::::get((hk, ck, netuid)), U64F64::from_num(0.0)); + // assert_ne!(RootClaimed::::get((netuid, hk, ck)), 0u128); + // } + // } + // // Don't touch SubnetLocked + // assert_ne!(SubnetLocked::::get(netuid), TaoCurrency::ZERO); + // } + // }); } #[test] diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index dfd9927da4..77012e6817 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -619,8 +619,9 @@ fn test_do_move_event_emission() { // Move stake and capture events System::reset_events(); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = U96F32::from_num( + ::SwapInterface::current_alpha_price(netuid.into()), + ); let tao_equivalent = (current_price * U96F32::from_num(alpha)).to_num::(); // no fee conversion assert_ok!(SubtensorModule::do_move_stake( RuntimeOrigin::signed(coldkey), diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 8214d58be0..c582352412 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -6,10 +6,16 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; -use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; +use sp_std::collections::{ + //btree_map::BTreeMap, + vec_deque::VecDeque, +}; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{MechId, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{Order, SwapHandler}; +use subtensor_swap_interface::{ + //Order, + SwapHandler, +}; #[test] fn test_registration_ok() { @@ -247,8 +253,9 @@ fn dissolve_owner_cut_refund_logic() { // Use the current alpha price to estimate the TAO equivalent. let owner_emission_tao = { - let price: U96F32 = - ::SwapInterface::current_alpha_price(net.into()); + let price: U96F32 = U96F32::from_num( + ::SwapInterface::current_alpha_price(net.into()), + ); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -906,8 +913,9 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let owner_emission_tao: u64 = { // Fallback matches the pallet's fallback - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); + let price: U96F32 = U96F32::from_num( + ::SwapInterface::current_alpha_price(netuid.into()), + ); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -986,8 +994,9 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { .saturating_to_num::(); let owner_emission_tao_u64 = { - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); + let price: U96F32 = U96F32::from_num( + ::SwapInterface::current_alpha_price(netuid.into()), + ); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -1777,449 +1786,450 @@ fn test_tempo_greater_than_weight_set_rate_limit() { #[allow(clippy::indexing_slicing)] #[test] fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state() { - new_test_ext(0).execute_with(|| { - // ──────────────────────────────────────────────────────────────────── - // 0) Constants and helpers (distinct hotkeys & coldkeys) - // ──────────────────────────────────────────────────────────────────── - const NUM_NETS: usize = 4; - - // Six LP coldkeys - let cold_lps: [U256; 6] = [ - U256::from(3001), - U256::from(3002), - U256::from(3003), - U256::from(3004), - U256::from(3005), - U256::from(3006), - ]; - - // For each coldkey, define two DISTINCT hotkeys it owns. - let mut cold_to_hots: BTreeMap = BTreeMap::new(); - for &c in cold_lps.iter() { - let h1 = U256::from(c.low_u64().saturating_add(100_000)); - let h2 = U256::from(c.low_u64().saturating_add(200_000)); - cold_to_hots.insert(c, [h1, h2]); - } - - // Distinct τ pot sizes per net. - let pots: [u64; NUM_NETS] = [12_345, 23_456, 34_567, 45_678]; - - let lp_sets_per_net: [&[U256]; NUM_NETS] = [ - &cold_lps[0..4], // net0: A,B,C,D - &cold_lps[2..6], // net1: C,D,E,F - &cold_lps[0..6], // net2: A..F - &cold_lps[1..5], // net3: B,C,D,E - ]; - - // Multiple bands/sizes → many positions per cold across nets, using mixed hotkeys. - let bands: [i32; 3] = [5, 13, 30]; - let liqs: [u64; 3] = [400_000, 700_000, 1_100_000]; - - // Helper: add a V3 position via a (hot, cold) pair. - let add_pos = |net: NetUid, hot: U256, cold: U256, band: i32, liq: u64| { - let ct = pallet_subtensor_swap::CurrentTick::::get(net); - let lo = ct.saturating_sub(band); - let hi = ct.saturating_add(band); - assert_ok!(pallet_subtensor_swap::Pallet::::add_liquidity( - RuntimeOrigin::signed(cold), - hot, - net, - lo, - hi, - liq - )); - }; - - // ──────────────────────────────────────────────────────────────────── - // 1) Create many subnets, enable V3, fix price at tick=0 (sqrt≈1) - // ──────────────────────────────────────────────────────────────────── - let mut nets: Vec = Vec::new(); - for i in 0..NUM_NETS { - let owner_hot = U256::from(10_000 + (i as u64)); - let owner_cold = U256::from(20_000 + (i as u64)); - let net = add_dynamic_network(&owner_hot, &owner_cold); - SubtensorModule::set_max_registrations_per_block(net, 1_000u16); - SubtensorModule::set_target_registrations_per_interval(net, 1_000u16); - Emission::::insert(net, Vec::::new()); - SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); - - assert_ok!( - pallet_subtensor_swap::Pallet::::toggle_user_liquidity( - RuntimeOrigin::root(), - net, - true - ) - ); - - // Price/tick pinned so LP math stays stable (sqrt(1)). - let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); - let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1) price"); - pallet_subtensor_swap::CurrentTick::::set(net, ct0); - pallet_subtensor_swap::AlphaSqrtPrice::::set(net, sqrt1); - - nets.push(net); - } - - // Map net → index for quick lookups. - let mut net_index: BTreeMap = BTreeMap::new(); - for (i, &n) in nets.iter().enumerate() { - net_index.insert(n, i); - } - - // ──────────────────────────────────────────────────────────────────── - // 2) Pre-create a handful of small (hot, cold) pairs so accounts exist - // ──────────────────────────────────────────────────────────────────── - for id in 0u64..10 { - let cold_acc = U256::from(1_000_000 + id); - let hot_acc = U256::from(2_000_000 + id); - for &net in nets.iter() { - register_ok_neuron(net, hot_acc, cold_acc, 100_000 + id); - } - } - - // ──────────────────────────────────────────────────────────────────── - // 3) LPs per net: register each (hot, cold), massive τ prefund, and stake - // ──────────────────────────────────────────────────────────────────── - for &cold in cold_lps.iter() { - SubtensorModule::add_balance_to_coldkey_account(&cold, u64::MAX); - } - - // τ balances before LP adds (after staking): - let mut tao_before: BTreeMap = BTreeMap::new(); - - // Ordered α snapshot per net at **pair granularity** (pre‑LP): - let mut alpha_pairs_per_net: BTreeMap> = BTreeMap::new(); - - // Register both hotkeys for each participating cold on each net and stake τ→α. - for (ni, &net) in nets.iter().enumerate() { - let participants = lp_sets_per_net[ni]; - for &cold in participants.iter() { - let [hot1, hot2] = cold_to_hots[&cold]; - - // Ensure (hot, cold) neurons exist on this net. - register_ok_neuron( - net, - hot1, - cold, - (ni as u64) * 10_000 + (hot1.low_u64() % 10_000), - ); - register_ok_neuron( - net, - hot2, - cold, - (ni as u64) * 10_000 + (hot2.low_u64() % 10_000) + 1, - ); - - // Stake τ (split across the two hotkeys). - let base: u64 = - 5_000_000 + ((ni as u64) * 1_000_000) + ((cold.low_u64() % 10) * 250_000); - let stake1: u64 = base.saturating_mul(3) / 5; // 60% - let stake2: u64 = base.saturating_sub(stake1); // 40% - - assert_ok!(SubtensorModule::do_add_stake( - RuntimeOrigin::signed(cold), - hot1, - net, - stake1.into() - )); - assert_ok!(SubtensorModule::do_add_stake( - RuntimeOrigin::signed(cold), - hot2, - net, - stake2.into() - )); - } - } - - // Record τ balances now (post‑stake, pre‑LP). - for &cold in cold_lps.iter() { - tao_before.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); - } - - // Capture **pair‑level** α snapshot per net (pre‑LP). - for ((hot, cold, net), amt) in Alpha::::iter() { - if let Some(&ni) = net_index.get(&net) - && lp_sets_per_net[ni].contains(&cold) { - let a: u128 = amt.saturating_to_num(); - if a > 0 { - alpha_pairs_per_net - .entry(net) - .or_default() - .push(((hot, cold), a)); - } - } - } - - // ──────────────────────────────────────────────────────────────────── - // 4) Add many V3 positions per cold across nets, alternating hotkeys - // ──────────────────────────────────────────────────────────────────── - for (ni, &net) in nets.iter().enumerate() { - let participants = lp_sets_per_net[ni]; - for (pi, &cold) in participants.iter().enumerate() { - let [hot1, hot2] = cold_to_hots[&cold]; - let hots = [hot1, hot2]; - for k in 0..3 { - let band = bands[(pi + k) % bands.len()]; - let liq = liqs[(ni + k) % liqs.len()]; - let hot = hots[k % hots.len()]; - add_pos(net, hot, cold, band, liq); - } - } - } - - // Snapshot τ balances AFTER LP adds (to measure actual principal debit). - let mut tao_after_adds: BTreeMap = BTreeMap::new(); - for &cold in cold_lps.iter() { - tao_after_adds.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); - } - - // ──────────────────────────────────────────────────────────────────── - // 5) Compute Hamilton-apportionment BASE shares per cold and total leftover - // from the **pair-level** pre‑LP α snapshot; also count pairs per cold. - // ──────────────────────────────────────────────────────────────────── - let mut base_share_cold: BTreeMap = - cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); - let mut pair_count_cold: BTreeMap = - cold_lps.iter().copied().map(|c| (c, 0_u32)).collect(); - - let mut leftover_total: u64 = 0; - - for (ni, &net) in nets.iter().enumerate() { - let pot = pots[ni]; - let pairs = alpha_pairs_per_net.get(&net).cloned().unwrap_or_default(); - if pot == 0 || pairs.is_empty() { - continue; - } - let total_alpha: u128 = pairs.iter().map(|(_, a)| *a).sum(); - if total_alpha == 0 { - continue; - } - - let mut base_sum_net: u64 = 0; - for ((_, cold), a) in pairs.iter().copied() { - // quota = a * pot / total_alpha - let prod: u128 = a.saturating_mul(pot as u128); - let base: u64 = (prod / total_alpha) as u64; - base_sum_net = base_sum_net.saturating_add(base); - *base_share_cold.entry(cold).or_default() = - base_share_cold[&cold].saturating_add(base); - *pair_count_cold.entry(cold).or_default() += 1; - } - let leftover_net = pot.saturating_sub(base_sum_net); - leftover_total = leftover_total.saturating_add(leftover_net); - } - - // ──────────────────────────────────────────────────────────────────── - // 6) Seed τ pots and dissolve *all* networks (liquidates LPs + refunds) - // ──────────────────────────────────────────────────────────────────── - for (ni, &net) in nets.iter().enumerate() { - SubnetTAO::::insert(net, TaoCurrency::from(pots[ni])); - } - for &net in nets.iter() { - assert_ok!(SubtensorModule::do_dissolve_network(net)); - } - - // ──────────────────────────────────────────────────────────────────── - // 7) Assertions: τ balances, α gone, nets removed, swap state clean - // (Hamilton invariants enforced at cold-level without relying on tie-break) - // ──────────────────────────────────────────────────────────────────── - // Collect actual pot credits per cold (principal cancels out against adds when comparing before→after). - let mut actual_pot_cold: BTreeMap = - cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); - for &cold in cold_lps.iter() { - let before = tao_before[&cold]; - let after = SubtensorModule::get_coldkey_balance(&cold); - actual_pot_cold.insert(cold, after.saturating_sub(before)); - } - - // (a) Sum of actual pot credits equals total pots. - let total_actual: u64 = actual_pot_cold.values().copied().sum(); - let total_pots: u64 = pots.iter().copied().sum(); - assert_eq!( - total_actual, total_pots, - "total τ pot credited across colds must equal sum of pots" - ); - - // (b) Each cold’s pot is within Hamilton bounds: base ≤ actual ≤ base + #pairs. - let mut extra_accum: u64 = 0; - for &cold in cold_lps.iter() { - let base = *base_share_cold.get(&cold).unwrap_or(&0); - let pairs = *pair_count_cold.get(&cold).unwrap_or(&0) as u64; - let actual = *actual_pot_cold.get(&cold).unwrap_or(&0); - - assert!( - actual >= base, - "cold {cold:?} actual pot {actual} is below base {base}" - ); - assert!( - actual <= base.saturating_add(pairs), - "cold {cold:?} actual pot {actual} exceeds base + pairs ({base} + {pairs})" - ); - - extra_accum = extra_accum.saturating_add(actual.saturating_sub(base)); - } - - // (c) The total “extra beyond base” equals the computed leftover_total across nets. - assert_eq!( - extra_accum, leftover_total, - "sum of extras beyond base must equal total leftover" - ); - - // (d) τ principal was fully refunded (compare after_adds → after). - for &cold in cold_lps.iter() { - let before = tao_before[&cold]; - let mid = tao_after_adds[&cold]; - let after = SubtensorModule::get_coldkey_balance(&cold); - let principal_actual = before.saturating_sub(mid); - let actual_pot = after.saturating_sub(before); - assert_eq!( - after.saturating_sub(mid), - principal_actual.saturating_add(actual_pot), - "cold {cold:?} τ balance incorrect vs 'after_adds'" - ); - } - - // For each dissolved net, check α ledgers gone, network removed, and swap state clean. - for &net in nets.iter() { - assert!( - Alpha::::iter().all(|((_h, _c, n), _)| n != net), - "alpha ledger not fully cleared for net {net:?}" - ); - assert!( - !SubtensorModule::if_subnet_exist(net), - "subnet {net:?} still exists" - ); - assert!( - pallet_subtensor_swap::Ticks::::iter_prefix(net) - .next() - .is_none(), - "ticks not cleared for net {net:?}" - ); - assert!( - !pallet_subtensor_swap::Positions::::iter() - .any(|((n, _owner, _pid), _)| n == net), - "swap positions not fully cleared for net {net:?}" - ); - assert_eq!( - pallet_subtensor_swap::FeeGlobalTao::::get(net).saturating_to_num::(), - 0, - "FeeGlobalTao nonzero for net {net:?}" - ); - assert_eq!( - pallet_subtensor_swap::FeeGlobalAlpha::::get(net).saturating_to_num::(), - 0, - "FeeGlobalAlpha nonzero for net {net:?}" - ); - assert_eq!( - pallet_subtensor_swap::CurrentLiquidity::::get(net), - 0, - "CurrentLiquidity not zero for net {net:?}" - ); - assert!( - !pallet_subtensor_swap::SwapV3Initialized::::get(net), - "SwapV3Initialized still set" - ); - assert!( - !pallet_subtensor_swap::EnabledUserLiquidity::::get(net), - "EnabledUserLiquidity still set" - ); - assert!( - pallet_subtensor_swap::TickIndexBitmapWords::::iter_prefix((net,)) - .next() - .is_none(), - "TickIndexBitmapWords not cleared for net {net:?}" - ); - } - - // ──────────────────────────────────────────────────────────────────── - // 8) Re-register a fresh subnet and re‑stake using the pallet’s min rule - // Assert αΔ equals the sim-swap result for the exact τ staked. - // ──────────────────────────────────────────────────────────────────── - let new_owner_hot = U256::from(99_000); - let new_owner_cold = U256::from(99_001); - let net_new = add_dynamic_network(&new_owner_hot, &new_owner_cold); - SubtensorModule::set_max_registrations_per_block(net_new, 1_000u16); - SubtensorModule::set_target_registrations_per_interval(net_new, 1_000u16); - Emission::::insert(net_new, Vec::::new()); - SubtensorModule::set_subnet_locked_balance(net_new, TaoCurrency::from(0)); - - assert_ok!( - pallet_subtensor_swap::Pallet::::toggle_user_liquidity( - RuntimeOrigin::root(), - net_new, - true - ) - ); - let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); - let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1)"); - pallet_subtensor_swap::CurrentTick::::set(net_new, ct0); - pallet_subtensor_swap::AlphaSqrtPrice::::set(net_new, sqrt1); - - // Compute the exact min stake per the pallet rule: DefaultMinStake + fee(DefaultMinStake). - let min_stake = DefaultMinStake::::get(); - let order = GetAlphaForTao::::with_amount(min_stake); - let fee_for_min = pallet_subtensor_swap::Pallet::::sim_swap( - net_new, - order, - ) - .map(|r| r.fee_paid) - .unwrap_or_else(|_e| { - as subtensor_swap_interface::SwapHandler>::approx_fee_amount(net_new, min_stake) - }); - let min_amount_required = min_stake.saturating_add(fee_for_min).to_u64(); - - // Re‑stake from three coldkeys; choose a specific DISTINCT hotkey per cold. - for &cold in &cold_lps[0..3] { - let [hot1, _hot2] = cold_to_hots[&cold]; - register_ok_neuron(net_new, hot1, cold, 7777); - - let before_tao = SubtensorModule::get_coldkey_balance(&cold); - let a_prev: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); - - // Expected α for this exact τ, using the same sim path as the pallet. - let order = GetAlphaForTao::::with_amount(min_amount_required); - let expected_alpha_out = pallet_subtensor_swap::Pallet::::sim_swap( - net_new, - order, - ) - .map(|r| r.amount_paid_out) - .expect("sim_swap must succeed for fresh net and min amount"); - - assert_ok!(SubtensorModule::do_add_stake( - RuntimeOrigin::signed(cold), - hot1, - net_new, - min_amount_required.into() - )); - - let after_tao = SubtensorModule::get_coldkey_balance(&cold); - let a_new: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); - let a_delta = a_new.saturating_sub(a_prev); - - // τ decreased by exactly the amount we sent. - assert_eq!( - after_tao, - before_tao.saturating_sub(min_amount_required), - "τ did not decrease by the min required restake amount for cold {cold:?}" - ); - - // α minted equals the simulated swap’s net out for that same τ. - assert_eq!( - a_delta, expected_alpha_out.to_u64(), - "α minted mismatch for cold {cold:?} (hot {hot1:?}) on new net (αΔ {a_delta}, expected {expected_alpha_out})" - ); - } - - // Ensure V3 still functional on new net: add a small position for the first cold using its hot1 - let who_cold = cold_lps[0]; - let [who_hot, _] = cold_to_hots[&who_cold]; - add_pos(net_new, who_hot, who_cold, 8, 123_456); - assert!( - pallet_subtensor_swap::Positions::::iter() - .any(|((n, owner, _pid), _)| n == net_new && owner == who_cold), - "new position not recorded on the re-registered net" - ); - }); + todo!(); + + // new_test_ext(0).execute_with(|| { + // // ──────────────────────────────────────────────────────────────────── + // // 0) Constants and helpers (distinct hotkeys & coldkeys) + // // ──────────────────────────────────────────────────────────────────── + // const NUM_NETS: usize = 4; + + // // Six LP coldkeys + // let cold_lps: [U256; 6] = [ + // U256::from(3001), + // U256::from(3002), + // U256::from(3003), + // U256::from(3004), + // U256::from(3005), + // U256::from(3006), + // ]; + + // // For each coldkey, define two DISTINCT hotkeys it owns. + // let mut cold_to_hots: BTreeMap = BTreeMap::new(); + // for &c in cold_lps.iter() { + // let h1 = U256::from(c.low_u64().saturating_add(100_000)); + // let h2 = U256::from(c.low_u64().saturating_add(200_000)); + // cold_to_hots.insert(c, [h1, h2]); + // } + + // // Distinct τ pot sizes per net. + // let pots: [u64; NUM_NETS] = [12_345, 23_456, 34_567, 45_678]; + + // let lp_sets_per_net: [&[U256]; NUM_NETS] = [ + // &cold_lps[0..4], // net0: A,B,C,D + // &cold_lps[2..6], // net1: C,D,E,F + // &cold_lps[0..6], // net2: A..F + // &cold_lps[1..5], // net3: B,C,D,E + // ]; + + // // Multiple bands/sizes → many positions per cold across nets, using mixed hotkeys. + // let bands: [i32; 3] = [5, 13, 30]; + // let liqs: [u64; 3] = [400_000, 700_000, 1_100_000]; + + // // Helper: add a V3 position via a (hot, cold) pair. + // let add_pos = |net: NetUid, hot: U256, cold: U256, band: i32, liq: u64| { + // let ct = pallet_subtensor_swap::CurrentTick::::get(net); + // let lo = ct.saturating_sub(band); + // let hi = ct.saturating_add(band); + // assert_ok!(pallet_subtensor_swap::Pallet::::add_liquidity( + // RuntimeOrigin::signed(cold), + // hot, + // net, + // lo, + // hi, + // liq + // )); + // }; + + // // ──────────────────────────────────────────────────────────────────── + // // 1) Create many subnets, enable V3, fix price at tick=0 (sqrt≈1) + // // ──────────────────────────────────────────────────────────────────── + // let mut nets: Vec = Vec::new(); + // for i in 0..NUM_NETS { + // let owner_hot = U256::from(10_000 + (i as u64)); + // let owner_cold = U256::from(20_000 + (i as u64)); + // let net = add_dynamic_network(&owner_hot, &owner_cold); + // SubtensorModule::set_max_registrations_per_block(net, 1_000u16); + // SubtensorModule::set_target_registrations_per_interval(net, 1_000u16); + // Emission::::insert(net, Vec::::new()); + // SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); + + // assert_ok!( + // pallet_subtensor_swap::Pallet::::toggle_user_liquidity( + // RuntimeOrigin::root(), + // net, + // true + // ) + // ); + + // // Price/tick pinned so LP math stays stable (sqrt(1)). + // let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); + // let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1) price"); + // pallet_subtensor_swap::CurrentTick::::set(net, ct0); + // pallet_subtensor_swap::AlphaSqrtPrice::::set(net, sqrt1); + + // nets.push(net); + // } + + // // Map net → index for quick lookups. + // let mut net_index: BTreeMap = BTreeMap::new(); + // for (i, &n) in nets.iter().enumerate() { + // net_index.insert(n, i); + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 2) Pre-create a handful of small (hot, cold) pairs so accounts exist + // // ──────────────────────────────────────────────────────────────────── + // for id in 0u64..10 { + // let cold_acc = U256::from(1_000_000 + id); + // let hot_acc = U256::from(2_000_000 + id); + // for &net in nets.iter() { + // register_ok_neuron(net, hot_acc, cold_acc, 100_000 + id); + // } + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 3) LPs per net: register each (hot, cold), massive τ prefund, and stake + // // ──────────────────────────────────────────────────────────────────── + // for &cold in cold_lps.iter() { + // SubtensorModule::add_balance_to_coldkey_account(&cold, u64::MAX); + // } + + // // τ balances before LP adds (after staking): + // let mut tao_before: BTreeMap = BTreeMap::new(); + + // // Ordered α snapshot per net at **pair granularity** (pre‑LP): + // let mut alpha_pairs_per_net: BTreeMap> = BTreeMap::new(); + + // // Register both hotkeys for each participating cold on each net and stake τ→α. + // for (ni, &net) in nets.iter().enumerate() { + // let participants = lp_sets_per_net[ni]; + // for &cold in participants.iter() { + // let [hot1, hot2] = cold_to_hots[&cold]; + + // // Ensure (hot, cold) neurons exist on this net. + // register_ok_neuron( + // net, + // hot1, + // cold, + // (ni as u64) * 10_000 + (hot1.low_u64() % 10_000), + // ); + // register_ok_neuron( + // net, + // hot2, + // cold, + // (ni as u64) * 10_000 + (hot2.low_u64() % 10_000) + 1, + // ); + + // // Stake τ (split across the two hotkeys). + // let base: u64 = + // 5_000_000 + ((ni as u64) * 1_000_000) + ((cold.low_u64() % 10) * 250_000); + // let stake1: u64 = base.saturating_mul(3) / 5; // 60% + // let stake2: u64 = base.saturating_sub(stake1); // 40% + + // assert_ok!(SubtensorModule::do_add_stake( + // RuntimeOrigin::signed(cold), + // hot1, + // net, + // stake1.into() + // )); + // assert_ok!(SubtensorModule::do_add_stake( + // RuntimeOrigin::signed(cold), + // hot2, + // net, + // stake2.into() + // )); + // } + // } + + // // Record τ balances now (post‑stake, pre‑LP). + // for &cold in cold_lps.iter() { + // tao_before.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); + // } + // // Capture **pair‑level** α snapshot per net (pre‑LP). + // for ((hot, cold, net), amt) in Alpha::::iter() { + // if let Some(&ni) = net_index.get(&net) + // && lp_sets_per_net[ni].contains(&cold) { + // let a: u128 = amt.saturating_to_num(); + // if a > 0 { + // alpha_pairs_per_net + // .entry(net) + // .or_default() + // .push(((hot, cold), a)); + // } + // } + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 4) Add many V3 positions per cold across nets, alternating hotkeys + // // ──────────────────────────────────────────────────────────────────── + // for (ni, &net) in nets.iter().enumerate() { + // let participants = lp_sets_per_net[ni]; + // for (pi, &cold) in participants.iter().enumerate() { + // let [hot1, hot2] = cold_to_hots[&cold]; + // let hots = [hot1, hot2]; + // for k in 0..3 { + // let band = bands[(pi + k) % bands.len()]; + // let liq = liqs[(ni + k) % liqs.len()]; + // let hot = hots[k % hots.len()]; + // add_pos(net, hot, cold, band, liq); + // } + // } + // } + + // // Snapshot τ balances AFTER LP adds (to measure actual principal debit). + // let mut tao_after_adds: BTreeMap = BTreeMap::new(); + // for &cold in cold_lps.iter() { + // tao_after_adds.insert(cold, SubtensorModule::get_coldkey_balance(&cold)); + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 5) Compute Hamilton-apportionment BASE shares per cold and total leftover + // // from the **pair-level** pre‑LP α snapshot; also count pairs per cold. + // // ──────────────────────────────────────────────────────────────────── + // let mut base_share_cold: BTreeMap = + // cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); + // let mut pair_count_cold: BTreeMap = + // cold_lps.iter().copied().map(|c| (c, 0_u32)).collect(); + + // let mut leftover_total: u64 = 0; + + // for (ni, &net) in nets.iter().enumerate() { + // let pot = pots[ni]; + // let pairs = alpha_pairs_per_net.get(&net).cloned().unwrap_or_default(); + // if pot == 0 || pairs.is_empty() { + // continue; + // } + // let total_alpha: u128 = pairs.iter().map(|(_, a)| *a).sum(); + // if total_alpha == 0 { + // continue; + // } + + // let mut base_sum_net: u64 = 0; + // for ((_, cold), a) in pairs.iter().copied() { + // // quota = a * pot / total_alpha + // let prod: u128 = a.saturating_mul(pot as u128); + // let base: u64 = (prod / total_alpha) as u64; + // base_sum_net = base_sum_net.saturating_add(base); + // *base_share_cold.entry(cold).or_default() = + // base_share_cold[&cold].saturating_add(base); + // *pair_count_cold.entry(cold).or_default() += 1; + // } + // let leftover_net = pot.saturating_sub(base_sum_net); + // leftover_total = leftover_total.saturating_add(leftover_net); + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 6) Seed τ pots and dissolve *all* networks (liquidates LPs + refunds) + // // ──────────────────────────────────────────────────────────────────── + // for (ni, &net) in nets.iter().enumerate() { + // SubnetTAO::::insert(net, TaoCurrency::from(pots[ni])); + // } + // for &net in nets.iter() { + // assert_ok!(SubtensorModule::do_dissolve_network(net)); + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 7) Assertions: τ balances, α gone, nets removed, swap state clean + // // (Hamilton invariants enforced at cold-level without relying on tie-break) + // // ──────────────────────────────────────────────────────────────────── + // // Collect actual pot credits per cold (principal cancels out against adds when comparing before→after). + // let mut actual_pot_cold: BTreeMap = + // cold_lps.iter().copied().map(|c| (c, 0_u64)).collect(); + // for &cold in cold_lps.iter() { + // let before = tao_before[&cold]; + // let after = SubtensorModule::get_coldkey_balance(&cold); + // actual_pot_cold.insert(cold, after.saturating_sub(before)); + // } + + // // (a) Sum of actual pot credits equals total pots. + // let total_actual: u64 = actual_pot_cold.values().copied().sum(); + // let total_pots: u64 = pots.iter().copied().sum(); + // assert_eq!( + // total_actual, total_pots, + // "total τ pot credited across colds must equal sum of pots" + // ); + + // // (b) Each cold’s pot is within Hamilton bounds: base ≤ actual ≤ base + #pairs. + // let mut extra_accum: u64 = 0; + // for &cold in cold_lps.iter() { + // let base = *base_share_cold.get(&cold).unwrap_or(&0); + // let pairs = *pair_count_cold.get(&cold).unwrap_or(&0) as u64; + // let actual = *actual_pot_cold.get(&cold).unwrap_or(&0); + + // assert!( + // actual >= base, + // "cold {cold:?} actual pot {actual} is below base {base}" + // ); + // assert!( + // actual <= base.saturating_add(pairs), + // "cold {cold:?} actual pot {actual} exceeds base + pairs ({base} + {pairs})" + // ); + + // extra_accum = extra_accum.saturating_add(actual.saturating_sub(base)); + // } + + // // (c) The total “extra beyond base” equals the computed leftover_total across nets. + // assert_eq!( + // extra_accum, leftover_total, + // "sum of extras beyond base must equal total leftover" + // ); + + // // (d) τ principal was fully refunded (compare after_adds → after). + // for &cold in cold_lps.iter() { + // let before = tao_before[&cold]; + // let mid = tao_after_adds[&cold]; + // let after = SubtensorModule::get_coldkey_balance(&cold); + // let principal_actual = before.saturating_sub(mid); + // let actual_pot = after.saturating_sub(before); + // assert_eq!( + // after.saturating_sub(mid), + // principal_actual.saturating_add(actual_pot), + // "cold {cold:?} τ balance incorrect vs 'after_adds'" + // ); + // } + + // // For each dissolved net, check α ledgers gone, network removed, and swap state clean. + // for &net in nets.iter() { + // assert!( + // Alpha::::iter().all(|((_h, _c, n), _)| n != net), + // "alpha ledger not fully cleared for net {net:?}" + // ); + // assert!( + // !SubtensorModule::if_subnet_exist(net), + // "subnet {net:?} still exists" + // ); + // assert!( + // pallet_subtensor_swap::Ticks::::iter_prefix(net) + // .next() + // .is_none(), + // "ticks not cleared for net {net:?}" + // ); + // assert!( + // !pallet_subtensor_swap::Positions::::iter() + // .any(|((n, _owner, _pid), _)| n == net), + // "swap positions not fully cleared for net {net:?}" + // ); + // assert_eq!( + // pallet_subtensor_swap::FeeGlobalTao::::get(net).saturating_to_num::(), + // 0, + // "FeeGlobalTao nonzero for net {net:?}" + // ); + // assert_eq!( + // pallet_subtensor_swap::FeeGlobalAlpha::::get(net).saturating_to_num::(), + // 0, + // "FeeGlobalAlpha nonzero for net {net:?}" + // ); + // assert_eq!( + // pallet_subtensor_swap::CurrentLiquidity::::get(net), + // 0, + // "CurrentLiquidity not zero for net {net:?}" + // ); + // assert!( + // !pallet_subtensor_swap::PalSwapInitialized::::get(net), + // "PalSwapInitialized still set" + // ); + // assert!( + // !pallet_subtensor_swap::EnabledUserLiquidity::::get(net), + // "EnabledUserLiquidity still set" + // ); + // assert!( + // pallet_subtensor_swap::TickIndexBitmapWords::::iter_prefix((net,)) + // .next() + // .is_none(), + // "TickIndexBitmapWords not cleared for net {net:?}" + // ); + // } + + // // ──────────────────────────────────────────────────────────────────── + // // 8) Re-register a fresh subnet and re‑stake using the pallet’s min rule + // // Assert αΔ equals the sim-swap result for the exact τ staked. + // // ──────────────────────────────────────────────────────────────────── + // let new_owner_hot = U256::from(99_000); + // let new_owner_cold = U256::from(99_001); + // let net_new = add_dynamic_network(&new_owner_hot, &new_owner_cold); + // SubtensorModule::set_max_registrations_per_block(net_new, 1_000u16); + // SubtensorModule::set_target_registrations_per_interval(net_new, 1_000u16); + // Emission::::insert(net_new, Vec::::new()); + // SubtensorModule::set_subnet_locked_balance(net_new, TaoCurrency::from(0)); + + // assert_ok!( + // pallet_subtensor_swap::Pallet::::toggle_user_liquidity( + // RuntimeOrigin::root(), + // net_new, + // true + // ) + // ); + // let ct0 = pallet_subtensor_swap::tick::TickIndex::new_unchecked(0); + // let sqrt1 = ct0.try_to_sqrt_price().expect("sqrt(1)"); + // pallet_subtensor_swap::CurrentTick::::set(net_new, ct0); + // pallet_subtensor_swap::AlphaSqrtPrice::::set(net_new, sqrt1); + + // // Compute the exact min stake per the pallet rule: DefaultMinStake + fee(DefaultMinStake). + // let min_stake = DefaultMinStake::::get(); + // let order = GetAlphaForTao::::with_amount(min_stake); + // let fee_for_min = pallet_subtensor_swap::Pallet::::sim_swap( + // net_new, + // order, + // ) + // .map(|r| r.fee_paid) + // .unwrap_or_else(|_e| { + // as subtensor_swap_interface::SwapHandler>::approx_fee_amount(net_new, min_stake) + // }); + // let min_amount_required = min_stake.saturating_add(fee_for_min).to_u64(); + + // // Re‑stake from three coldkeys; choose a specific DISTINCT hotkey per cold. + // for &cold in &cold_lps[0..3] { + // let [hot1, _hot2] = cold_to_hots[&cold]; + // register_ok_neuron(net_new, hot1, cold, 7777); + + // let before_tao = SubtensorModule::get_coldkey_balance(&cold); + // let a_prev: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); + + // // Expected α for this exact τ, using the same sim path as the pallet. + // let order = GetAlphaForTao::::with_amount(min_amount_required); + // let expected_alpha_out = pallet_subtensor_swap::Pallet::::sim_swap( + // net_new, + // order, + // ) + // .map(|r| r.amount_paid_out) + // .expect("sim_swap must succeed for fresh net and min amount"); + + // assert_ok!(SubtensorModule::do_add_stake( + // RuntimeOrigin::signed(cold), + // hot1, + // net_new, + // min_amount_required.into() + // )); + + // let after_tao = SubtensorModule::get_coldkey_balance(&cold); + // let a_new: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); + // let a_delta = a_new.saturating_sub(a_prev); + + // // τ decreased by exactly the amount we sent. + // assert_eq!( + // after_tao, + // before_tao.saturating_sub(min_amount_required), + // "τ did not decrease by the min required restake amount for cold {cold:?}" + // ); + + // // α minted equals the simulated swap’s net out for that same τ. + // assert_eq!( + // a_delta, expected_alpha_out.to_u64(), + // "α minted mismatch for cold {cold:?} (hot {hot1:?}) on new net (αΔ {a_delta}, expected {expected_alpha_out})" + // ); + // } + + // // Ensure V3 still functional on new net: add a small position for the first cold using its hot1 + // let who_cold = cold_lps[0]; + // let [who_hot, _] = cold_to_hots[&who_cold]; + // add_pos(net_new, who_hot, who_cold, 8, 123_456); + // assert!( + // pallet_subtensor_swap::Positions::::iter() + // .any(|((n, owner, _pid), _)| n == net_new && owner == who_cold), + // "new position not recorded on the re-registered net" + // ); + // }); } #[test] diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 4146786709..7cdfedab1a 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -6,11 +6,10 @@ use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays use frame_support::sp_runtime::DispatchError; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::RawOrigin; -use pallet_subtensor_swap::Call as SwapCall; -use pallet_subtensor_swap::tick::TickIndex; +// use pallet_subtensor_swap::Call as SwapCall; use safe_math::FixedExt; use sp_core::{Get, H256, U256}; -use sp_runtime::traits::Dispatchable; +// use sp_runtime::traits::Dispatchable; use substrate_fixed::traits::FromFixed; use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{ @@ -716,8 +715,10 @@ fn test_remove_stake_total_balance_no_change() { ); // Add subnet TAO for the equivalent amount added at price - let amount_tao = U96F32::saturating_from_num(amount) - * ::SwapInterface::current_alpha_price(netuid.into()); + let amount_tao = U96F32::from_num(amount) + * U96F32::from_num( + ::SwapInterface::current_alpha_price(netuid.into()), + ); SubnetTAO::::mutate(netuid, |v| { *v += amount_tao.saturating_to_num::().into() }); @@ -2178,8 +2179,9 @@ fn test_get_total_delegated_stake_after_unstaking() { netuid, unstake_amount_alpha.into() )); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = U96F32::from_num( + ::SwapInterface::current_alpha_price(netuid.into()), + ); // Calculate the expected delegated stake let unstake_amount = @@ -4840,126 +4842,104 @@ fn test_unstake_full_amount() { }); } -fn price_to_tick(price: f64) -> TickIndex { - let price_sqrt: U64F64 = U64F64::from_num(price.sqrt()); - // Handle potential errors in the conversion - match TickIndex::try_from_sqrt_price(price_sqrt) { - Ok(mut tick) => { - // Ensure the tick is within bounds - if tick > TickIndex::MAX { - tick = TickIndex::MAX; - } else if tick < TickIndex::MIN { - tick = TickIndex::MIN; - } - tick - } - // Default to a reasonable value when conversion fails - Err(_) => { - if price > 1.0 { - TickIndex::MAX - } else { - TickIndex::MIN - } - } - } -} - /// Test correctness of swap fees: /// 1. TAO is not minted or burned /// 2. Fees match FeeRate /// #[test] fn test_swap_fees_tao_correctness() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(1); - let owner_coldkey = U256::from(2); - let coldkey = U256::from(4); - let amount = 1_000_000_000; - let owner_balance_before = amount * 10; - let user_balance_before = amount * 100; - - // add network - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - SubtensorModule::add_balance_to_coldkey_account(&owner_coldkey, owner_balance_before); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, user_balance_before); - let fee_rate = pallet_subtensor_swap::FeeRate::::get(NetUid::from(netuid)) as f64 - / u16::MAX as f64; - pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); - - // Forse-set alpha in and tao reserve to make price equal 0.25 - let tao_reserve = TaoCurrency::from(100_000_000_000); - let alpha_in = AlphaCurrency::from(400_000_000_000); - mock::setup_reserves(netuid, tao_reserve, alpha_in); - - // Check starting "total TAO" - let total_tao_before = - user_balance_before + owner_balance_before + SubnetTAO::::get(netuid).to_u64(); - - // Get alpha for owner - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(owner_coldkey), - owner_hotkey, - netuid, - amount.into(), - )); - let mut fees = (fee_rate * amount as f64) as u64; - - // Add owner coldkey Alpha as concentrated liquidity - // between current price current price + 0.01 - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .to_num::() - + 0.0001; - let limit_price = current_price + 0.01; - let tick_low = price_to_tick(current_price); - let tick_high = price_to_tick(limit_price); - let liquidity = amount; - - assert_ok!(::SwapInterface::do_add_liquidity( - netuid.into(), - &owner_coldkey, - &owner_hotkey, - tick_low, - tick_high, - liquidity, - )); - - // Limit-buy and then sell all alpha for user to hit owner liquidity - assert_ok!(SubtensorModule::add_stake_limit( - RuntimeOrigin::signed(coldkey), - owner_hotkey, - netuid, - amount.into(), - ((limit_price * u64::MAX as f64) as u64).into(), - true - )); - fees += (fee_rate * amount as f64) as u64; - - let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &owner_hotkey, - &coldkey, - netuid, - ); - remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); - assert_ok!(SubtensorModule::remove_stake( - RuntimeOrigin::signed(coldkey), - owner_hotkey, - netuid, - user_alpha, - )); - // Do not add fees because selling fees are in alpha - - // Check ending "total TAO" - let owner_balance_after = SubtensorModule::get_coldkey_balance(&owner_coldkey); - let user_balance_after = SubtensorModule::get_coldkey_balance(&coldkey); - let total_tao_after = user_balance_after - + owner_balance_after - + SubnetTAO::::get(netuid).to_u64() - + fees; - - // Total TAO does not change, leave some epsilon for rounding - assert_abs_diff_eq!(total_tao_before, total_tao_after, epsilon = 2); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let owner_hotkey = U256::from(1); + // let owner_coldkey = U256::from(2); + // let coldkey = U256::from(4); + // let amount = 1_000_000_000; + // let owner_balance_before = amount * 10; + // let user_balance_before = amount * 100; + + // // add network + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // SubtensorModule::add_balance_to_coldkey_account(&owner_coldkey, owner_balance_before); + // SubtensorModule::add_balance_to_coldkey_account(&coldkey, user_balance_before); + // let fee_rate = pallet_subtensor_swap::FeeRate::::get(NetUid::from(netuid)) as f64 + // / u16::MAX as f64; + // pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); + + // // Forse-set alpha in and tao reserve to make price equal 0.25 + // let tao_reserve = TaoCurrency::from(100_000_000_000); + // let alpha_in = AlphaCurrency::from(400_000_000_000); + // mock::setup_reserves(netuid, tao_reserve, alpha_in); + + // // Check starting "total TAO" + // let total_tao_before = + // user_balance_before + owner_balance_before + SubnetTAO::::get(netuid).to_u64(); + + // // Get alpha for owner + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(owner_coldkey), + // owner_hotkey, + // netuid, + // amount.into(), + // )); + // let mut fees = (fee_rate * amount as f64) as u64; + + // // Add owner coldkey Alpha as concentrated liquidity + // // between current price current price + 0.01 + // let current_price = + // ::SwapInterface::current_alpha_price(netuid.into()) + // .to_num::() + // + 0.0001; + // let limit_price = current_price + 0.01; + // let tick_low = price_to_tick(current_price); + // let tick_high = price_to_tick(limit_price); + // let liquidity = amount; + + // assert_ok!(::SwapInterface::do_add_liquidity( + // netuid.into(), + // &owner_coldkey, + // &owner_hotkey, + // tick_low, + // tick_high, + // liquidity, + // )); + + // // Limit-buy and then sell all alpha for user to hit owner liquidity + // assert_ok!(SubtensorModule::add_stake_limit( + // RuntimeOrigin::signed(coldkey), + // owner_hotkey, + // netuid, + // amount.into(), + // ((limit_price * u64::MAX as f64) as u64).into(), + // true + // )); + // fees += (fee_rate * amount as f64) as u64; + + // let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &owner_hotkey, + // &coldkey, + // netuid, + // ); + // remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); + // assert_ok!(SubtensorModule::remove_stake( + // RuntimeOrigin::signed(coldkey), + // owner_hotkey, + // netuid, + // user_alpha, + // )); + // // Do not add fees because selling fees are in alpha + + // // Check ending "total TAO" + // let owner_balance_after = SubtensorModule::get_coldkey_balance(&owner_coldkey); + // let user_balance_after = SubtensorModule::get_coldkey_balance(&coldkey); + // let total_tao_after = user_balance_after + // + owner_balance_after + // + SubnetTAO::::get(netuid).to_u64() + // + fees; + + // // Total TAO does not change, leave some epsilon for rounding + // assert_abs_diff_eq!(total_tao_before, total_tao_after, epsilon = 2); + // }); } #[test] @@ -5207,206 +5187,210 @@ fn test_default_min_stake_sufficiency() { /// cargo test --package pallet-subtensor --lib -- tests::staking::test_update_position_fees --exact --show-output #[test] fn test_update_position_fees() { - // Test cases: add or remove liquidity during modification - [false, true].into_iter().for_each(|add| { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(1); - let owner_coldkey = U256::from(2); - let coldkey = U256::from(4); - let amount = 1_000_000_000; - - // add network - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - SubtensorModule::add_balance_to_coldkey_account(&owner_coldkey, amount * 10); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount * 100); - pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); - - // Forse-set alpha in and tao reserve to make price equal 0.25 - let tao_reserve = TaoCurrency::from(100_000_000_000); - let alpha_in = AlphaCurrency::from(400_000_000_000); - mock::setup_reserves(netuid, tao_reserve, alpha_in); - - // Get alpha for owner - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(owner_coldkey), - owner_hotkey, - netuid, - amount.into(), - )); - - // Add owner coldkey Alpha as concentrated liquidity - // between current price current price + 0.01 - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .to_num::() - + 0.0001; - let limit_price = current_price + 0.001; - let tick_low = price_to_tick(current_price); - let tick_high = price_to_tick(limit_price); - let liquidity = amount; - - let (position_id, _, _) = ::SwapInterface::do_add_liquidity( - NetUid::from(netuid), - &owner_coldkey, - &owner_hotkey, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - // Buy and then sell all alpha for user to hit owner liquidity - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - owner_hotkey, - netuid, - amount.into(), - )); - - remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); - - let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &owner_hotkey, - &coldkey, - netuid, - ); - assert_ok!(SubtensorModule::remove_stake( - RuntimeOrigin::signed(coldkey), - owner_hotkey, - netuid, - user_alpha, - )); - - // Modify position - fees should be collected and paid to the owner - let owner_tao_before = SubtensorModule::get_coldkey_balance(&owner_coldkey); - let owner_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &owner_hotkey, - &owner_coldkey, - netuid, - ); - - // Make small modification - let delta = - ::MinimumLiquidity::get() - as i64 - * (if add { 1 } else { -1 }); - assert_ok!(Swap::modify_position( - RuntimeOrigin::signed(owner_coldkey), - owner_hotkey, - netuid.into(), - position_id.into(), - delta, - )); - - // Check ending owner TAO and alpha - let owner_tao_after_add = SubtensorModule::get_coldkey_balance(&owner_coldkey); - let owner_alpha_after_add = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &owner_hotkey, - &owner_coldkey, - netuid, - ); - - assert!(owner_tao_after_add > owner_tao_before); - assert!(owner_alpha_after_add > owner_alpha_before); // always greater because of claimed fees - - // Make small modification again - should not claim more fees - assert_ok!(Swap::modify_position( - RuntimeOrigin::signed(owner_coldkey), - owner_hotkey, - netuid.into(), - position_id.into(), - delta, - )); - - // Check ending owner TAO and alpha - let owner_tao_after_repeat = SubtensorModule::get_coldkey_balance(&owner_coldkey); - let owner_alpha_after_repeat = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &owner_hotkey, - &owner_coldkey, - netuid, - ); - - assert!(owner_tao_after_add == owner_tao_after_repeat); - if add { - assert!(owner_alpha_after_add > owner_alpha_after_repeat); - } else { - assert!(owner_alpha_after_add < owner_alpha_after_repeat); - } - }); - }); + todo!(); + + // // Test cases: add or remove liquidity during modification + // [false, true].into_iter().for_each(|add| { + // new_test_ext(1).execute_with(|| { + // let owner_hotkey = U256::from(1); + // let owner_coldkey = U256::from(2); + // let coldkey = U256::from(4); + // let amount = 1_000_000_000; + + // // add network + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // SubtensorModule::add_balance_to_coldkey_account(&owner_coldkey, amount * 10); + // SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount * 100); + // pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); + + // // Forse-set alpha in and tao reserve to make price equal 0.25 + // let tao_reserve = TaoCurrency::from(100_000_000_000); + // let alpha_in = AlphaCurrency::from(400_000_000_000); + // mock::setup_reserves(netuid, tao_reserve, alpha_in); + + // // Get alpha for owner + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(owner_coldkey), + // owner_hotkey, + // netuid, + // amount.into(), + // )); + + // // Add owner coldkey Alpha as concentrated liquidity + // // between current price current price + 0.01 + // let current_price = + // ::SwapInterface::current_alpha_price(netuid.into()) + // .to_num::() + // + 0.0001; + // let limit_price = current_price + 0.001; + // let tick_low = price_to_tick(current_price); + // let tick_high = price_to_tick(limit_price); + // let liquidity = amount; + + // let (position_id, _, _) = ::SwapInterface::do_add_liquidity( + // NetUid::from(netuid), + // &owner_coldkey, + // &owner_hotkey, + // tick_low, + // tick_high, + // liquidity, + // ) + // .unwrap(); + + // // Buy and then sell all alpha for user to hit owner liquidity + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(coldkey), + // owner_hotkey, + // netuid, + // amount.into(), + // )); + + // remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); + + // let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &owner_hotkey, + // &coldkey, + // netuid, + // ); + // assert_ok!(SubtensorModule::remove_stake( + // RuntimeOrigin::signed(coldkey), + // owner_hotkey, + // netuid, + // user_alpha, + // )); + + // // Modify position - fees should be collected and paid to the owner + // let owner_tao_before = SubtensorModule::get_coldkey_balance(&owner_coldkey); + // let owner_alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &owner_hotkey, + // &owner_coldkey, + // netuid, + // ); + + // // Make small modification + // let delta = + // ::MinimumLiquidity::get() + // as i64 + // * (if add { 1 } else { -1 }); + // assert_ok!(Swap::modify_position( + // RuntimeOrigin::signed(owner_coldkey), + // owner_hotkey, + // netuid.into(), + // position_id.into(), + // delta, + // )); + + // // Check ending owner TAO and alpha + // let owner_tao_after_add = SubtensorModule::get_coldkey_balance(&owner_coldkey); + // let owner_alpha_after_add = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &owner_hotkey, + // &owner_coldkey, + // netuid, + // ); + + // assert!(owner_tao_after_add > owner_tao_before); + // assert!(owner_alpha_after_add > owner_alpha_before); // always greater because of claimed fees + + // // Make small modification again - should not claim more fees + // assert_ok!(Swap::modify_position( + // RuntimeOrigin::signed(owner_coldkey), + // owner_hotkey, + // netuid.into(), + // position_id.into(), + // delta, + // )); + + // // Check ending owner TAO and alpha + // let owner_tao_after_repeat = SubtensorModule::get_coldkey_balance(&owner_coldkey); + // let owner_alpha_after_repeat = + // SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &owner_hotkey, + // &owner_coldkey, + // netuid, + // ); + + // assert!(owner_tao_after_add == owner_tao_after_repeat); + // if add { + // assert!(owner_alpha_after_add > owner_alpha_after_repeat); + // } else { + // assert!(owner_alpha_after_add < owner_alpha_after_repeat); + // } + // }); + // }); } -fn setup_positions(netuid: NetUid) { - for (coldkey, hotkey, low_price, high_price, liquidity) in [ - (2, 12, 0.1, 0.20, 1_000_000_000_000_u64), - (3, 13, 0.15, 0.25, 200_000_000_000_u64), - (4, 14, 0.25, 0.5, 3_000_000_000_000_u64), - (5, 15, 0.3, 0.6, 300_000_000_000_u64), - (6, 16, 0.4, 0.7, 8_000_000_000_000_u64), - (7, 17, 0.5, 0.8, 600_000_000_000_u64), - (8, 18, 0.6, 0.9, 700_000_000_000_u64), - (9, 19, 0.7, 1.0, 100_000_000_000_u64), - (10, 20, 0.8, 1.1, 300_000_000_000_u64), - ] { - SubtensorModule::create_account_if_non_existent(&U256::from(coldkey), &U256::from(hotkey)); - SubtensorModule::add_balance_to_coldkey_account( - &U256::from(coldkey), - 1_000_000_000_000_000, - ); - SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( - &U256::from(hotkey), - &U256::from(coldkey), - netuid.into(), - 1_000_000_000_000_000.into(), - ); - - let tick_low = price_to_tick(low_price); - let tick_high = price_to_tick(high_price); - let add_lq_call = SwapCall::::add_liquidity { - hotkey: U256::from(hotkey), - netuid: netuid.into(), - tick_low, - tick_high, - liquidity, - }; - assert_ok!( - RuntimeCall::Swap(add_lq_call).dispatch(RuntimeOrigin::signed(U256::from(coldkey))) - ); - } -} +// fn setup_positions(netuid: NetUid) { +// for (coldkey, hotkey, low_price, high_price, liquidity) in [ +// (2, 12, 0.1, 0.20, 1_000_000_000_000_u64), +// (3, 13, 0.15, 0.25, 200_000_000_000_u64), +// (4, 14, 0.25, 0.5, 3_000_000_000_000_u64), +// (5, 15, 0.3, 0.6, 300_000_000_000_u64), +// (6, 16, 0.4, 0.7, 8_000_000_000_000_u64), +// (7, 17, 0.5, 0.8, 600_000_000_000_u64), +// (8, 18, 0.6, 0.9, 700_000_000_000_u64), +// (9, 19, 0.7, 1.0, 100_000_000_000_u64), +// (10, 20, 0.8, 1.1, 300_000_000_000_u64), +// ] { +// SubtensorModule::create_account_if_non_existent(&U256::from(coldkey), &U256::from(hotkey)); +// SubtensorModule::add_balance_to_coldkey_account( +// &U256::from(coldkey), +// 1_000_000_000_000_000, +// ); +// SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( +// &U256::from(hotkey), +// &U256::from(coldkey), +// netuid.into(), +// 1_000_000_000_000_000.into(), +// ); + +// let tick_low = price_to_tick(low_price); +// let tick_high = price_to_tick(high_price); +// let add_lq_call = SwapCall::::add_liquidity { +// hotkey: U256::from(hotkey), +// netuid: netuid.into(), +// tick_low, +// tick_high, +// liquidity, +// }; +// assert_ok!( +// RuntimeCall::Swap(add_lq_call).dispatch(RuntimeOrigin::signed(U256::from(coldkey))) +// ); +// } +// } #[test] fn test_large_swap() { - new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(1); - let owner_coldkey = U256::from(2); - let coldkey = U256::from(100); - - // add network - let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); - pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); - - // Force the swap to initialize - SubtensorModule::swap_tao_for_alpha( - netuid, - TaoCurrency::ZERO, - 1_000_000_000_000.into(), - false, - ) - .unwrap(); - - setup_positions(netuid.into()); - - let swap_amount = TaoCurrency::from(100_000_000_000_000); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - owner_hotkey, - netuid, - swap_amount, - )); - }); + todo!(); + + // new_test_ext(1).execute_with(|| { + // let owner_hotkey = U256::from(1); + // let owner_coldkey = U256::from(2); + // let coldkey = U256::from(100); + + // // add network + // let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + // SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); + // pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); + + // // Force the swap to initialize + // SubtensorModule::swap_tao_for_alpha( + // netuid, + // TaoCurrency::ZERO, + // 1_000_000_000_000.into(), + // false, + // ) + // .unwrap(); + + // setup_positions(netuid.into()); + + // let swap_amount = TaoCurrency::from(100_000_000_000_000); + // assert_ok!(SubtensorModule::add_stake( + // RuntimeOrigin::signed(coldkey), + // owner_hotkey, + // netuid, + // swap_amount, + // )); + // }); } #[test] diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 19af1303c1..c99db6a475 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -2,7 +2,7 @@ use core::ops::Neg; use frame_support::pallet_prelude::*; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::U64F64; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; @@ -38,7 +38,7 @@ pub trait SwapHandler { Self: SwapEngine; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; - fn current_alpha_price(netuid: NetUid) -> U96F32; + fn current_alpha_price(netuid: NetUid) -> U64F64; fn get_protocol_tao(netuid: NetUid) -> TaoCurrency; fn max_price() -> C; fn min_price() -> C; diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index 1576283fd5..fc3f2acefe 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -11,7 +11,7 @@ pub trait Order: Clone { fn with_amount(amount: impl Into) -> Self; fn amount(&self) -> Self::PaidIn; - fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool; + fn is_beyond_price_limit(&self, current_price: U64F64, limit_price: U64F64) -> bool; } #[derive(Clone, Default)] @@ -45,8 +45,8 @@ where self.amount } - fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { - alpha_sqrt_price < limit_sqrt_price + fn is_beyond_price_limit(&self, current_price: U64F64, limit_price: U64F64) -> bool { + current_price < limit_price } } @@ -81,7 +81,7 @@ where self.amount } - fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { - alpha_sqrt_price > limit_sqrt_price + fn is_beyond_price_limit(&self, current_price: U64F64, limit_price: U64F64) -> bool { + current_price > limit_price } } diff --git a/pallets/swap/Cargo.toml b/pallets/swap/Cargo.toml index 7de8e49c1d..89534fa81c 100644 --- a/pallets/swap/Cargo.toml +++ b/pallets/swap/Cargo.toml @@ -12,6 +12,7 @@ frame-support.workspace = true frame-system.workspace = true log.workspace = true safe-math.workspace = true +safe-bigmath.workspace = true scale-info = { workspace = true, features = ["derive"] } serde = { workspace = true, optional = true } sp-arithmetic.workspace = true @@ -28,6 +29,8 @@ subtensor-swap-interface.workspace = true [dev-dependencies] sp-tracing.workspace = true +rand = "0.8" +rayon = "1.10" [lints] workspace = true diff --git a/pallets/swap/src/benchmarking.rs b/pallets/swap/src/benchmarking.rs index 66ff88fd31..1ff029c281 100644 --- a/pallets/swap/src/benchmarking.rs +++ b/pallets/swap/src/benchmarking.rs @@ -3,22 +3,19 @@ #![allow(clippy::multiple_bound_locations)] use core::marker::PhantomData; - use frame_benchmarking::v2::*; -use frame_support::traits::Get; use frame_system::RawOrigin; -use substrate_fixed::types::{I64F64, U64F64}; use subtensor_runtime_common::NetUid; use crate::{ - pallet::{ - AlphaSqrtPrice, Call, Config, CurrentLiquidity, CurrentTick, Pallet, Positions, - SwapV3Initialized, - }, + pallet::{Call, Config, Pallet, PositionsV2}, position::{Position, PositionId}, - tick::TickIndex, }; +fn init_swap(netuid: NetUid) { + let _ = Pallet::::maybe_initialize_palswap(netuid); +} + #[benchmarks(where T: Config)] mod benchmarks { use super::*; @@ -36,54 +33,33 @@ mod benchmarks { fn add_liquidity() { let netuid = NetUid::from(1); - if !SwapV3Initialized::::get(netuid) { - SwapV3Initialized::::insert(netuid, true); - AlphaSqrtPrice::::insert(netuid, U64F64::from_num(1)); - CurrentTick::::insert(netuid, TickIndex::new(0).unwrap()); - CurrentLiquidity::::insert(netuid, T::MinimumLiquidity::get()); - } + init_swap::(netuid); let caller: T::AccountId = whitelisted_caller(); let hotkey: T::AccountId = account("hotkey", 0, 0); - let tick_low = TickIndex::new_unchecked(-1000); - let tick_high = TickIndex::new_unchecked(1000); #[extrinsic_call] - add_liquidity( - RawOrigin::Signed(caller), - hotkey, - netuid, - tick_low, - tick_high, - 1000, - ); + add_liquidity(RawOrigin::Signed(caller), hotkey, netuid, 1000); } #[benchmark] fn remove_liquidity() { let netuid = NetUid::from(1); - if !SwapV3Initialized::::get(netuid) { - SwapV3Initialized::::insert(netuid, true); - AlphaSqrtPrice::::insert(netuid, U64F64::from_num(1)); - CurrentTick::::insert(netuid, TickIndex::new(0).unwrap()); - CurrentLiquidity::::insert(netuid, T::MinimumLiquidity::get()); - } + init_swap::(netuid); let caller: T::AccountId = whitelisted_caller(); let hotkey: T::AccountId = account("hotkey", 0, 0); let id = PositionId::from(1u128); - Positions::::insert( + PositionsV2::::insert( (netuid, caller.clone(), id), Position { id, netuid, - tick_low: TickIndex::new(-10000).unwrap(), - tick_high: TickIndex::new(10000).unwrap(), - liquidity: 1000, - fees_tao: I64F64::from_num(0), - fees_alpha: I64F64::from_num(0), + // liquidity: 1000, + // fees_tao: I64F64::from_num(0), + // fees_alpha: I64F64::from_num(0), _phantom: PhantomData, }, ); @@ -96,27 +72,20 @@ mod benchmarks { fn modify_position() { let netuid = NetUid::from(1); - if !SwapV3Initialized::::get(netuid) { - SwapV3Initialized::::insert(netuid, true); - AlphaSqrtPrice::::insert(netuid, U64F64::from_num(1)); - CurrentTick::::insert(netuid, TickIndex::new(0).unwrap()); - CurrentLiquidity::::insert(netuid, T::MinimumLiquidity::get()); - } + init_swap::(netuid); let caller: T::AccountId = whitelisted_caller(); let hotkey: T::AccountId = account("hotkey", 0, 0); let id = PositionId::from(1u128); - Positions::::insert( + PositionsV2::::insert( (netuid, caller.clone(), id), Position { id, netuid, - tick_low: TickIndex::new(-10000).unwrap(), - tick_high: TickIndex::new(10000).unwrap(), - liquidity: 10000, - fees_tao: I64F64::from_num(0), - fees_alpha: I64F64::from_num(0), + // liquidity: 10000, + // fees_tao: I64F64::from_num(0), + // fees_alpha: I64F64::from_num(0), _phantom: PhantomData, }, ); diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index 6257df852b..6ce2d20e7e 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -1,10 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use substrate_fixed::types::U64F64; - pub mod pallet; pub mod position; -pub mod tick; pub mod weights; pub use pallet::*; @@ -14,5 +11,3 @@ pub mod benchmarking; #[cfg(test)] pub(crate) mod mock; - -type SqrtPrice = U64F64; diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index aacdf90835..fed68195f8 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -14,13 +14,20 @@ use sp_runtime::{ BuildStorage, Vec, traits::{BlakeTwo256, IdentityLookup}, }; -use substrate_fixed::types::U64F64; +use std::{cell::RefCell, collections::HashMap}; +// use substrate_fixed::types::U64F64; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, + BalanceOps, + // Currency, + CurrencyReserve, + NetUid, + SubnetInfo, + TaoCurrency, }; use subtensor_swap_interface::Order; -use crate::pallet::{EnabledUserLiquidity, FeeGlobalAlpha, FeeGlobalTao}; +use crate::pallet::EnabledUserLiquidity; construct_runtime!( pub enum Test { @@ -87,11 +94,34 @@ parameter_types! { pub const MinimumReserves: NonZeroU64 = NonZeroU64::new(1).unwrap(); } +thread_local! { + // maps netuid -> mocked tao reserve + static MOCK_TAO_RESERVES: RefCell> = + RefCell::new(HashMap::new()); + // maps netuid -> mocked alpha reserve + static MOCK_ALPHA_RESERVES: RefCell> = + RefCell::new(HashMap::new()); +} + #[derive(Clone)] pub struct TaoReserve; +impl TaoReserve { + pub fn set_mock_reserve(netuid: NetUid, value: TaoCurrency) { + MOCK_TAO_RESERVES.with(|m| { + m.borrow_mut().insert(netuid, value); + }); + } +} + impl CurrencyReserve for TaoReserve { fn reserve(netuid: NetUid) -> TaoCurrency { + // If test has set an override, use it + if let Some(val) = MOCK_TAO_RESERVES.with(|m| m.borrow().get(&netuid).cloned()) { + return val; + } + + // Otherwise, fall back to our defaults match netuid.into() { 123u16 => 10_000, WRAPPING_FEES_NETUID => 100_000_000_000, @@ -107,8 +137,22 @@ impl CurrencyReserve for TaoReserve { #[derive(Clone)] pub struct AlphaReserve; +impl AlphaReserve { + pub fn set_mock_reserve(netuid: NetUid, value: AlphaCurrency) { + MOCK_ALPHA_RESERVES.with(|m| { + m.borrow_mut().insert(netuid, value); + }); + } +} + impl CurrencyReserve for AlphaReserve { fn reserve(netuid: NetUid) -> AlphaCurrency { + // If test has set an override, use it + if let Some(val) = MOCK_ALPHA_RESERVES.with(|m| m.borrow().get(&netuid).cloned()) { + return val; + } + + // Otherwise, fall back to our defaults match netuid.into() { 123u16 => 10_000.into(), WRAPPING_FEES_NETUID => 400_000_000_000.into(), @@ -123,22 +167,7 @@ impl CurrencyReserve for AlphaReserve { pub type GetAlphaForTao = subtensor_swap_interface::GetAlphaForTao; pub type GetTaoForAlpha = subtensor_swap_interface::GetTaoForAlpha; -pub(crate) trait GlobalFeeInfo: Currency { - fn global_fee(&self, netuid: NetUid) -> U64F64; -} - -impl GlobalFeeInfo for TaoCurrency { - fn global_fee(&self, netuid: NetUid) -> U64F64 { - FeeGlobalTao::::get(netuid) - } -} - -impl GlobalFeeInfo for AlphaCurrency { - fn global_fee(&self, netuid: NetUid) -> U64F64 { - FeeGlobalAlpha::::get(netuid) - } -} - +#[allow(dead_code)] pub(crate) trait TestExt { fn approx_expected_swap_output( sqrt_current_price: f64, diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 6ec02879bf..0b36aee691 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1,28 +1,31 @@ -use core::ops::Neg; - use frame_support::storage::{TransactionOutcome, transactional}; use frame_support::{ensure, pallet_prelude::DispatchError, traits::Get}; use safe_math::*; -use sp_arithmetic::helpers_128bit; -use sp_runtime::{DispatchResult, Vec, traits::AccountIdConversion}; -use substrate_fixed::types::{I64F64, U64F64, U96F32}; +use sp_arithmetic::{ + //helpers_128bit, + Perquintill, +}; +use sp_runtime::{DispatchResult, traits::AccountIdConversion}; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, + // BalanceOps, + Currency, + CurrencyReserve, + NetUid, + SubnetInfo, + TaoCurrency, }; use subtensor_swap_interface::{ DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, }; use super::pallet::*; -use super::swap_step::{BasicSwapStep, SwapStep, SwapStepAction}; +use super::swap_step::{BasicSwapStep, SwapStep}; use crate::{ - SqrtPrice, - position::{Position, PositionId}, - tick::{ActiveTickIndexManager, Tick, TickIndex}, + pallet::ReserveWeight, pallet::reserve_weights::ReserveWeightError, position::PositionId, }; -const MAX_SWAP_ITERATIONS: u16 = 1000; - #[derive(Debug, PartialEq)] pub struct UpdateLiquidityResult { pub tao: TaoCurrency, @@ -30,8 +33,6 @@ pub struct UpdateLiquidityResult { pub fee_tao: TaoCurrency, pub fee_alpha: AlphaCurrency, pub removed: bool, - pub tick_low: TickIndex, - pub tick_high: TickIndex, } #[derive(Debug, PartialEq)] @@ -40,176 +41,99 @@ pub struct RemoveLiquidityResult { pub alpha: AlphaCurrency, pub fee_tao: TaoCurrency, pub fee_alpha: AlphaCurrency, - pub tick_low: TickIndex, - pub tick_high: TickIndex, pub liquidity: u64, } impl Pallet { - pub fn current_price(netuid: NetUid) -> U96F32 { + pub fn current_price(netuid: NetUid) -> U64F64 { match T::SubnetInfo::mechanism(netuid.into()) { 1 => { - if SwapV3Initialized::::get(netuid) { - let sqrt_price = AlphaSqrtPrice::::get(netuid); - U96F32::saturating_from_num(sqrt_price.saturating_mul(sqrt_price)) - } else { + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + if !alpha_reserve.is_zero() { let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - if !alpha_reserve.is_zero() { - U96F32::saturating_from_num(tao_reserve) - .saturating_div(U96F32::saturating_from_num(alpha_reserve)) - } else { - U96F32::saturating_from_num(0) - } + let reserve_weight = SwapReserveWeight::::get(netuid); + reserve_weight.calculate_price(alpha_reserve.into(), tao_reserve.into()) + } else { + U64F64::saturating_from_num(0) } } - _ => U96F32::saturating_from_num(1), + _ => U64F64::saturating_from_num(1), } } - // initializes V3 swap for a subnet if needed - pub fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { - if SwapV3Initialized::::get(netuid) { + // initializes pal-swap (balancer) for a subnet if needed + pub fn maybe_initialize_palswap(netuid: NetUid) -> Result<(), Error> { + if PalSwapInitialized::::get(netuid) { return Ok(()); } - // Initialize the v3: - // Reserves are re-purposed, nothing to set, just query values for liquidity and price - // calculation - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - - // Set price - let price = U64F64::saturating_from_num(tao_reserve) - .safe_div(U64F64::saturating_from_num(alpha_reserve)); - - let epsilon = U64F64::saturating_from_num(0.000000000001); - - let current_sqrt_price = price.checked_sqrt(epsilon).unwrap_or(U64F64::from_num(0)); - AlphaSqrtPrice::::set(netuid, current_sqrt_price); + // Insert 0.5 into SwapReserveWeight + let reserve_weight = ReserveWeight::new(Perquintill::from_rational(1_u64, 2_u64)).map_err( + |err| match err { + ReserveWeightError::InvalidValue => Error::::ReservesOutOfBalance, + }, + )?; + SwapReserveWeight::::insert(netuid, reserve_weight); - // Set current tick - let current_tick = TickIndex::from_sqrt_price_bounded(current_sqrt_price); - CurrentTick::::set(netuid, current_tick); + // TODO: Review when/if we have user liquidity + // Initialize the pal-swap: + // Reserves are re-purposed, nothing to set, just query values for creation + // of protocol position + // let tao_reserve = T::TaoReserve::reserve(netuid.into()); + // let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); // Set initial (protocol owned) liquidity and positions - // Protocol liquidity makes one position from TickIndex::MIN to TickIndex::MAX + // Protocol liquidity makes one position // We are using the sp_arithmetic sqrt here, which works for u128 - let liquidity = helpers_128bit::sqrt( - (tao_reserve.to_u64() as u128).saturating_mul(alpha_reserve.to_u64() as u128), - ) as u64; - let protocol_account_id = Self::protocol_account_id(); + // let liquidity = helpers_128bit::sqrt( + // (tao_reserve.to_u64() as u128).saturating_mul(alpha_reserve.to_u64() as u128), + // ) as u64; + // let protocol_account_id = Self::protocol_account_id(); - let (position, _, _) = Self::add_liquidity_not_insert( - netuid, - &protocol_account_id, - TickIndex::MIN, - TickIndex::MAX, - liquidity, - )?; + // let (position, _, _) = Self::add_liquidity_not_insert( + // netuid, + // &protocol_account_id, + // TickIndex::MIN, + // TickIndex::MAX, + // liquidity, + // )?; - Positions::::insert(&(netuid, protocol_account_id, position.id), position); + // Positions::::insert(&(netuid, protocol_account_id, position.id), position); Ok(()) } - pub(crate) fn get_proportional_alpha_tao_and_remainders( - sqrt_alpha_price: U64F64, - amount_tao: TaoCurrency, - amount_alpha: AlphaCurrency, - ) -> (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency) { - let price = sqrt_alpha_price.saturating_mul(sqrt_alpha_price); - let tao_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_alpha)) - .saturating_mul(price) - .saturating_to_num(); - let amount_tao_u64 = u64::from(amount_tao); - - if tao_equivalent <= amount_tao_u64 { - // Too much or just enough TAO - ( - tao_equivalent.into(), - amount_alpha, - amount_tao.saturating_sub(TaoCurrency::from(tao_equivalent)), - 0.into(), - ) - } else { - // Too much Alpha - let alpha_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_tao)) - .safe_div(price) - .saturating_to_num(); - ( - amount_tao, - alpha_equivalent.into(), - 0.into(), - u64::from(amount_alpha) - .saturating_sub(alpha_equivalent) - .into(), - ) - } - } - /// Adjusts protocol liquidity with new values of TAO and Alpha reserve pub(super) fn adjust_protocol_liquidity( netuid: NetUid, tao_delta: TaoCurrency, alpha_delta: AlphaCurrency, ) { - // Update protocol position with new liquidity - let protocol_account_id = Self::protocol_account_id(); - let mut positions = - Positions::::iter_prefix_values((netuid, protocol_account_id.clone())) - .collect::>(); - - if let Some(position) = positions.get_mut(0) { - // Claim protocol fees and add them to liquidity - let (tao_fees, alpha_fees) = position.collect_fees(); - - // Add fee reservoirs and get proportional amounts - let current_sqrt_price = AlphaSqrtPrice::::get(netuid); - let tao_reservoir = ScrapReservoirTao::::get(netuid); - let alpha_reservoir = ScrapReservoirAlpha::::get(netuid); - let (corrected_tao_delta, corrected_alpha_delta, tao_scrap, alpha_scrap) = - Self::get_proportional_alpha_tao_and_remainders( - current_sqrt_price, - tao_delta - .saturating_add(TaoCurrency::from(tao_fees)) - .saturating_add(tao_reservoir), - alpha_delta - .saturating_add(AlphaCurrency::from(alpha_fees)) - .saturating_add(alpha_reservoir), - ); - - // Update scrap reservoirs - ScrapReservoirTao::::insert(netuid, tao_scrap); - ScrapReservoirAlpha::::insert(netuid, alpha_scrap); - - // Adjust liquidity - let maybe_token_amounts = position.to_token_amounts(current_sqrt_price); - if let Ok((tao, alpha)) = maybe_token_amounts { - // Get updated reserves, calculate liquidity - let new_tao_reserve = tao.saturating_add(corrected_tao_delta.to_u64()); - let new_alpha_reserve = alpha.saturating_add(corrected_alpha_delta.to_u64()); - let new_liquidity = helpers_128bit::sqrt( - (new_tao_reserve as u128).saturating_mul(new_alpha_reserve as u128), - ) as u64; - let liquidity_delta = new_liquidity.saturating_sub(position.liquidity); - - // Update current liquidity - CurrentLiquidity::::mutate(netuid, |current_liquidity| { - *current_liquidity = current_liquidity.saturating_add(liquidity_delta); - }); - - // Update protocol position - position.liquidity = new_liquidity; - Positions::::insert( - (netuid, protocol_account_id, position.id), - position.clone(), - ); - - // Update position ticks - Self::add_liquidity_at_index(netuid, position.tick_low, liquidity_delta, false); - Self::add_liquidity_at_index(netuid, position.tick_high, liquidity_delta, true); - } + // Get reserves + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let mut reserve_weight = SwapReserveWeight::::get(netuid); + + // Update weights and log errors if they go out of range + if reserve_weight + .update_weights_for_added_liquidity( + u64::from(tao_reserve), + u64::from(alpha_reserve), + u64::from(tao_delta), + u64::from(alpha_delta), + ) + .is_err() + { + log::error!( + "Reserves are out of range for emission: netuid = {}, tao = {}, alpha = {}, tao_delta = {}, alpha_delta = {}", + netuid, + tao_reserve, + alpha_reserve, + tao_delta, + alpha_delta + ); + } else { + SwapReserveWeight::::insert(netuid, reserve_weight); } } @@ -240,7 +164,7 @@ impl Pallet { pub(crate) fn do_swap( netuid: NetUid, order: Order, - limit_sqrt_price: SqrtPrice, + limit_price: U64F64, drop_fees: bool, simulate: bool, ) -> Result, DispatchError> @@ -251,7 +175,7 @@ impl Pallet { transactional::with_transaction(|| { let reserve = Order::ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::(netuid, order, limit_sqrt_price, drop_fees) + let result = Self::swap_inner::(netuid, order, limit_price, drop_fees) .map_err(Into::into); if simulate || result.is_err() { @@ -277,7 +201,7 @@ impl Pallet { fn swap_inner( netuid: NetUid, order: Order, - limit_sqrt_price: SqrtPrice, + limit_price: U64F64, drop_fees: bool, ) -> Result, Error> where @@ -289,70 +213,39 @@ impl Pallet { Error::::ReservesTooLow ); - Self::maybe_initialize_v3(netuid)?; + Self::maybe_initialize_palswap(netuid)?; + + println!("Self::current_price(netuid) = {:?}", Self::current_price(netuid)); + println!("limit_price = {:?}", limit_price); // Because user specifies the limit price, check that it is in fact beoynd the current one ensure!( - order.is_beyond_price_limit(AlphaSqrtPrice::::get(netuid), limit_sqrt_price), + order.is_beyond_price_limit(Self::current_price(netuid), limit_price), Error::::PriceLimitExceeded ); - let mut amount_remaining = order.amount(); - let mut amount_paid_out = Order::PaidOut::ZERO; - let mut iteration_counter: u16 = 0; - let mut in_acc = Order::PaidIn::ZERO; - let mut fee_acc = Order::PaidIn::ZERO; - log::trace!("======== Start Swap ========"); - log::trace!("Amount Remaining: {amount_remaining}"); - - // Swap one tick at a time until we reach one of the stop conditions - while !amount_remaining.is_zero() { - log::trace!("\nIteration: {iteration_counter}"); - log::trace!( - "\tCurrent Liquidity: {}", - CurrentLiquidity::::get(netuid) - ); - - // Create and execute a swap step - let mut swap_step = BasicSwapStep::::new( - netuid, - amount_remaining, - limit_sqrt_price, - drop_fees, - ); - - let swap_result = swap_step.execute()?; - - in_acc = in_acc.saturating_add(swap_result.delta_in); - fee_acc = fee_acc.saturating_add(swap_result.fee_paid); - amount_remaining = amount_remaining.saturating_sub(swap_result.amount_to_take); - amount_paid_out = amount_paid_out.saturating_add(swap_result.delta_out); - - if swap_step.action() == SwapStepAction::Stop { - amount_remaining = Order::PaidIn::ZERO; - } + let amount_to_swap = order.amount(); + log::trace!("Amount to swap: {amount_to_swap}"); - // The swap step didn't exchange anything - if swap_result.amount_to_take.is_zero() { - amount_remaining = Order::PaidIn::ZERO; - } - - iteration_counter = iteration_counter.saturating_add(1); + // Create and execute a swap step + let mut swap_step = BasicSwapStep::::new( + netuid, + amount_to_swap, + limit_price, + drop_fees, + ); - ensure!( - iteration_counter <= MAX_SWAP_ITERATIONS, - Error::::TooManySwapSteps - ); - } + let swap_result = swap_step.execute()?; - log::trace!("\nAmount Paid Out: {amount_paid_out}"); + log::trace!("Delta out: {}", swap_result.delta_out); + log::trace!("Fees: {}", swap_result.fee_paid); log::trace!("======== End Swap ========"); Ok(SwapResult { - amount_paid_in: in_acc, - amount_paid_out, - fee_paid: fee_acc, + amount_paid_in: swap_result.delta_in, + amount_paid_out: swap_result.delta_out, + fee_paid: swap_result.fee_paid, }) } @@ -381,23 +274,6 @@ impl Pallet { } } - pub fn find_closest_lower_active_tick(netuid: NetUid, index: TickIndex) -> Option { - ActiveTickIndexManager::::find_closest_lower(netuid, index) - .and_then(|ti| Ticks::::get(netuid, ti)) - } - - pub fn find_closest_higher_active_tick(netuid: NetUid, index: TickIndex) -> Option { - ActiveTickIndexManager::::find_closest_higher(netuid, index) - .and_then(|ti| Ticks::::get(netuid, ti)) - } - - /// Here we subtract minimum safe liquidity from current liquidity to stay in the safe range - pub(crate) fn current_liquidity_safe(netuid: NetUid) -> U64F64 { - U64F64::saturating_from_num( - CurrentLiquidity::::get(netuid).saturating_sub(T::MinimumLiquidity::get()), - ) - } - /// Adds liquidity to the specified price range. /// /// This function allows an account to provide liquidity to a given range of price ticks. The @@ -429,86 +305,16 @@ impl Pallet { /// - Other [`SwapError`] variants as applicable. pub fn do_add_liquidity( netuid: NetUid, - coldkey_account_id: &T::AccountId, - hotkey_account_id: &T::AccountId, - tick_low: TickIndex, - tick_high: TickIndex, - liquidity: u64, + _coldkey_account_id: &T::AccountId, + _hotkey_account_id: &T::AccountId, + _liquidity: u64, ) -> Result<(PositionId, u64, u64), Error> { ensure!( EnabledUserLiquidity::::get(netuid), Error::::UserLiquidityDisabled ); - let (position, tao, alpha) = Self::add_liquidity_not_insert( - netuid, - coldkey_account_id, - tick_low, - tick_high, - liquidity, - )?; - let position_id = position.id; - - ensure!( - T::BalanceOps::tao_balance(coldkey_account_id) >= TaoCurrency::from(tao) - && T::BalanceOps::alpha_balance( - netuid.into(), - coldkey_account_id, - hotkey_account_id - ) >= AlphaCurrency::from(alpha), - Error::::InsufficientBalance - ); - - // Small delta is not allowed - ensure!( - liquidity >= T::MinimumLiquidity::get(), - Error::::InvalidLiquidityValue - ); - - Positions::::insert(&(netuid, coldkey_account_id, position.id), position); - - Ok((position_id, tao, alpha)) - } - - // add liquidity without inserting position into storage (used privately for v3 intiialization). - // unlike Self::add_liquidity it also doesn't perform account's balance check. - // - // the public interface is [`Self::add_liquidity`] - fn add_liquidity_not_insert( - netuid: NetUid, - coldkey_account_id: &T::AccountId, - tick_low: TickIndex, - tick_high: TickIndex, - liquidity: u64, - ) -> Result<(Position, u64, u64), Error> { - ensure!( - Self::count_positions(netuid, coldkey_account_id) < T::MaxPositions::get() as usize, - Error::::MaxPositionsExceeded - ); - - // Ensure that tick_high is actually higher than tick_low - ensure!(tick_high > tick_low, Error::::InvalidTickRange); - - // Add liquidity at tick - Self::add_liquidity_at_index(netuid, tick_low, liquidity, false); - Self::add_liquidity_at_index(netuid, tick_high, liquidity, true); - - // Update current tick liquidity - let current_tick_index = TickIndex::current_bounded::(netuid); - Self::clamp_sqrt_price(netuid, current_tick_index); - - Self::update_liquidity_if_needed(netuid, tick_low, tick_high, liquidity as i128); - - // New position - let position_id = PositionId::new::(); - let position = Position::new(position_id, netuid, tick_low, tick_high, liquidity); - - let current_price_sqrt = AlphaSqrtPrice::::get(netuid); - let (tao, alpha) = position.to_token_amounts(current_price_sqrt)?; - - SwapV3Initialized::::set(netuid, true); - - Ok((position, tao, alpha)) + todo!(); } /// Remove liquidity and credit balances back to (coldkey_account_id, hotkey_account_id) stake. @@ -516,290 +322,46 @@ impl Pallet { /// /// Account ID and Position ID identify position in the storage map pub fn do_remove_liquidity( - netuid: NetUid, - coldkey_account_id: &T::AccountId, - position_id: PositionId, + _netuid: NetUid, + _coldkey_account_id: &T::AccountId, + _position_id: PositionId, ) -> Result> { - let Some(mut position) = Positions::::get((netuid, coldkey_account_id, position_id)) - else { - return Err(Error::::LiquidityNotFound); - }; - - // Collect fees and get tao and alpha amounts - let (fee_tao, fee_alpha) = position.collect_fees(); - let current_price = AlphaSqrtPrice::::get(netuid); - let (tao, alpha) = position.to_token_amounts(current_price)?; - - // Update liquidity at position ticks - Self::remove_liquidity_at_index(netuid, position.tick_low, position.liquidity, false); - Self::remove_liquidity_at_index(netuid, position.tick_high, position.liquidity, true); - - // Update current tick liquidity - Self::update_liquidity_if_needed( - netuid, - position.tick_low, - position.tick_high, - (position.liquidity as i128).neg(), - ); - - // Remove user position - Positions::::remove((netuid, coldkey_account_id, position_id)); - - Ok(RemoveLiquidityResult { - tao: tao.into(), - alpha: alpha.into(), - fee_tao: fee_tao.into(), - fee_alpha: fee_alpha.into(), - tick_low: position.tick_low, - tick_high: position.tick_high, - liquidity: position.liquidity, - }) + todo!(); } pub fn do_modify_position( netuid: NetUid, - coldkey_account_id: &T::AccountId, - hotkey_account_id: &T::AccountId, - position_id: PositionId, - liquidity_delta: i64, + _coldkey_account_id: &T::AccountId, + _hotkey_account_id: &T::AccountId, + _position_id: PositionId, + _liquidity_delta: i64, ) -> Result> { ensure!( EnabledUserLiquidity::::get(netuid), Error::::UserLiquidityDisabled ); - // Find the position - let Some(mut position) = Positions::::get((netuid, coldkey_account_id, position_id)) - else { - return Err(Error::::LiquidityNotFound); - }; - - // Small delta is not allowed - ensure!( - liquidity_delta.abs() >= T::MinimumLiquidity::get() as i64, - Error::::InvalidLiquidityValue - ); - let mut delta_liquidity_abs = liquidity_delta.unsigned_abs(); - - // Determine the effective price for token calculations - let current_price_sqrt = AlphaSqrtPrice::::get(netuid); - let sqrt_pa: SqrtPrice = position - .tick_low - .try_to_sqrt_price() - .map_err(|_| Error::::InvalidTickRange)?; - let sqrt_pb: SqrtPrice = position - .tick_high - .try_to_sqrt_price() - .map_err(|_| Error::::InvalidTickRange)?; - let sqrt_price_box = if current_price_sqrt < sqrt_pa { - sqrt_pa - } else if current_price_sqrt > sqrt_pb { - sqrt_pb - } else { - // Update current liquidity if price is in range - let new_liquidity_curr = if liquidity_delta > 0 { - CurrentLiquidity::::get(netuid).saturating_add(delta_liquidity_abs) - } else { - CurrentLiquidity::::get(netuid).saturating_sub(delta_liquidity_abs) - }; - CurrentLiquidity::::set(netuid, new_liquidity_curr); - current_price_sqrt - }; - - // Calculate token amounts for the liquidity change - let mul = SqrtPrice::from_num(1) - .safe_div(sqrt_price_box) - .saturating_sub(SqrtPrice::from_num(1).safe_div(sqrt_pb)); - let alpha = SqrtPrice::saturating_from_num(delta_liquidity_abs).saturating_mul(mul); - let tao = SqrtPrice::saturating_from_num(delta_liquidity_abs) - .saturating_mul(sqrt_price_box.saturating_sub(sqrt_pa)); - - // Validate delta - if liquidity_delta > 0 { - // Check that user has enough balances - ensure!( - T::BalanceOps::tao_balance(coldkey_account_id) - >= TaoCurrency::from(tao.saturating_to_num::()) - && T::BalanceOps::alpha_balance(netuid, coldkey_account_id, hotkey_account_id) - >= AlphaCurrency::from(alpha.saturating_to_num::()), - Error::::InsufficientBalance - ); - } else { - // Check that position has enough liquidity - ensure!( - position.liquidity >= delta_liquidity_abs, - Error::::InsufficientLiquidity - ); - } - - // Collect fees - let (fee_tao, fee_alpha) = position.collect_fees(); - - // If delta brings the position liquidity below MinimumLiquidity, eliminate position and - // withdraw full amounts - let mut remove = false; - if (liquidity_delta < 0) - && (position.liquidity.saturating_sub(delta_liquidity_abs) < T::MinimumLiquidity::get()) - { - delta_liquidity_abs = position.liquidity; - remove = true; - } - - // Adjust liquidity at the ticks based on the delta sign - if liquidity_delta > 0 { - // Add liquidity at tick - Self::add_liquidity_at_index(netuid, position.tick_low, delta_liquidity_abs, false); - Self::add_liquidity_at_index(netuid, position.tick_high, delta_liquidity_abs, true); - - // Add liquidity to user position - position.liquidity = position.liquidity.saturating_add(delta_liquidity_abs); - } else { - // Remove liquidity at tick - Self::remove_liquidity_at_index(netuid, position.tick_low, delta_liquidity_abs, false); - Self::remove_liquidity_at_index(netuid, position.tick_high, delta_liquidity_abs, true); - - // Remove liquidity from user position - position.liquidity = position.liquidity.saturating_sub(delta_liquidity_abs); - } - - // Update or, in case if full liquidity is removed, remove the position - if remove { - Positions::::remove((netuid, coldkey_account_id, position_id)); - } else { - Positions::::insert(&(netuid, coldkey_account_id, position.id), position.clone()); - } - - Ok(UpdateLiquidityResult { - tao: tao.saturating_to_num::().into(), - alpha: alpha.saturating_to_num::().into(), - fee_tao: fee_tao.into(), - fee_alpha: fee_alpha.into(), - removed: remove, - tick_low: position.tick_low, - tick_high: position.tick_high, - }) + todo!(); } - /// Adds or updates liquidity at a specific tick index for a subnet - /// - /// # Arguments - /// * `netuid` - The subnet ID - /// * `tick_index` - The tick index to add liquidity to - /// * `liquidity` - The amount of liquidity to add - fn add_liquidity_at_index(netuid: NetUid, tick_index: TickIndex, liquidity: u64, upper: bool) { - // Convert liquidity to signed value, negating it for upper bounds - let net_liquidity_change = if upper { - (liquidity as i128).neg() - } else { - liquidity as i128 - }; - - Ticks::::mutate(netuid, tick_index, |maybe_tick| match maybe_tick { - Some(tick) => { - tick.liquidity_net = tick.liquidity_net.saturating_add(net_liquidity_change); - tick.liquidity_gross = tick.liquidity_gross.saturating_add(liquidity); - } - None => { - let current_tick = TickIndex::current_bounded::(netuid); - - let (fees_out_tao, fees_out_alpha) = if tick_index > current_tick { - ( - I64F64::saturating_from_num(FeeGlobalTao::::get(netuid)), - I64F64::saturating_from_num(FeeGlobalAlpha::::get(netuid)), - ) - } else { - ( - I64F64::saturating_from_num(0), - I64F64::saturating_from_num(0), - ) - }; - *maybe_tick = Some(Tick { - liquidity_net: net_liquidity_change, - liquidity_gross: liquidity, - fees_out_tao, - fees_out_alpha, - }); - } - }); - - // Update active ticks - ActiveTickIndexManager::::insert(netuid, tick_index); - } - - /// Remove liquidity at tick index. - fn remove_liquidity_at_index( - netuid: NetUid, - tick_index: TickIndex, - liquidity: u64, - upper: bool, - ) { - // Calculate net liquidity addition - let net_reduction = if upper { - (liquidity as i128).neg() - } else { - liquidity as i128 - }; - - Ticks::::mutate_exists(netuid, tick_index, |maybe_tick| { - if let Some(tick) = maybe_tick { - tick.liquidity_net = tick.liquidity_net.saturating_sub(net_reduction); - tick.liquidity_gross = tick.liquidity_gross.saturating_sub(liquidity); - - // If no liquidity is left at the tick, remove it - if tick.liquidity_gross == 0 { - *maybe_tick = None; - - // Update active ticks: Final liquidity is zero, remove this tick from active. - ActiveTickIndexManager::::remove(netuid, tick_index); - } - } - }); - } - - /// Updates the current liquidity for a subnet if the current tick index is within the specified - /// range - /// - /// This function handles both increasing and decreasing liquidity based on the sign of the - /// liquidity parameter. It uses i128 to safely handle values up to u64::MAX in both positive - /// and negative directions. - fn update_liquidity_if_needed( - netuid: NetUid, - tick_low: TickIndex, - tick_high: TickIndex, - liquidity: i128, - ) { - let current_tick_index = TickIndex::current_bounded::(netuid); - if (tick_low <= current_tick_index) && (current_tick_index < tick_high) { - CurrentLiquidity::::mutate(netuid, |current_liquidity| { - let is_neg = liquidity.is_negative(); - let liquidity = liquidity.abs().min(u64::MAX as i128) as u64; - if is_neg { - *current_liquidity = current_liquidity.saturating_sub(liquidity); - } else { - *current_liquidity = current_liquidity.saturating_add(liquidity); - } - }); - } - } + // /// Returns the number of positions for an account in a specific subnet + // /// + // /// # Arguments + // /// * `netuid` - The subnet ID + // /// * `account_id` - The account ID + // /// + // /// # Returns + // /// The number of positions that the account has in the specified subnet + // pub(super) fn count_positions(netuid: NetUid, account_id: &T::AccountId) -> usize { + // PositionsV2::::iter_prefix_values((netuid, account_id.clone())).count() + // } - /// Clamps the subnet's sqrt price when tick index is outside of valid bounds - fn clamp_sqrt_price(netuid: NetUid, tick_index: TickIndex) { - if tick_index >= TickIndex::MAX || tick_index <= TickIndex::MIN { - let corrected_price = tick_index.as_sqrt_price_bounded(); - AlphaSqrtPrice::::set(netuid, corrected_price); - } + pub(crate) fn min_price_inner() -> C { + u64::from(1_000_u64).into() } - /// Returns the number of positions for an account in a specific subnet - /// - /// # Arguments - /// * `netuid` - The subnet ID - /// * `account_id` - The account ID - /// - /// # Returns - /// The number of positions that the account has in the specified subnet - pub(super) fn count_positions(netuid: NetUid, account_id: &T::AccountId) -> usize { - Positions::::iter_prefix_values((netuid, account_id.clone())).count() + pub(crate) fn max_price_inner() -> C { + u64::from(1_000_000_000_000_000_u64).into() } /// Returns the protocol account ID @@ -810,207 +372,185 @@ impl Pallet { T::ProtocolId::get().into_account_truncating() } - pub(crate) fn min_price_inner() -> C { - TickIndex::min_sqrt_price() - .saturating_mul(TickIndex::min_sqrt_price()) - .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_to_num::() - .into() - } - - pub(crate) fn max_price_inner() -> C { - TickIndex::max_sqrt_price() - .saturating_mul(TickIndex::max_sqrt_price()) - .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_round() - .saturating_to_num::() - .into() - } - /// Dissolve all LPs and clean state. - pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { - if SwapV3Initialized::::get(netuid) { - // 1) Snapshot only *non‑protocol* positions: (owner, position_id). - struct CloseItem { - owner: A, - pos_id: PositionId, - } - let protocol_account = Self::protocol_account_id(); - - let mut to_close: sp_std::vec::Vec> = sp_std::vec::Vec::new(); - for ((owner, pos_id), _pos) in Positions::::iter_prefix((netuid,)) { - if owner != protocol_account { - to_close.push(CloseItem { owner, pos_id }); - } - } - - if to_close.is_empty() { - log::debug!( - "dissolve_all_lp: no user positions; netuid={netuid:?}, protocol liquidity untouched" - ); - return Ok(()); - } - - let mut user_refunded_tao = TaoCurrency::ZERO; - let mut user_staked_alpha = AlphaCurrency::ZERO; - - let trust: Vec = T::SubnetInfo::get_validator_trust(netuid.into()); - let permit: Vec = T::SubnetInfo::get_validator_permit(netuid.into()); - - // Helper: pick target validator uid, only among permitted validators, by highest trust. - let pick_target_uid = |trust: &Vec, permit: &Vec| -> Option { - let mut best_uid: Option = None; - let mut best_trust: u16 = 0; - for (i, (&t, &p)) in trust.iter().zip(permit.iter()).enumerate() { - if p && (best_uid.is_none() || t > best_trust) { - best_uid = Some(i); - best_trust = t; - } - } - best_uid.map(|i| i as u16) - }; - - for CloseItem { owner, pos_id } in to_close.into_iter() { - match Self::do_remove_liquidity(netuid, &owner, pos_id) { - Ok(rm) => { - // α withdrawn from the pool = principal + accrued fees - let alpha_total_from_pool: AlphaCurrency = - rm.alpha.saturating_add(rm.fee_alpha); - - // ---------------- USER: refund τ and convert α → stake ---------------- - - // 1) Refund τ principal directly. - let tao_total_from_pool: TaoCurrency = rm.tao.saturating_add(rm.fee_tao); - if tao_total_from_pool > TaoCurrency::ZERO { - T::BalanceOps::increase_balance(&owner, tao_total_from_pool); - user_refunded_tao = - user_refunded_tao.saturating_add(tao_total_from_pool); - T::TaoReserve::decrease_provided(netuid, tao_total_from_pool); - } - - // 2) Stake ALL withdrawn α (principal + fees) to the best permitted validator. - if alpha_total_from_pool > AlphaCurrency::ZERO { - if let Some(target_uid) = pick_target_uid(&trust, &permit) { - let validator_hotkey: T::AccountId = - T::SubnetInfo::hotkey_of_uid(netuid.into(), target_uid).ok_or( - sp_runtime::DispatchError::Other( - "validator_hotkey_missing", - ), - )?; - - // Stake α from LP owner (coldkey) to chosen validator (hotkey). - T::BalanceOps::increase_stake( - &owner, - &validator_hotkey, - netuid, - alpha_total_from_pool, - )?; - - user_staked_alpha = - user_staked_alpha.saturating_add(alpha_total_from_pool); - - log::debug!( - "dissolve_all_lp: user dissolved & staked α: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_staked={alpha_total_from_pool:?}, target_uid={target_uid}" - ); - } else { - // No permitted validators; burn to avoid balance drift. - log::debug!( - "dissolve_all_lp: no permitted validators; α burned: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_total={alpha_total_from_pool:?}" - ); - } - - T::AlphaReserve::decrease_provided(netuid, alpha_total_from_pool); - } - } - Err(e) => { - log::debug!( - "dissolve_all_lp: force-close failed: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, err={e:?}" - ); - continue; - } - } - } - - log::debug!( - "dissolve_all_liquidity_providers (users-only): netuid={netuid:?}, users_refunded_total_τ={user_refunded_tao:?}, users_staked_total_α={user_staked_alpha:?}; protocol liquidity untouched" - ); - - return Ok(()); - } - - log::debug!( - "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, leaving all liquidity/state intact" - ); - + pub fn do_dissolve_all_liquidity_providers(_netuid: NetUid) -> DispatchResult { + // TODO: Revise when user liquidity is available Ok(()) - } + // if PalSwapInitialized::::get(netuid) { + // // 1) Snapshot only *non‑protocol* positions: (owner, position_id). + // struct CloseItem { + // owner: A, + // pos_id: PositionId, + // } + // let protocol_account = Self::protocol_account_id(); + + // let mut to_close: sp_std::vec::Vec> = sp_std::vec::Vec::new(); + // for ((owner, pos_id), _pos) in Positions::::iter_prefix((netuid,)) { + // if owner != protocol_account { + // to_close.push(CloseItem { owner, pos_id }); + // } + // } + + // if to_close.is_empty() { + // log::debug!( + // "dissolve_all_lp: no user positions; netuid={netuid:?}, protocol liquidity untouched" + // ); + // return Ok(()); + // } + + // let mut user_refunded_tao = TaoCurrency::ZERO; + // let mut user_staked_alpha = AlphaCurrency::ZERO; + + // let trust: Vec = T::SubnetInfo::get_validator_trust(netuid.into()); + // let permit: Vec = T::SubnetInfo::get_validator_permit(netuid.into()); + + // // Helper: pick target validator uid, only among permitted validators, by highest trust. + // let pick_target_uid = |trust: &Vec, permit: &Vec| -> Option { + // let mut best_uid: Option = None; + // let mut best_trust: u16 = 0; + // for (i, (&t, &p)) in trust.iter().zip(permit.iter()).enumerate() { + // if p && (best_uid.is_none() || t > best_trust) { + // best_uid = Some(i); + // best_trust = t; + // } + // } + // best_uid.map(|i| i as u16) + // }; + + // for CloseItem { owner, pos_id } in to_close.into_iter() { + // match Self::do_remove_liquidity(netuid, &owner, pos_id) { + // Ok(rm) => { + // // α withdrawn from the pool = principal + accrued fees + // let alpha_total_from_pool: AlphaCurrency = + // rm.alpha.saturating_add(rm.fee_alpha); + + // // ---------------- USER: refund τ and convert α → stake ---------------- + + // // 1) Refund τ principal directly. + // let tao_total_from_pool: TaoCurrency = rm.tao.saturating_add(rm.fee_tao); + // if tao_total_from_pool > TaoCurrency::ZERO { + // T::BalanceOps::increase_balance(&owner, tao_total_from_pool); + // user_refunded_tao = + // user_refunded_tao.saturating_add(tao_total_from_pool); + // T::TaoReserve::decrease_provided(netuid, tao_total_from_pool); + // } + + // // 2) Stake ALL withdrawn α (principal + fees) to the best permitted validator. + // if alpha_total_from_pool > AlphaCurrency::ZERO { + // if let Some(target_uid) = pick_target_uid(&trust, &permit) { + // let validator_hotkey: T::AccountId = + // T::SubnetInfo::hotkey_of_uid(netuid.into(), target_uid).ok_or( + // sp_runtime::DispatchError::Other( + // "validator_hotkey_missing", + // ), + // )?; + + // // Stake α from LP owner (coldkey) to chosen validator (hotkey). + // T::BalanceOps::increase_stake( + // &owner, + // &validator_hotkey, + // netuid, + // alpha_total_from_pool, + // )?; + + // user_staked_alpha = + // user_staked_alpha.saturating_add(alpha_total_from_pool); + + // log::debug!( + // "dissolve_all_lp: user dissolved & staked α: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_staked={alpha_total_from_pool:?}, target_uid={target_uid}" + // ); + // } else { + // // No permitted validators; burn to avoid balance drift. + // log::debug!( + // "dissolve_all_lp: no permitted validators; α burned: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_total={alpha_total_from_pool:?}" + // ); + // } + + // T::AlphaReserve::decrease_provided(netuid, alpha_total_from_pool); + // } + // } + // Err(e) => { + // log::debug!( + // "dissolve_all_lp: force-close failed: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, err={e:?}" + // ); + // continue; + // } + // } + // } + + // log::debug!( + // "dissolve_all_liquidity_providers (users-only): netuid={netuid:?}, users_refunded_total_τ={user_refunded_tao:?}, users_staked_total_α={user_staked_alpha:?}; protocol liquidity untouched" + // ); + + // return Ok(()); + // } + + // log::debug!( + // "dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, leaving all liquidity/state intact" + // ); + + // Ok(()) + } + + /// TODO: Revise when user liquidity is available /// Clear **protocol-owned** liquidity and wipe all swap state for `netuid`. pub fn do_clear_protocol_liquidity(netuid: NetUid) -> DispatchResult { - let protocol_account = Self::protocol_account_id(); + // let protocol_account = Self::protocol_account_id(); // 1) Force-close only protocol positions, burning proceeds. - let mut burned_tao = TaoCurrency::ZERO; - let mut burned_alpha = AlphaCurrency::ZERO; - - // Collect protocol position IDs first to avoid mutating while iterating. - let protocol_pos_ids: sp_std::vec::Vec = Positions::::iter_prefix((netuid,)) - .filter_map(|((owner, pos_id), _)| { - if owner == protocol_account { - Some(pos_id) - } else { - None - } - }) - .collect(); - - for pos_id in protocol_pos_ids { - match Self::do_remove_liquidity(netuid, &protocol_account, pos_id) { - Ok(rm) => { - let alpha_total_from_pool: AlphaCurrency = - rm.alpha.saturating_add(rm.fee_alpha); - let tao_total_from_pool: TaoCurrency = rm.tao.saturating_add(rm.fee_tao); - - if tao_total_from_pool > TaoCurrency::ZERO { - burned_tao = burned_tao.saturating_add(tao_total_from_pool); - } - if alpha_total_from_pool > AlphaCurrency::ZERO { - burned_alpha = burned_alpha.saturating_add(alpha_total_from_pool); - } - - log::debug!( - "clear_protocol_liquidity: burned protocol pos: netuid={netuid:?}, pos_id={pos_id:?}, τ={tao_total_from_pool:?}, α_total={alpha_total_from_pool:?}" - ); - } - Err(e) => { - log::debug!( - "clear_protocol_liquidity: force-close failed: netuid={netuid:?}, pos_id={pos_id:?}, err={e:?}" - ); - continue; - } - } - } - - // 2) Clear active tick index entries, then all swap state (idempotent even if empty/non‑V3). - let active_ticks: sp_std::vec::Vec = - Ticks::::iter_prefix(netuid).map(|(ti, _)| ti).collect(); - for ti in active_ticks { - ActiveTickIndexManager::::remove(netuid, ti); - } - - let _ = Positions::::clear_prefix((netuid,), u32::MAX, None); - let _ = Ticks::::clear_prefix(netuid, u32::MAX, None); - - FeeGlobalTao::::remove(netuid); - FeeGlobalAlpha::::remove(netuid); - CurrentLiquidity::::remove(netuid); - CurrentTick::::remove(netuid); - AlphaSqrtPrice::::remove(netuid); - SwapV3Initialized::::remove(netuid); - - let _ = TickIndexBitmapWords::::clear_prefix((netuid,), u32::MAX, None); + let burned_tao = T::TaoReserve::reserve(netuid.into()); + let burned_alpha = T::AlphaReserve::reserve(netuid.into()); + + T::TaoReserve::decrease_provided(netuid.into(), burned_tao); + T::AlphaReserve::decrease_provided(netuid.into(), burned_alpha); + + // // Collect protocol position IDs first to avoid mutating while iterating. + // let protocol_pos_ids: sp_std::vec::Vec = Positions::::iter_prefix((netuid,)) + // .filter_map(|((owner, pos_id), _)| { + // if owner == protocol_account { + // Some(pos_id) + // } else { + // None + // } + // }) + // .collect(); + + // for pos_id in protocol_pos_ids { + // match Self::do_remove_liquidity(netuid, &protocol_account, pos_id) { + // Ok(rm) => { + // let alpha_total_from_pool: AlphaCurrency = + // rm.alpha.saturating_add(rm.fee_alpha); + // let tao_total_from_pool: TaoCurrency = rm.tao.saturating_add(rm.fee_tao); + + // if tao_total_from_pool > TaoCurrency::ZERO { + // burned_tao = burned_tao.saturating_add(tao_total_from_pool); + // } + // if alpha_total_from_pool > AlphaCurrency::ZERO { + // burned_alpha = burned_alpha.saturating_add(alpha_total_from_pool); + // } + + // log::debug!( + // "clear_protocol_liquidity: burned protocol pos: netuid={netuid:?}, pos_id={pos_id:?}, τ={tao_total_from_pool:?}, α_total={alpha_total_from_pool:?}" + // ); + // } + // Err(e) => { + // log::debug!( + // "clear_protocol_liquidity: force-close failed: netuid={netuid:?}, pos_id={pos_id:?}, err={e:?}" + // ); + // continue; + // } + // } + // } + + let _ = PositionsV2::::clear_prefix((netuid,), u32::MAX, None); + + FeesTao::::remove(netuid); + FeesAlpha::::remove(netuid); + PalSwapInitialized::::remove(netuid); FeeRate::::remove(netuid); EnabledUserLiquidity::::remove(netuid); + SwapReserveWeight::::remove(netuid); log::debug!( "clear_protocol_liquidity: netuid={netuid:?}, protocol_burned: τ={burned_tao:?}, α={burned_alpha:?}; state cleared" @@ -1046,15 +586,13 @@ where drop_fees: bool, should_rollback: bool, ) -> Result, DispatchError> { - let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit.to_u64()) - .safe_div(SqrtPrice::saturating_from_num(1_000_000_000)) - .checked_sqrt(SqrtPrice::saturating_from_num(0.0000000001)) - .ok_or(Error::::PriceLimitExceeded)?; + let limit_price = U64F64::saturating_from_num(price_limit.to_u64()) + .safe_div(U64F64::saturating_from_num(1_000_000_000_u64)); Self::do_swap::( NetUid::from(netuid), order, - limit_sqrt_price, + limit_price, drop_fees, should_rollback, ) @@ -1110,20 +648,20 @@ impl SwapHandler for Pallet { Self::calculate_fee_amount(netuid, amount, false) } - fn current_alpha_price(netuid: NetUid) -> U96F32 { + fn current_alpha_price(netuid: NetUid) -> U64F64 { Self::current_price(netuid.into()) } fn get_protocol_tao(netuid: NetUid) -> TaoCurrency { let protocol_account_id = Self::protocol_account_id(); let mut positions = - Positions::::iter_prefix_values((netuid, protocol_account_id.clone())) + PositionsV2::::iter_prefix_values((netuid, protocol_account_id.clone())) .collect::>(); if let Some(position) = positions.get_mut(0) { - let current_sqrt_price = AlphaSqrtPrice::::get(netuid); + let price = Self::current_price(netuid); // Adjust liquidity - let maybe_token_amounts = position.to_token_amounts(current_sqrt_price); + let maybe_token_amounts = position.to_token_amounts(price); if let Ok((tao, _)) = maybe_token_amounts { return tao.into(); } diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 97a25ec242..7c6b484f79 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -3,20 +3,21 @@ use core::ops::Neg; use frame_support::{PalletId, pallet_prelude::*, traits::Get}; use frame_system::pallet_prelude::*; -use substrate_fixed::types::U64F64; +// use safe_math::SafeDiv; use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use crate::{ + pallet::reserve_weights::ReserveWeight, position::{Position, PositionId}, - tick::{LayerLevel, Tick, TickIndex}, weights::WeightInfo, }; pub use pallet::*; mod impls; +mod reserve_weights; mod swap_step; #[cfg(test)] mod tests; @@ -82,33 +83,33 @@ mod pallet { #[pallet::storage] pub type FeeRate = StorageMap<_, Twox64Concat, NetUid, u16, ValueQuery, DefaultFeeRate>; - // Global accrued fees in tao per subnet - #[pallet::storage] - pub type FeeGlobalTao = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; + // // Global accrued fees in tao per subnet + // #[pallet::storage] + // pub type FeeGlobalTao = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; - // Global accrued fees in alpha per subnet - #[pallet::storage] - pub type FeeGlobalAlpha = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; + // // Global accrued fees in alpha per subnet + // #[pallet::storage] + // pub type FeeGlobalAlpha = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; - /// Storage for all ticks, using subnet ID as the primary key and tick index as the secondary key - #[pallet::storage] - pub type Ticks = StorageDoubleMap<_, Twox64Concat, NetUid, Twox64Concat, TickIndex, Tick>; + // /// Storage for all ticks, using subnet ID as the primary key and tick index as the secondary key + // #[pallet::storage] + // pub type Ticks = StorageDoubleMap<_, Twox64Concat, NetUid, Twox64Concat, TickIndex, Tick>; - /// Storage to determine whether swap V3 was initialized for a specific subnet. - #[pallet::storage] - pub type SwapV3Initialized = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; + // /// Storage to determine whether swap V3 was initialized for a specific subnet. + // #[pallet::storage] + // pub type SwapV3Initialized = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; - /// Storage for the square root price of Alpha token for each subnet. - #[pallet::storage] - pub type AlphaSqrtPrice = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; + // /// Storage for the square root price of Alpha token for each subnet. + // #[pallet::storage] + // pub type AlphaSqrtPrice = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; - /// Storage for the current price tick. - #[pallet::storage] - pub type CurrentTick = StorageMap<_, Twox64Concat, NetUid, TickIndex, ValueQuery>; + // /// Storage for the current price tick. + // #[pallet::storage] + // pub type CurrentTick = StorageMap<_, Twox64Concat, NetUid, TickIndex, ValueQuery>; - /// Storage for the current liquidity amount for each subnet. - #[pallet::storage] - pub type CurrentLiquidity = StorageMap<_, Twox64Concat, NetUid, u64, ValueQuery>; + // /// Storage for the current liquidity amount for each subnet. + // #[pallet::storage] + // pub type CurrentLiquidity = StorageMap<_, Twox64Concat, NetUid, u64, ValueQuery>; /// Indicates whether a subnet has been switched to V3 swap from V2. /// If `true`, the subnet is permanently on V3 swap mode allowing add/remove liquidity @@ -119,7 +120,7 @@ mod pallet { /// Storage for user positions, using subnet ID and account ID as keys /// The value is a bounded vector of Position structs with details about the liquidity positions #[pallet::storage] - pub type Positions = StorageNMap< + pub type PositionsV2 = StorageNMap< _, ( NMapKey, // Subnet ID @@ -134,27 +135,52 @@ mod pallet { #[pallet::storage] pub type LastPositionId = StorageValue<_, u128, ValueQuery>; - /// Tick index bitmap words storage + // /// Tick index bitmap words storage + // #[pallet::storage] + // pub type TickIndexBitmapWords = StorageNMap< + // _, + // ( + // NMapKey, // Subnet ID + // NMapKey, // Layer level + // NMapKey, // word index + // ), + // u128, + // ValueQuery, + // >; + + // /// TAO reservoir for scraps of protocol claimed fees. + // #[pallet::storage] + // pub type ScrapReservoirTao = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>; + + // /// Alpha reservoir for scraps of protocol claimed fees. + // #[pallet::storage] + // pub type ScrapReservoirAlpha = + // StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>; + + //////////////////////////////////////////////////// + // Balancer (PalSwap) maps and variables + + /// Default reserve weight + #[pallet::type_value] + pub fn DefaultReserveWeight() -> ReserveWeight { + ReserveWeight::default() + } + /// u64-normalized reserve weight #[pallet::storage] - pub type TickIndexBitmapWords = StorageNMap< - _, - ( - NMapKey, // Subnet ID - NMapKey, // Layer level - NMapKey, // word index - ), - u128, - ValueQuery, - >; + pub type SwapReserveWeight = + StorageMap<_, Twox64Concat, NetUid, ReserveWeight, ValueQuery, DefaultReserveWeight>; - /// TAO reservoir for scraps of protocol claimed fees. + /// Storage to determine whether balancer swap was initialized for a specific subnet. #[pallet::storage] - pub type ScrapReservoirTao = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>; + pub type PalSwapInitialized = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; - /// Alpha reservoir for scraps of protocol claimed fees. + /// Total fees in TAO per subnet due to be paid to users / protocol #[pallet::storage] - pub type ScrapReservoirAlpha = - StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>; + pub type FeesTao = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>; + + /// Total fees in Alpha per subnet due to be paid to users / protocol + #[pallet::storage] + pub type FeesAlpha = StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -167,7 +193,7 @@ mod pallet { UserLiquidityToggled { netuid: NetUid, enable: bool }, /// Event emitted when a liquidity position is added to a subnet's liquidity pool. - LiquidityAdded { + LiquidityAddedV2 { /// The coldkey account that owns the position coldkey: T::AccountId, /// The hotkey account where Alpha comes from @@ -182,14 +208,10 @@ mod pallet { tao: TaoCurrency, /// The amount of Alpha tokens committed to the position alpha: AlphaCurrency, - /// the lower tick - tick_low: TickIndex, - /// the upper tick - tick_high: TickIndex, }, /// Event emitted when a liquidity position is removed from a subnet's liquidity pool. - LiquidityRemoved { + LiquidityRemovedV2 { /// The coldkey account that owns the position coldkey: T::AccountId, /// The hotkey account where Alpha goes to @@ -208,15 +230,11 @@ mod pallet { fee_tao: TaoCurrency, /// The amount of Alpha fees earned from the position fee_alpha: AlphaCurrency, - /// the lower tick - tick_low: TickIndex, - /// the upper tick - tick_high: TickIndex, }, /// Event emitted when a liquidity position is modified in a subnet's liquidity pool. /// Modifying causes the fees to be claimed. - LiquidityModified { + LiquidityModifiedV2 { /// The coldkey account that owns the position coldkey: T::AccountId, /// The hotkey account where Alpha comes from or goes to @@ -235,10 +253,6 @@ mod pallet { fee_tao: TaoCurrency, /// The amount of Alpha fees earned from the position fee_alpha: AlphaCurrency, - /// the lower tick - tick_low: TickIndex, - /// the upper tick - tick_high: TickIndex, }, } @@ -286,6 +300,9 @@ mod pallet { /// The subnet does not have subtoken enabled SubtokenDisabled, + + /// Swap reserves are too imbalanced + ReservesOutOfBalance, } #[pallet::call] @@ -373,8 +390,6 @@ mod pallet { origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, - tick_low: TickIndex, - tick_high: TickIndex, liquidity: u64, ) -> DispatchResult { let coldkey = ensure_signed(origin)?; @@ -390,14 +405,8 @@ mod pallet { Error::::SubtokenDisabled ); - let (position_id, tao, alpha) = Self::do_add_liquidity( - netuid.into(), - &coldkey, - &hotkey, - tick_low, - tick_high, - liquidity, - )?; + let (position_id, tao, alpha) = + Self::do_add_liquidity(netuid.into(), &coldkey, &hotkey, liquidity)?; let alpha = AlphaCurrency::from(alpha); let tao = TaoCurrency::from(tao); @@ -414,7 +423,7 @@ mod pallet { T::AlphaReserve::increase_provided(netuid.into(), alpha_provided); // Emit an event - Self::deposit_event(Event::LiquidityAdded { + Self::deposit_event(Event::LiquidityAddedV2 { coldkey, hotkey, netuid, @@ -422,8 +431,6 @@ mod pallet { liquidity, tao, alpha, - tick_low, - tick_high, }); Ok(()) @@ -470,7 +477,7 @@ mod pallet { T::AlphaReserve::decrease_provided(netuid.into(), result.alpha); // Emit an event - Self::deposit_event(Event::LiquidityRemoved { + Self::deposit_event(Event::LiquidityRemovedV2 { coldkey, hotkey, netuid: netuid.into(), @@ -480,8 +487,6 @@ mod pallet { alpha: result.alpha, fee_tao: result.fee_tao, fee_alpha: result.fee_alpha, - tick_low: result.tick_low.into(), - tick_high: result.tick_high.into(), }); Ok(()) @@ -535,7 +540,7 @@ mod pallet { ); // Emit an event - Self::deposit_event(Event::LiquidityModified { + Self::deposit_event(Event::LiquidityModifiedV2 { coldkey: coldkey.clone(), hotkey: hotkey.clone(), netuid, @@ -545,8 +550,6 @@ mod pallet { alpha: result.alpha.to_u64() as i64, fee_tao: result.fee_tao, fee_alpha: result.fee_alpha, - tick_low: result.tick_low, - tick_high: result.tick_high, }); } else { // Credit the returned tao and alpha to the account @@ -555,7 +558,7 @@ mod pallet { // Emit an event if result.removed { - Self::deposit_event(Event::LiquidityRemoved { + Self::deposit_event(Event::LiquidityRemovedV2 { coldkey: coldkey.clone(), hotkey: hotkey.clone(), netuid, @@ -565,11 +568,9 @@ mod pallet { alpha: result.alpha, fee_tao: result.fee_tao, fee_alpha: result.fee_alpha, - tick_low: result.tick_low, - tick_high: result.tick_high, }); } else { - Self::deposit_event(Event::LiquidityModified { + Self::deposit_event(Event::LiquidityModifiedV2 { coldkey: coldkey.clone(), hotkey: hotkey.clone(), netuid, @@ -579,8 +580,6 @@ mod pallet { alpha: (result.alpha.to_u64() as i64).neg(), fee_tao: result.fee_tao, fee_alpha: result.fee_alpha, - tick_low: result.tick_low, - tick_high: result.tick_high, }); } } diff --git a/pallets/swap/src/pallet/reserve_weights.rs b/pallets/swap/src/pallet/reserve_weights.rs new file mode 100644 index 0000000000..72994e8796 --- /dev/null +++ b/pallets/swap/src/pallet/reserve_weights.rs @@ -0,0 +1,718 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use safe_bigmath::*; +use safe_math::*; +use sp_arithmetic::Perquintill; +use sp_core::U256; +use sp_runtime::Saturating; +use substrate_fixed::types::U64F64; +use subtensor_macros::freeze_struct; + +#[freeze_struct("8c6bbe52ef752203")] +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub struct ReserveWeight { + quote: Perquintill, +} + +// Lower imit of weights is 0.01 +pub const ACCURACY: u64 = 1_000_000_000_000_000_000_u64; +pub const MIN_WEIGHT: Perquintill = Perquintill::from_parts(ACCURACY / 100); +pub const ONE: Perquintill = Perquintill::from_parts(ACCURACY); + +#[derive(Debug)] +pub enum ReserveWeightError { + InvalidValue, +} + +impl Default for ReserveWeight { + fn default() -> Self { + Self { + quote: Perquintill::from_rational(1u128, 2u128), + } + } +} + +impl ReserveWeight { + pub fn new(quote: Perquintill) -> Result { + if Self::check_constraints(quote) { + Ok(ReserveWeight { quote }) + } else { + Err(ReserveWeightError::InvalidValue) + } + } + + fn check_constraints(quote: Perquintill) -> bool { + let base = ONE.saturating_sub(quote); + (base >= MIN_WEIGHT) && (quote >= MIN_WEIGHT) + } + + pub fn get_quote_weight(&self) -> Perquintill { + self.quote + } + + pub fn get_base_weight(&self) -> Perquintill { + ONE.saturating_sub(self.quote) + } + + pub fn set_quote_weight(&self, new_value: Perquintill) -> Result<(), ReserveWeightError> { + if Self::check_constraints(new_value) { + Ok(()) + } else { + Err(ReserveWeightError::InvalidValue) + } + } + + fn exp_scaled(&self, x: u64, dx: u64, base_quote: bool) -> U64F64 { + let den = x.saturating_add(dx); + if den == 0 { + return U64F64::saturating_from_num(0); + } + let w1: u128 = self.get_base_weight().deconstruct() as u128; + let w2: u128 = self.get_quote_weight().deconstruct() as u128; + let x_plus_dx = x.saturating_add(dx); + + let precision = 1024; + let x_safe = SafeInt::from(x); + let w1_safe = SafeInt::from(w1); + let w2_safe = SafeInt::from(w2); + let perquintill_scale = SafeInt::from(ACCURACY as u128); + let denominator = SafeInt::from(x_plus_dx); + let maybe_result_safe_int = if base_quote { + + println!("x = {:?}", x); + println!("dx = {:?}", dx); + println!("x_safe = {:?}", x_safe); + println!("denominator = {:?}", denominator); + println!("w1_safe = {:?}", w1_safe); + println!("w2_safe = {:?}", w2_safe); + println!("precision = {:?}", precision); + println!("perquintill_scale = {:?}", perquintill_scale); + + SafeInt::pow_ratio_scaled( + &x_safe, + &denominator, + &w1_safe, + &w2_safe, + precision, + &perquintill_scale, + ) + } else { + SafeInt::pow_ratio_scaled( + &x_safe, + &denominator, + &w2_safe, + &w1_safe, + precision, + &perquintill_scale, + ) + }; + + if let Some(result_safe_int) = maybe_result_safe_int { + if let Some(result_u64) = result_safe_int.to_u64() { + return U64F64::saturating_from_num(result_u64) + .safe_div(U64F64::saturating_from_num(ACCURACY)); + } + } + return U64F64::saturating_from_num(0); + } + + /// Calculates exponent of (x / (x + ∆x)) ^ (w_base/w_quote) + pub fn exp_base_quote(&self, x: u64, dx: u64) -> U64F64 { + self.exp_scaled(x, dx, true) + } + + /// Calculates exponent of (y / (y + ∆y)) ^ (w_quote/w_base) + pub fn exp_quote_base(&self, y: u64, dy: u64) -> U64F64 { + self.exp_scaled(y, dy, false) + } + + /// Calculates price as (w1/w2) * (y/x) + pub fn calculate_price(&self, x: u64, y: u64) -> U64F64 { + let w2_fixed = U64F64::saturating_from_num(self.get_quote_weight().deconstruct()); + let w1_fixed = U64F64::saturating_from_num(self.get_base_weight().deconstruct()); + let x_fixed = U64F64::saturating_from_num(x); + let y_fixed = U64F64::saturating_from_num(y); + w1_fixed + .safe_div(w2_fixed) + .saturating_mul(y_fixed.safe_div(x_fixed)) + } + + /// Multiply a u128 value by a Perquintill with u128 result rounded to the + /// nearest integer + fn mul_perquintill_round(p: Perquintill, value: u128) -> u128 { + let parts = p.deconstruct() as u128; + let acc = ACCURACY as u128; + + let num = U256::from(value) * U256::from(parts); + let den = U256::from(acc); + + // add 0.5 ulp before integer division → round-to-nearest + let res = (num + den / U256::from(2u8)) / den; + res.min(U256::from(u128::MAX)).as_u128() + } + + pub fn update_weights_for_added_liquidity( + &mut self, + tao_reserve: u64, + alpha_reserve: u64, + tao_delta: u64, + alpha_delta: u64, + ) -> Result<(), ReserveWeightError> { + // Calculate new to-be reserves (do not update here) + let tao_reserve_u128 = u64::from(tao_reserve) as u128; + let alpha_reserve_u128 = u64::from(alpha_reserve) as u128; + let tao_delta_u128 = u64::from(tao_delta) as u128; + let alpha_delta_u128 = u64::from(alpha_delta) as u128; + let new_tao_reserve_u128 = tao_reserve_u128.saturating_add(tao_delta_u128); + let new_alpha_reserve_u128 = alpha_reserve_u128.saturating_add(alpha_delta_u128); + + // Calculate new weights + let quantity_1: u128 = Self::mul_perquintill_round( + self.get_base_weight(), + tao_reserve_u128.saturating_mul(new_alpha_reserve_u128), + ); + let quantity_2: u128 = Self::mul_perquintill_round( + self.get_quote_weight(), + alpha_reserve_u128.saturating_mul(new_tao_reserve_u128), + ); + let q_sum = quantity_1.saturating_add(quantity_2); + + // Calculate new reserve weights + let new_reserve_weight = if q_sum != 0 { + // Both TAO and Alpha are non-zero, normal case + Perquintill::from_rational(quantity_2, q_sum) + } else { + // Either TAO or Alpha reserve were and/or remain zero => Initialize weights to 0.5 + Perquintill::from_rational(1u128, 2u128) + }; + + self.set_quote_weight(new_reserve_weight) + } + + /// Calculates quote delta needed to reach the price up when byuing + pub fn calculate_quote_delta_in( + &self, + current_price: U64F64, + target_price: U64F64, + reserve: u64, + ) -> u64 { + let base_numerator: u128 = target_price.to_bits(); + let base_denominator: u128 = current_price.to_bits(); + let w1_fixed: u128 = self.get_base_weight().deconstruct() as u128; + let scale: u128 = 10u128.pow(18); + + let maybe_exp_result = SafeInt::pow_ratio_scaled( + &SafeInt::from(base_numerator), + &SafeInt::from(base_denominator), + // &SafeInt::from(3u128), + // &SafeInt::from(4u128), + &SafeInt::from(w1_fixed), + &SafeInt::from(ACCURACY), + 160, + &SafeInt::from(scale), + ); + + if let Some(exp_result_safe_int) = maybe_exp_result { + if let Some(exp_result_u64) = exp_result_safe_int.to_u64() { + let reserve_fixed = U64F64::saturating_from_num(reserve); + let exp_result_fixed = U64F64::saturating_from_num(exp_result_u64); + let one = U64F64::saturating_from_num(1); + let scale_fixed = U64F64::saturating_from_num(scale); + return reserve_fixed + .saturating_mul(exp_result_fixed.safe_div(scale_fixed).saturating_sub(one)) + .saturating_to_num::(); + } + } + return 0u64; + } + + /// Calculates base delta needed to reach the price down when selling + pub fn calculate_base_delta_in( + &self, + current_price: U64F64, + target_price: U64F64, + reserve: u64, + ) -> u64 { + let base_numerator: u128 = current_price.to_bits(); + let base_denominator: u128 = target_price.to_bits(); + let w2_fixed: u128 = self.get_quote_weight().deconstruct() as u128; + let scale: u128 = 10u128.pow(18); + + let maybe_exp_result = SafeInt::pow_ratio_scaled( + &SafeInt::from(base_numerator), + &SafeInt::from(base_denominator), + &SafeInt::from(w2_fixed), + &SafeInt::from(ACCURACY), + 160, + &SafeInt::from(scale), + ); + + if let Some(exp_result_safe_int) = maybe_exp_result { + if let Some(exp_result_u64) = exp_result_safe_int.to_u64() { + let reserve_fixed = U64F64::saturating_from_num(reserve); + let exp_result_fixed = U64F64::saturating_from_num(exp_result_u64); + let one = U64F64::saturating_from_num(1); + let scale_fixed = U64F64::saturating_from_num(scale); + return reserve_fixed + .saturating_mul(exp_result_fixed.safe_div(scale_fixed).saturating_sub(one)) + .saturating_to_num::(); + } + } + return 0u64; + } +} + +// cargo test --package pallet-subtensor-swap --lib -- pallet::reserve_weights::tests --nocapture +#[cfg(test)] +mod tests { + use crate::pallet::ReserveWeight; + use crate::pallet::reserve_weights::*; + use approx::assert_abs_diff_eq; + use sp_arithmetic::Perquintill; + + fn perquintill_to_f64(p: Perquintill) -> f64 { + let parts = p.deconstruct() as f64; + parts / ACCURACY as f64 + } + + #[test] + fn test_perquintill_power() { + const PRECISION: u32 = 4096; + const PERQUINTILL: u128 = ACCURACY as u128; + + let x = SafeInt::from(21_000_000_000_000_000u64); + let delta = SafeInt::from(7_000_000_000_000_000u64); + let w1 = SafeInt::from(600_000_000_000_000_000u128); + let w2 = SafeInt::from(400_000_000_000_000_000u128); + let denominator = &x + δ + assert_eq!(w1.clone() + w2.clone(), SafeInt::from(PERQUINTILL)); + + let perquintill_result = SafeInt::pow_ratio_scaled( + &x, + &denominator, + &w1, + &w2, + PRECISION, + &SafeInt::from(PERQUINTILL), + ) + .expect("perquintill integer result"); + + assert_eq!( + perquintill_result, + SafeInt::from(649_519_052_838_328_985u128) + ); + let readable = safe_bigmath::SafeDec::<18>::from_raw(perquintill_result); + assert_eq!(format!("{}", readable), "0.649519052838328985"); + } + + /// Validate realistic values that can be calculated with f64 precision + #[test] + fn test_exp_base_quote_happy_path() { + // Outer test cases: w_quote + [ + // Perquintill::from_rational(500_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_000_000_001_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(499_999_999_999_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_000_000_100_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_000_001_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_000_010_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_000_100_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_001_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_010_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(500_100_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(501_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(510_000_000_000_u128, 1_000_000_000_000_u128), + Perquintill::from_rational(100_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(100_000_000_001_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(200_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(300_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(400_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(600_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(700_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(800_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(899_999_999_999_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational(900_000_000_000_u128, 1_000_000_000_000_u128), + // Perquintill::from_rational( + // 102_337_248_363_782_924_u128, + // 1_000_000_000_000_000_000_u128, + // ), + ] + .into_iter() + .for_each(|w_quote| { + // Inner test cases: y, x, ∆x + [ + // (1_000_u64, 1_000_u64, 0_u64), + // (1_000_u64, 1_000_u64, 1_u64), + (1_500_u64, 1_000_u64, 1_u64), + // ( + // 1_000_000_000_000_u64, + // 100_000_000_000_000_u64, + // 100_000_000_u64, + // ), + // ( + // 1_000_000_000_000_u64, + // 100_000_000_000_000_u64, + // 100_000_000_u64, + // ), + // ( + // 100_000_000_000_u64, + // 100_000_000_000_000_u64, + // 100_000_000_u64, + // ), + // (100_000_000_000_u64, 100_000_000_000_000_u64, 1_000_000_u64), + // ( + // 100_000_000_000_u64, + // 100_000_000_000_000_u64, + // 1_000_000_000_000_u64, + // ), + // ( + // 1_000_000_000_u64, + // 100_000_000_000_000_u64, + // 1_000_000_000_000_u64, + // ), + // ( + // 1_000_000_u64, + // 100_000_000_000_000_u64, + // 1_000_000_000_000_u64, + // ), + // (1_000_u64, 100_000_000_000_000_u64, 1_000_000_000_000_u64), + // (1_000_u64, 100_000_000_000_000_u64, 1_000_000_000_u64), + // (1_000_u64, 100_000_000_000_000_u64, 1_000_000_u64), + // (1_000_u64, 100_000_000_000_000_u64, 1_000_u64), + // (1_000_u64, 100_000_000_000_000_u64, 100_000_000_000_000_u64), + // (10_u64, 100_000_000_000_000_u64, 100_000_000_000_000_u64), + // // Extreme values of ∆x for small x + // (1_000_000_000_u64, 4_000_000_000_u64, 1_000_000_000_000_u64), + // (1_000_000_000_000_u64, 1_000_u64, 1_000_000_000_000_u64), + // ( + // 5_628_038_062_729_553_u64, + // 400_775_553_u64, + // 14_446_633_907_665_582_u64, + // ), + // ( + // 5_600_000_000_000_000_u64, + // 400_000_000_u64, + // 14_000_000_000_000_000_u64, + // ), + ] + .into_iter() + .for_each(|(y, x, dx)| { + let rw = ReserveWeight::new(w_quote).unwrap(); + let e = rw.exp_base_quote(x, dx); + let one = U64F64::from_num(1); + let y_fixed = U64F64::from_num(y); + println!("debug 1: e = {:?}", e); + let dy = y_fixed * (one - e); + println!("debug 2: dy = {:?}", dy); + + let w1 = perquintill_to_f64(rw.get_base_weight()); + let w2 = perquintill_to_f64(rw.get_quote_weight()); + let e_expected = (x as f64 / (x as f64 + dx as f64)).powf(w1 / w2); + let dy_expected = y as f64 * (1. - e_expected); + + println!("debug 3: dy_expected = {:?}", dy_expected); + + let mut eps = dy_expected / 100000.; + if eps > 1.0 { + eps = 1.0; + } + assert_abs_diff_eq!(dy.to_num::(), dy_expected, epsilon = eps); + }) + }); + } + + /// This test exercises practical application edge cases of exp_base_quote + /// The practical formula where this function is used: + /// ∆y = y * (exp_base_quote(x, ∆x) - 1) + /// + /// The test validates that two different sets of parameters produce (sensibly) + /// different results + /// + #[test] + fn test_exp_base_quote_dy_precision() { + // Test cases: y, x1, ∆x1, w_quote1, x2, ∆x2, w_quote2 + // Realized dy1 should be greater than dy2 + [ + ( + 1_000_000_000_u64, + 21_000_000_000_000_000_u64, + 21_000_000_000_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + 21_000_000_000_000_000_u64, + 21_000_000_000_u64, + Perquintill::from_rational(1_000_000_000_001_u128, 2_000_000_000_000_u128), + ), + ( + 1_000_000_000_u64, + 21_000_000_000_000_000_u64, + 21_000_000_000_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_001_u128), + 21_000_000_000_000_000_u64, + 21_000_000_000_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + ), + ( + 1_000_000_000_u64, + 21_000_000_000_000_000_u64, + 2_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + 21_000_000_000_000_000_u64, + 1_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + ), + ( + 1_000_000_000_u64, + 21_000_000_000_000_000_u64, + 1_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + 21_000_000_000_000_000_u64, + 1_u64, + Perquintill::from_rational(1_010_000_000_000_u128, 2_000_000_000_000_u128), + ), + ( + 1_000_000_000_u64, + 21_000_000_000_000_000_u64, + 1_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_010_000_000_000_u128), + 21_000_000_000_000_000_u64, + 1_u64, + Perquintill::from_rational(1_000_000_000_000_u128, 2_000_000_000_000_u128), + ), + ] + .into_iter() + .for_each(|(y, x1, dx1, w_quote1, x2, dx2, w_quote2)| { + let rw1 = ReserveWeight::new(w_quote1).unwrap(); + let rw2 = ReserveWeight::new(w_quote2).unwrap(); + + let exp1 = rw1.exp_base_quote(x1, dx1); + let exp2 = rw2.exp_base_quote(x2, dx2); + + let one = U64F64::from_num(1); + let y_fixed = U64F64::from_num(y); + let dy1 = y_fixed * (one - exp1); + let dy2 = y_fixed * (one - exp2); + + assert!(dy1 > dy2); + + let zero = U64F64::from_num(0); + assert!(dy1 != zero); + assert!(dy2 != zero); + }) + } + + /// Test the broad range of w_quote values, usually should be ignored + #[ignore] + #[test] + fn test_exp_quote_broad_range() { + let y = 1_000_000_000_000_u64; + let x = 100_000_000_000_000_u64; + let dx = 10_000_000_u64; + + let mut prev = U64F64::from_num(1_000_000_000); + let mut last_progress = 0.; + let start = 100_000_000_000_u128; + let stop = 900_000_000_000_u128; + for num in (start..=stop).step_by(1000 as usize) { + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + let e = rw.exp_base_quote(x, dx); + + let one = U64F64::from_num(1); + // println!("e = {:?}", e); + // println!("1 - e = {:?}", one - e); + + let dy = U64F64::from_num(y) * (one - e); + // println!("dy = {:?}", dy); + + let progress = (num as f64 - start as f64) / (stop as f64 - start as f64); + + if progress - last_progress >= 0.0001 { + println!("progress = {:?}%", progress * 100.); + println!("dy = {:?}", dy); + last_progress = progress; + } + + assert!(dy != U64F64::from_num(0)); + assert!(dy <= prev); + prev = dy; + } + } + + #[ignore] + #[test] + fn test_exp_quote_fuzzy() { + use rand::rngs::StdRng; + use rand::{Rng, SeedableRng}; + use rayon::prelude::*; + use std::sync::Arc; + use std::sync::atomic::{AtomicUsize, Ordering}; + + const ITERATIONS: usize = 1_000_000_000; + let counter = Arc::new(AtomicUsize::new(0)); + + (0..ITERATIONS) + .into_par_iter() + .for_each(|i| { + // Each iteration gets its own deterministic RNG. + // Seed depends on i, so runs are reproducible. + let mut rng = StdRng::seed_from_u64(42 + i as u64); + let max_supply: u64 = 21_000_000_000_000_000; + let full_range = true; + + let x: u64 = rng.gen_range(1_000..=max_supply); // Alpha reserve + let y: u64 = if full_range { + // TAO reserve (allow huge prices) + rng.gen_range(1_000..=max_supply) + } else { + // TAO reserve (limit prices with 0-1000) + rng.gen_range(1_000..x.saturating_mul(1000).min(max_supply)) + }; + let dx: u64 = if full_range { + // Alhpa sold (allow huge values) + rng.gen_range(1_000..=21_000_000_000_000_000) + } else { + // Alhpa sold (do not sell more than 100% of what's in alpha reserve) + rng.gen_range(1_000..=x) + }; + let w_numerator: u64 = rng.gen_range(ACCURACY / 10..=ACCURACY / 10 * 9); + let w_quote = Perquintill::from_rational(w_numerator, ACCURACY); + + let rw = ReserveWeight::new(w_quote).unwrap(); + let e = rw.exp_base_quote(x, dx); + + let one = U64F64::from_num(1); + let dy = U64F64::from_num(y) * (one - e); + + // Calculate expected in f64 and approx-assert + let w1 = perquintill_to_f64(rw.get_base_weight()); + let w2 = perquintill_to_f64(rw.get_quote_weight()); + let e_expected = (x as f64 / (x as f64 + dx as f64)).powf(w1 / w2); + let dy_expected = y as f64 * (1. - e_expected); + + let actual = dy.to_num::(); + let mut eps = dy_expected / 1_000_000.; + if eps > 1000.0 { + eps = 1000.0; + } + if eps < 1.0 { + eps = 1.0; + } + + assert!( + (actual - dy_expected).abs() <= eps, + "dy mismatch:\n actual: {}\n expected: {}\n eps: {}\nParameters:\n x: {}\n y: {}\n dx: {}\n w_numerator: {}\n", + actual, dy_expected, eps, x, y, dx, w_numerator, + ); + + // Assert that we aren't giving out more than reserve y + assert!(dy <= y, "dy = {},\ny = {}", dy, y,); + + // Print progress + let done = counter.fetch_add(1, Ordering::Relaxed) + 1; + if done % 100_000_000 == 0 { + let progress = done as f64 / ITERATIONS as f64 * 100.0; + println!("progress = {progress:.4}%"); + } + }); + } + + #[test] + fn test_calculate_quote_delta_in() { + let num = 250_000_000_000_u128; // w1 = 0.75 + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + + let current_price: U64F64 = U64F64::from_num(0.1); + let target_price: U64F64 = U64F64::from_num(0.2); + let tao_reserve: u64 = 1_000_000_000; + + let dy = rw.calculate_quote_delta_in(current_price, target_price, tao_reserve); + + // ∆y = y•[(p'/p)^w1 - 1] + let dy_expected = tao_reserve as f64 + * ((target_price.to_num::() / current_price.to_num::()).powf(0.75) - 1.0); + + assert_eq!(dy, dy_expected as u64,); + } + + #[test] + fn test_calculate_base_delta_in() { + let num = 250_000_000_000_u128; // w2 = 0.25 + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + + let current_price: U64F64 = U64F64::from_num(0.2); + let target_price: U64F64 = U64F64::from_num(0.1); + let alpha_reserve: u64 = 1_000_000_000; + + let dx = rw.calculate_base_delta_in(current_price, target_price, alpha_reserve); + + // ∆x = x•[(p/p')^w2 - 1] + let dx_expected = alpha_reserve as f64 + * ((current_price.to_num::() / target_price.to_num::()).powf(0.25) - 1.0); + + assert_eq!(dx, dx_expected as u64,); + } + + #[test] + fn test_calculate_quote_delta_in_impossible() { + let num = 250_000_000_000_u128; // w1 = 0.75 + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + + // Impossible price (lower) + let current_price: U64F64 = U64F64::from_num(0.1); + let target_price: U64F64 = U64F64::from_num(0.05); + let tao_reserve: u64 = 1_000_000_000; + + let dy = rw.calculate_quote_delta_in(current_price, target_price, tao_reserve); + let dy_expected = 0u64; + + assert_eq!(dy, dy_expected as u64,); + } + + #[test] + fn test_calculate_base_delta_in_impossible() { + let num = 250_000_000_000_u128; // w2 = 0.25 + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + + // Impossible price (higher) + let current_price: U64F64 = U64F64::from_num(0.1); + let target_price: U64F64 = U64F64::from_num(0.2); + let alpha_reserve: u64 = 1_000_000_000; + + let dx = rw.calculate_base_delta_in(current_price, target_price, alpha_reserve); + let dx_expected = 0u64; + + assert_eq!(dx, dx_expected as u64,); + } + + #[test] + fn test_calculate_delta_in_reverse_swap() { + let num = 500_000_000_000_u128; + let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); + let rw = ReserveWeight::new(w_quote).unwrap(); + + let current_price: U64F64 = U64F64::from_num(0.1); + let target_price: U64F64 = U64F64::from_num(0.2); + let tao_reserve: u64 = 1_000_000_000; + + // Here is the simple case of w1 = w2 = 0.5, so alpha = tao / price + let alpha_reserve: u64 = (tao_reserve as f64 / current_price.to_num::()) as u64; + + let dy = rw.calculate_quote_delta_in(current_price, target_price, tao_reserve); + let dx = alpha_reserve as f64 + * (1.0 + - (tao_reserve as f64 / (tao_reserve as f64 + dy as f64)) + .powf(num as f64 / (1_000_000_000_000 - num) as f64)); + + // Verify that buying with dy will in fact bring the price to target_price + let actual_price = rw.calculate_price(alpha_reserve - dx as u64, tao_reserve + dy); + assert_abs_diff_eq!( + actual_price.to_num::(), + target_price.to_num::(), + epsilon = target_price.to_num::() / 1_000_000_000. + ); + } +} diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 6791835b1a..bfcdd5a4f9 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -1,14 +1,11 @@ use core::marker::PhantomData; +use frame_support::ensure; use safe_math::*; -use substrate_fixed::types::{I64F64, U64F64}; -use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use substrate_fixed::types::U64F64; +use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; use super::pallet::*; -use crate::{ - SqrtPrice, - tick::{ActiveTickIndexManager, TickIndex}, -}; /// A struct representing a single swap step with all its parameters and state pub(crate) struct BasicSwapStep @@ -20,22 +17,16 @@ where // Input parameters netuid: NetUid, drop_fees: bool, + requested_delta_in: PaidIn, + limit_price: U64F64, - // Computed values - current_liquidity: U64F64, - possible_delta_in: PaidIn, - - // Ticks and prices (current, limit, edge, target) - target_sqrt_price: SqrtPrice, - limit_sqrt_price: SqrtPrice, - current_sqrt_price: SqrtPrice, - edge_sqrt_price: SqrtPrice, - edge_tick: TickIndex, + // Intermediate calculations + target_price: U64F64, + current_price: U64F64, // Result values - action: SwapStepAction, delta_in: PaidIn, - final_price: SqrtPrice, + final_price: U64F64, fee: PaidIn, _phantom: PhantomData<(T, PaidIn, PaidOut)>, @@ -52,36 +43,28 @@ where pub(crate) fn new( netuid: NetUid, amount_remaining: PaidIn, - limit_sqrt_price: SqrtPrice, + limit_price: U64F64, drop_fees: bool, ) -> Self { - // Calculate prices and ticks - let current_tick = CurrentTick::::get(netuid); - let current_sqrt_price = AlphaSqrtPrice::::get(netuid); - let edge_tick = Self::tick_edge(netuid, current_tick); - let edge_sqrt_price = edge_tick.as_sqrt_price_bounded(); - let fee = Pallet::::calculate_fee_amount(netuid, amount_remaining, drop_fees); - let possible_delta_in = amount_remaining.saturating_sub(fee); + let requested_delta_in = amount_remaining.saturating_sub(fee); + + // Target and current prices + let target_price = Self::price_target(netuid, requested_delta_in); + let current_price = Pallet::::current_price(netuid); - // Target price and quantities - let current_liquidity = U64F64::saturating_from_num(CurrentLiquidity::::get(netuid)); - let target_sqrt_price = - Self::sqrt_price_target(current_liquidity, current_sqrt_price, possible_delta_in); + println!("requested_delta_in = {}", requested_delta_in); + println!("target_price = {}", target_price); Self { netuid, drop_fees, - target_sqrt_price, - limit_sqrt_price, - current_sqrt_price, - edge_sqrt_price, - edge_tick, - possible_delta_in, - current_liquidity, - action: SwapStepAction::Stop, + requested_delta_in, + limit_price, + target_price, + current_price, delta_in: PaidIn::ZERO, - final_price: target_sqrt_price, + final_price: target_price, fee, _phantom: PhantomData, } @@ -98,64 +81,25 @@ where let mut recalculate_fee = false; // Calculate the stopping price: The price at which we either reach the limit price, - // exchange the full amount, or reach the edge price. - if Self::price_is_closer(&self.target_sqrt_price, &self.limit_sqrt_price) - && Self::price_is_closer(&self.target_sqrt_price, &self.edge_sqrt_price) - { - // Case 1. target_quantity is the lowest - // The trade completely happens within one tick, no tick crossing happens. - self.action = SwapStepAction::Stop; - self.final_price = self.target_sqrt_price; - self.delta_in = self.possible_delta_in; - } else if Self::price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) - && Self::price_is_closer(&self.limit_sqrt_price, &self.edge_sqrt_price) - { - // Case 2. lim_quantity is the lowest - // The trade also completely happens within one tick, no tick crossing happens. - self.action = SwapStepAction::Stop; - self.final_price = self.limit_sqrt_price; - self.delta_in = Self::delta_in( - self.current_liquidity, - self.current_sqrt_price, - self.limit_sqrt_price, - ); - recalculate_fee = true; + // or exchange the full amount. + if Self::price_is_closer(&self.target_price, &self.limit_price) { + // Case 1. target_quantity is the lowest, execute in full + self.final_price = self.target_price; + self.delta_in = self.requested_delta_in; } else { - // Case 3. edge_quantity is the lowest - // Tick crossing is likely - self.action = SwapStepAction::Crossing; - self.delta_in = Self::delta_in( - self.current_liquidity, - self.current_sqrt_price, - self.edge_sqrt_price, - ); - self.final_price = self.edge_sqrt_price; + // Case 2. lim_quantity is the lowest + self.final_price = self.limit_price; + self.delta_in = Self::delta_in(self.netuid, self.current_price, self.limit_price); recalculate_fee = true; } - log::trace!("\tAction : {:?}", self.action); - log::trace!( - "\tCurrent Price : {}", - self.current_sqrt_price - .saturating_mul(self.current_sqrt_price) - ); - log::trace!( - "\tTarget Price : {}", - self.target_sqrt_price - .saturating_mul(self.target_sqrt_price) - ); - log::trace!( - "\tLimit Price : {}", - self.limit_sqrt_price.saturating_mul(self.limit_sqrt_price) - ); - log::trace!( - "\tEdge Price : {}", - self.edge_sqrt_price.saturating_mul(self.edge_sqrt_price) - ); + log::trace!("\tCurrent Price : {}", self.current_price); + log::trace!("\tTarget Price : {}", self.target_price); + log::trace!("\tLimit Price : {}", self.limit_price); log::trace!("\tDelta In : {}", self.delta_in); // Because on step creation we calculate fee off the total amount, we might need to - // recalculate it in case if we hit the limit price or the edge price. + // recalculate it in case if we hit the limit price. if recalculate_fee { let u16_max = U64F64::saturating_from_num(u16::MAX); let fee_rate = if self.drop_fees { @@ -170,324 +114,131 @@ where .into(); } - // Now correct the action if we stopped exactly at the edge no matter what was the case - // above. Because order type buy moves the price up and tick semi-open interval doesn't - // include its right point, we cross on buys and stop on sells. - let natural_reason_stop_price = - if Self::price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) { - self.limit_sqrt_price - } else { - self.target_sqrt_price - }; - if natural_reason_stop_price == self.edge_sqrt_price { - self.action = Self::action_on_edge_sqrt_price(); - } + + println!("=========== debug 1 (end of determine_action)"); } /// Process a single step of a swap fn process_swap(&self) -> Result, Error> { - // Hold the fees - Self::add_fees( - self.netuid, - Pallet::::current_liquidity_safe(self.netuid), - self.fee, - ); + // Convert amounts, actual swap happens here let delta_out = Self::convert_deltas(self.netuid, self.delta_in); - // log::trace!("\tDelta Out : {delta_out:?}"); - - if self.action == SwapStepAction::Crossing { - let mut tick = Ticks::::get(self.netuid, self.edge_tick).unwrap_or_default(); - tick.fees_out_tao = I64F64::saturating_from_num(FeeGlobalTao::::get(self.netuid)) - .saturating_sub(tick.fees_out_tao); - tick.fees_out_alpha = - I64F64::saturating_from_num(FeeGlobalAlpha::::get(self.netuid)) - .saturating_sub(tick.fees_out_alpha); - Self::update_liquidity_at_crossing(self.netuid)?; - Ticks::::insert(self.netuid, self.edge_tick, tick); - } - - // Update current price - AlphaSqrtPrice::::set(self.netuid, self.final_price); + log::trace!("\tDelta Out : {delta_out}"); + println!("delta_out = {}", delta_out); + ensure!( + delta_out > 0.into(), + Error::::ReservesTooLow + ); - // Update current tick - let new_current_tick = TickIndex::from_sqrt_price_bounded(self.final_price); - CurrentTick::::set(self.netuid, new_current_tick); + // Hold the fees + Self::add_fees(self.netuid, self.fee); Ok(SwapStepResult { - amount_to_take: self.delta_in.saturating_add(self.fee), fee_paid: self.fee, delta_in: self.delta_in, delta_out, }) } - - pub(crate) fn action(&self) -> SwapStepAction { - self.action - } } impl SwapStep for BasicSwapStep { - fn delta_in( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - sqrt_price_target: SqrtPrice, - ) -> TaoCurrency { - liquidity_curr - .saturating_mul(sqrt_price_target.saturating_sub(sqrt_price_curr)) - .saturating_to_num::() - .into() + fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> TaoCurrency { + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let reserve_weight = SwapReserveWeight::::get(netuid); + TaoCurrency::from(reserve_weight.calculate_quote_delta_in( + price_curr, + price_target, + tao_reserve.into(), + )) } - fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex { - ActiveTickIndexManager::::find_closest_higher( - netuid, - current_tick.next().unwrap_or(TickIndex::MAX), + fn price_target(netuid: NetUid, delta_in: TaoCurrency) -> U64F64 { + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + let reserve_weight = SwapReserveWeight::::get(netuid); + let dy = delta_in; + let dx = Self::convert_deltas(netuid, dy); + reserve_weight.calculate_price( + u64::from(alpha_reserve.saturating_sub(dx)), + u64::from(tao_reserve.saturating_add(dy)), ) - .unwrap_or(TickIndex::MAX) - } - - fn sqrt_price_target( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - delta_in: TaoCurrency, - ) -> SqrtPrice { - let delta_fixed = U64F64::saturating_from_num(delta_in); - - // No liquidity means that price should go to the limit - if liquidity_curr == 0 { - return SqrtPrice::saturating_from_num( - Pallet::::max_price_inner::().to_u64(), - ); - } - - delta_fixed - .safe_div(liquidity_curr) - .saturating_add(sqrt_price_curr) } - fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { - sq_price1 <= sq_price2 + fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool { + price1 <= price2 } - fn action_on_edge_sqrt_price() -> SwapStepAction { - SwapStepAction::Crossing - } - - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: TaoCurrency) { - if current_liquidity == 0 { - return; - } - - let fee_fixed = U64F64::saturating_from_num(fee.to_u64()); - - FeeGlobalTao::::mutate(netuid, |value| { - *value = value.saturating_add(fee_fixed.safe_div(current_liquidity)) - }); + fn add_fees(netuid: NetUid, fee: TaoCurrency) { + FeesTao::::mutate(netuid, |total| *total = total.saturating_add(fee)) } fn convert_deltas(netuid: NetUid, delta_in: TaoCurrency) -> AlphaCurrency { - // Skip conversion if delta_in is zero - if delta_in.is_zero() { - return AlphaCurrency::ZERO; - } - - let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); - let sqrt_price_curr = AlphaSqrtPrice::::get(netuid); - let delta_fixed = SqrtPrice::saturating_from_num(delta_in.to_u64()); - - // Calculate result based on order type with proper fixed-point math - // Using safe math operations throughout to prevent overflows - let result = { - // (liquidity_curr * sqrt_price_curr + delta_fixed) * sqrt_price_curr; - let a = liquidity_curr - .saturating_mul(sqrt_price_curr) - .saturating_add(delta_fixed) - .saturating_mul(sqrt_price_curr); - // liquidity_curr / a; - let b = liquidity_curr.safe_div(a); - // b * delta_fixed; - b.saturating_mul(delta_fixed) - }; - - result.saturating_to_num::().into() - } - - fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error> { - let mut liquidity_curr = CurrentLiquidity::::get(netuid); - let current_tick_index = TickIndex::current_bounded::(netuid); - - // Find the appropriate tick based on order type - let tick = { - // Self::find_closest_higher_active_tick(netuid, current_tick_index), - let upper_tick = ActiveTickIndexManager::::find_closest_higher( - netuid, - current_tick_index.next().unwrap_or(TickIndex::MAX), - ) - .unwrap_or(TickIndex::MAX); - Ticks::::get(netuid, upper_tick) - } - .ok_or(Error::::InsufficientLiquidity)?; - - let liquidity_update_abs_u64 = tick.liquidity_net_as_u64(); - - // Update liquidity based on the sign of liquidity_net and the order type - liquidity_curr = if tick.liquidity_net >= 0 { - liquidity_curr.saturating_add(liquidity_update_abs_u64) - } else { - liquidity_curr.saturating_sub(liquidity_update_abs_u64) - }; - - CurrentLiquidity::::set(netuid, liquidity_curr); - - Ok(()) + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let reserve_weight = SwapReserveWeight::::get(netuid); + let e = reserve_weight.exp_quote_base(tao_reserve.into(), delta_in.into()); + let one = U64F64::from_num(1); + let alpha_reserve_fixed = U64F64::from_num(alpha_reserve); + AlphaCurrency::from( + alpha_reserve_fixed + .saturating_mul(one.saturating_sub(e)) + .saturating_to_num::(), + ) } } impl SwapStep for BasicSwapStep { - fn delta_in( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - sqrt_price_target: SqrtPrice, - ) -> AlphaCurrency { - let one = U64F64::saturating_from_num(1); - - liquidity_curr - .saturating_mul( - one.safe_div(sqrt_price_target.into()) - .saturating_sub(one.safe_div(sqrt_price_curr)), - ) - .saturating_to_num::() - .into() - } - - fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex { - let current_price: SqrtPrice = AlphaSqrtPrice::::get(netuid); - let current_tick_price = current_tick.as_sqrt_price_bounded(); - let is_active = ActiveTickIndexManager::::tick_is_active(netuid, current_tick); - - if is_active && current_price > current_tick_price { - return ActiveTickIndexManager::::find_closest_lower(netuid, current_tick) - .unwrap_or(TickIndex::MIN); - } - - ActiveTickIndexManager::::find_closest_lower( - netuid, - current_tick.prev().unwrap_or(TickIndex::MIN), - ) - .unwrap_or(TickIndex::MIN) + fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> AlphaCurrency { + let alpha_reserve = T::TaoReserve::reserve(netuid); + let reserve_weight = SwapReserveWeight::::get(netuid); + AlphaCurrency::from(reserve_weight.calculate_base_delta_in( + price_curr, + price_target, + alpha_reserve.into(), + )) } - fn sqrt_price_target( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - delta_in: AlphaCurrency, - ) -> SqrtPrice { - let delta_fixed = U64F64::saturating_from_num(delta_in); - let one = U64F64::saturating_from_num(1); - - // No liquidity means that price should go to the limit - if liquidity_curr == 0 { - return SqrtPrice::saturating_from_num( - Pallet::::min_price_inner::().to_u64(), - ); - } - - one.safe_div( - delta_fixed - .safe_div(liquidity_curr) - .saturating_add(one.safe_div(sqrt_price_curr)), + fn price_target(netuid: NetUid, delta_in: AlphaCurrency) -> U64F64 { + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + let reserve_weight = SwapReserveWeight::::get(netuid); + let dx = delta_in; + let dy = Self::convert_deltas(netuid, dx); + reserve_weight.calculate_price( + u64::from(alpha_reserve.saturating_add(dx)), + u64::from(tao_reserve.saturating_sub(dy)), ) } - fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { - sq_price1 >= sq_price2 - } - - fn action_on_edge_sqrt_price() -> SwapStepAction { - SwapStepAction::Stop + fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool { + price1 >= price2 } - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: AlphaCurrency) { - if current_liquidity == 0 { - return; - } - - let fee_fixed = U64F64::saturating_from_num(fee.to_u64()); - - FeeGlobalAlpha::::mutate(netuid, |value| { - *value = value.saturating_add(fee_fixed.safe_div(current_liquidity)) - }); + fn add_fees(netuid: NetUid, fee: AlphaCurrency) { + FeesAlpha::::mutate(netuid, |total| *total = total.saturating_add(fee)) } fn convert_deltas(netuid: NetUid, delta_in: AlphaCurrency) -> TaoCurrency { - // Skip conversion if delta_in is zero - if delta_in.is_zero() { - return TaoCurrency::ZERO; - } - - let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); - let sqrt_price_curr = AlphaSqrtPrice::::get(netuid); - let delta_fixed = SqrtPrice::saturating_from_num(delta_in.to_u64()); - - // Calculate result based on order type with proper fixed-point math - // Using safe math operations throughout to prevent overflows - let result = { - // liquidity_curr / (liquidity_curr / sqrt_price_curr + delta_fixed); - let denom = liquidity_curr - .safe_div(sqrt_price_curr) - .saturating_add(delta_fixed); - let a = liquidity_curr.safe_div(denom); - // a * sqrt_price_curr; - let b = a.saturating_mul(sqrt_price_curr); - - // delta_fixed * b; - delta_fixed.saturating_mul(b) - }; - - result.saturating_to_num::().into() - } - - fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error> { - let mut liquidity_curr = CurrentLiquidity::::get(netuid); - let current_tick_index = TickIndex::current_bounded::(netuid); - - // Find the appropriate tick based on order type - let tick = { - // Self::find_closest_lower_active_tick(netuid, current_tick_index) - let current_price = AlphaSqrtPrice::::get(netuid); - let current_tick_price = current_tick_index.as_sqrt_price_bounded(); - let is_active = ActiveTickIndexManager::::tick_is_active(netuid, current_tick_index); - - let lower_tick = if is_active && current_price > current_tick_price { - ActiveTickIndexManager::::find_closest_lower(netuid, current_tick_index) - .unwrap_or(TickIndex::MIN) - } else { - ActiveTickIndexManager::::find_closest_lower( - netuid, - current_tick_index.prev().unwrap_or(TickIndex::MIN), - ) - .unwrap_or(TickIndex::MIN) - }; - Ticks::::get(netuid, lower_tick) - } - .ok_or(Error::::InsufficientLiquidity)?; - - let liquidity_update_abs_u64 = tick.liquidity_net_as_u64(); - - // Update liquidity based on the sign of liquidity_net and the order type - liquidity_curr = if tick.liquidity_net >= 0 { - liquidity_curr.saturating_sub(liquidity_update_abs_u64) - } else { - liquidity_curr.saturating_add(liquidity_update_abs_u64) - }; - - CurrentLiquidity::::set(netuid, liquidity_curr); - - Ok(()) + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let reserve_weight = SwapReserveWeight::::get(netuid); + let e = reserve_weight.exp_base_quote(alpha_reserve.into(), delta_in.into()); + + println!("alpha_reserve = {}", alpha_reserve); + println!("tao_reserve = {}", tao_reserve); + println!("reserve_weight = {:?}", reserve_weight); + println!("e = {}", e); + + let one = U64F64::from_num(1); + let tao_reserve_fixed = U64F64::from_num(u64::from(tao_reserve)); + TaoCurrency::from( + tao_reserve_fixed + .saturating_mul(one.saturating_sub(e)) + .saturating_to_num::(), + ) } } @@ -498,49 +249,25 @@ where PaidOut: Currency, { /// Get the input amount needed to reach the target price - fn delta_in( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - sqrt_price_target: SqrtPrice, - ) -> PaidIn; + fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> PaidIn; - /// Get the tick at the current tick edge. - /// - /// If anything is wrong with tick math and it returns Err, we just abort the deal, i.e. return - /// the edge that is impossible to execute - fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex; + /// Get the target price based on the input amount + fn price_target(netuid: NetUid, delta_in: PaidIn) -> U64F64; - /// Get the target square root price based on the input amount - /// - /// This is the price that would be reached if - /// - There are no liquidity positions other than protocol liquidity - /// - Full delta_in amount is executed - fn sqrt_price_target( - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - delta_in: PaidIn, - ) -> SqrtPrice; - - /// Returns True if sq_price1 is closer to the current price than sq_price2 + /// Returns True if price1 is closer to the current price than price2 /// in terms of order direction. - /// For buying: sq_price1 <= sq_price2 - /// For selling: sq_price1 >= sq_price2 - fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool; - - /// Get swap step action on the edge sqrt price. - fn action_on_edge_sqrt_price() -> SwapStepAction; + /// For buying: price1 <= price2 + /// For selling: price1 >= price2 + fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool; /// Add fees to the global fee counters - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: PaidIn); + fn add_fees(netuid: NetUid, fee: PaidIn); /// Convert input amount (delta_in) to output amount (delta_out) /// - /// This is the core method of uniswap V3 that tells how much output token is given for an - /// amount of input token within one price tick. + /// This is the core method of the swap that tells how much output token is given for an + /// amount of input token fn convert_deltas(netuid: NetUid, delta_in: PaidIn) -> PaidOut; - - /// Update liquidity when crossing a tick - fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error>; } #[derive(Debug, PartialEq)] @@ -549,14 +276,7 @@ where PaidIn: Currency, PaidOut: Currency, { - pub(crate) amount_to_take: PaidIn, pub(crate) fee_paid: PaidIn, pub(crate) delta_in: PaidIn, pub(crate) delta_out: PaidOut, } - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SwapStepAction { - Crossing, - Stop, -} diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 4013248abb..ecdb810b24 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -6,76 +6,35 @@ )] use approx::assert_abs_diff_eq; -use frame_support::{assert_err, assert_noop, assert_ok}; -use sp_arithmetic::helpers_128bit; +use frame_support::{ + //assert_err, + assert_noop, + assert_ok, +}; +use sp_arithmetic::{ + //helpers_128bit, + Perquintill, +}; use sp_runtime::DispatchError; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::NetUid; use subtensor_swap_interface::Order as OrderT; use super::*; +use crate::mock::*; use crate::pallet::swap_step::*; -use crate::{SqrtPrice, mock::*}; - -// this function is used to convert price (NON-SQRT price!) to TickIndex. it's only utility for -// testing, all the implementation logic is based on sqrt prices -fn price_to_tick(price: f64) -> TickIndex { - let price_sqrt: SqrtPrice = SqrtPrice::from_num(price.sqrt()); - // Handle potential errors in the conversion - match TickIndex::try_from_sqrt_price(price_sqrt) { - Ok(mut tick) => { - // Ensure the tick is within bounds - if tick > TickIndex::MAX { - tick = TickIndex::MAX; - } else if tick < TickIndex::MIN { - tick = TickIndex::MIN; - } - tick - } - // Default to a reasonable value when conversion fails - Err(_) => { - if price > 1.0 { - TickIndex::MAX - } else { - TickIndex::MIN - } - } - } -} - -fn get_ticked_prices_around_current_price() -> (f64, f64) { - // Get current price, ticks around it, and prices on the tick edges for test cases - let netuid = NetUid::from(1); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - let current_tick = CurrentTick::::get(netuid); - // Low and high prices that match to a lower and higher tick that doesn't contain the current price - let current_price_low_sqrt = current_tick.as_sqrt_price_bounded(); - let current_price_high_sqrt = current_tick.next().unwrap().as_sqrt_price_bounded(); - let current_price_low = U96F32::from_num(current_price_low_sqrt * current_price_low_sqrt); - let current_price_high = U96F32::from_num(current_price_high_sqrt * current_price_high_sqrt); +// Run all tests: +// cargo test --package pallet-subtensor-swap --lib -- pallet::tests --nocapture - ( - current_price_low.to_num::(), - current_price_high.to_num::() + 0.000000001, - ) +#[allow(dead_code)] +fn get_min_price() -> U64F64 { + U64F64::from_num(Pallet::::min_price_inner::()) / U64F64::from_num(1_000_000_000) } -// this function is used to convert tick index NON-SQRT (!) price. it's only utility for -// testing, all the implementation logic is based on sqrt prices -fn tick_to_price(tick: TickIndex) -> f64 { - // Handle errors gracefully - match tick.try_to_sqrt_price() { - Ok(price_sqrt) => (price_sqrt * price_sqrt).to_num::(), - Err(_) => { - // Return a sensible default based on whether the tick is above or below the valid range - if tick > TickIndex::MAX { - tick_to_price(TickIndex::MAX) // Use the max valid tick price - } else { - tick_to_price(TickIndex::MIN) // Use the min valid tick price - } - } - } +#[allow(dead_code)] +fn get_max_price() -> U64F64 { + U64F64::from_num(Pallet::::max_price_inner::()) / U64F64::from_num(1_000_000_000) } mod dispatchables { @@ -114,6 +73,303 @@ mod dispatchables { }); } + fn perquintill_to_f64(p: Perquintill) -> f64 { + let parts = p.deconstruct() as f64; + parts / 1_000_000_000_000_000_000_f64 + } + + /// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::dispatchables::test_adjust_protocol_liquidity_happy --exact --nocapture + #[test] + fn test_adjust_protocol_liquidity_happy() { + // test case: tao_delta, alpha_delta + [ + (0_u64, 0_u64), + (0_u64, 1_u64), + (1_u64, 0_u64), + (1_u64, 1_u64), + (0_u64, 10_u64), + (10_u64, 0_u64), + (10_u64, 10_u64), + (0_u64, 100_u64), + (100_u64, 0_u64), + (100_u64, 100_u64), + (0_u64, 1_000_u64), + (1_000_u64, 0_u64), + (1_000_u64, 1_000_u64), + (1_000_000_u64, 0_u64), + (0_u64, 1_000_000_u64), + (1_000_000_u64, 1_000_000_u64), + (1_000_000_000_u64, 0_u64), + (0_u64, 1_000_000_000_u64), + (1_000_000_000_u64, 1_000_000_000_u64), + (1_000_000_000_000_u64, 0_u64), + (0_u64, 1_000_000_000_000_u64), + (1_000_000_000_000_u64, 1_000_000_000_000_u64), + (1_u64, 2_u64), + (2_u64, 1_u64), + (10_u64, 20_u64), + (20_u64, 10_u64), + (100_u64, 200_u64), + (200_u64, 100_u64), + (1_000_u64, 2_000_u64), + (2_000_u64, 1_000_u64), + (1_000_000_u64, 2_000_000_u64), + (2_000_000_u64, 1_000_000_u64), + (1_000_000_000_u64, 2_000_000_000_u64), + (2_000_000_000_u64, 1_000_000_000_u64), + (1_000_000_000_000_u64, 2_000_000_000_000_u64), + (2_000_000_000_000_u64, 1_000_000_000_000_u64), + (1_234_567_u64, 2_432_765_u64), + (1_234_567_u64, 2_432_765_890_u64), + ] + .into_iter() + .for_each(|(tao_delta, alpha_delta)| { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + let tao_delta = TaoCurrency::from(tao_delta); + let alpha_delta = AlphaCurrency::from(alpha_delta); + + // Initialize reserves and price + let tao = TaoCurrency::from(1_000_000_000_000_u64); + let alpha = AlphaCurrency::from(4_000_000_000_000_u64); + TaoReserve::set_mock_reserve(netuid, tao); + AlphaReserve::set_mock_reserve(netuid, alpha); + let price_before = Swap::current_price(netuid); + + // Adjust reserves + Swap::adjust_protocol_liquidity(netuid, tao_delta, alpha_delta); + TaoReserve::set_mock_reserve(netuid, tao + tao_delta); + AlphaReserve::set_mock_reserve(netuid, alpha + alpha_delta); + + // Check that price didn't change + let price_after = Swap::current_price(netuid); + assert_abs_diff_eq!( + price_before.to_num::(), + price_after.to_num::(), + epsilon = price_before.to_num::() / 1_000_000_000_000. + ); + + // Check that reserve weight was properly updated + let new_tao = u64::from(tao + tao_delta) as f64; + let new_alpha = u64::from(alpha + alpha_delta) as f64; + let expected_quote_weight = + new_tao / (new_alpha * price_before.to_num::() + new_tao); + let expected_quote_weight_delta = expected_quote_weight - 0.5; + let res_weights = SwapReserveWeight::::get(netuid); + let actual_quote_weight_delta = + perquintill_to_f64(res_weights.get_quote_weight()) - 0.5; + let eps = expected_quote_weight / 1_000_000_000_000.; + assert_abs_diff_eq!( + expected_quote_weight_delta, + actual_quote_weight_delta, + epsilon = eps + ); + }); + }); + } + + /// This test case verifies that small gradual injections (like emissions in every block) + /// in the worst case + /// - Do not cause price to change + /// - Result in the same weight change as one large injection + /// + /// This is a long test that only tests validity of weights math. Run again if changing + /// ReserveWeight::update_weights_for_added_liquidity + /// + /// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::dispatchables::test_adjust_protocol_liquidity_deltas --exact --nocapture + #[ignore] + #[test] + fn test_adjust_protocol_liquidity_deltas() { + // The number of times (blocks) over which gradual injections will be made + // One year price drift due to precision is under 1e-6 + const ITERATIONS: u64 = 2_700_000; + const PRICE_PRECISION: f64 = 0.000_001; + const PREC_LARGE_DELTA: f64 = 0.001; + const WEIGHT_PRECISION: f64 = 0.000_000_000_000_000_001; + + let initial_tao_reserve = TaoCurrency::from(1_000_000_000_000_000_u64); + let initial_alpha_reserve = AlphaCurrency::from(10_000_000_000_000_000_u64); + + // test case: tao_delta, alpha_delta, price_precision + [ + (0_u64, 0_u64, PRICE_PRECISION), + (0_u64, 1_u64, PRICE_PRECISION), + (1_u64, 0_u64, PRICE_PRECISION), + (1_u64, 1_u64, PRICE_PRECISION), + (0_u64, 10_u64, PRICE_PRECISION), + (10_u64, 0_u64, PRICE_PRECISION), + (10_u64, 10_u64, PRICE_PRECISION), + (0_u64, 100_u64, PRICE_PRECISION), + (100_u64, 0_u64, PRICE_PRECISION), + (100_u64, 100_u64, PRICE_PRECISION), + (0_u64, 987_u64, PRICE_PRECISION), + (987_u64, 0_u64, PRICE_PRECISION), + (876_u64, 987_u64, PRICE_PRECISION), + (0_u64, 1_000_u64, PRICE_PRECISION), + (1_000_u64, 0_u64, PRICE_PRECISION), + (1_000_u64, 1_000_u64, PRICE_PRECISION), + (0_u64, 1_234_u64, PRICE_PRECISION), + (1_234_u64, 0_u64, PRICE_PRECISION), + (1_234_u64, 4_321_u64, PRICE_PRECISION), + (1_234_000_u64, 4_321_000_u64, PREC_LARGE_DELTA), + (1_234_u64, 4_321_000_u64, PREC_LARGE_DELTA), + ] + .into_iter() + .for_each(|(tao_delta, alpha_delta, price_precision)| { + new_test_ext().execute_with(|| { + let netuid1 = NetUid::from(1); + + let tao_delta = TaoCurrency::from(tao_delta); + let alpha_delta = AlphaCurrency::from(alpha_delta); + + // Initialize realistically large reserves + let mut tao = initial_tao_reserve; + let mut alpha = initial_alpha_reserve; + TaoReserve::set_mock_reserve(netuid1, tao); + AlphaReserve::set_mock_reserve(netuid1, alpha); + let price_before = Swap::current_price(netuid1); + + // Adjust reserves gradually + for _ in 0..ITERATIONS { + Swap::adjust_protocol_liquidity(netuid1, tao_delta, alpha_delta); + tao += tao_delta; + alpha += alpha_delta; + TaoReserve::set_mock_reserve(netuid1, tao); + AlphaReserve::set_mock_reserve(netuid1, alpha); + } + + // Check that price didn't change + let price_after = Swap::current_price(netuid1); + assert_abs_diff_eq!( + price_before.to_num::(), + price_after.to_num::(), + epsilon = price_precision + ); + + ///////////////////////// + + // Now do one-time big injection with another netuid and compare weights + + let netuid2 = NetUid::from(2); + + // Initialize same large reserves + TaoReserve::set_mock_reserve(netuid2, initial_tao_reserve); + AlphaReserve::set_mock_reserve(netuid2, initial_alpha_reserve); + + // Adjust reserves by one large amount at once + let tao_delta_once = TaoCurrency::from(ITERATIONS * u64::from(tao_delta)); + let alpha_delta_once = AlphaCurrency::from(ITERATIONS * u64::from(alpha_delta)); + Swap::adjust_protocol_liquidity(netuid2, tao_delta_once, alpha_delta_once); + TaoReserve::set_mock_reserve(netuid2, initial_tao_reserve + tao_delta_once); + AlphaReserve::set_mock_reserve(netuid2, initial_alpha_reserve + alpha_delta_once); + + // Compare reserve weights for netuid 1 and 2 + let res_weights1 = SwapReserveWeight::::get(netuid1); + let res_weights2 = SwapReserveWeight::::get(netuid2); + let actual_quote_weight1 = perquintill_to_f64(res_weights1.get_quote_weight()); + let actual_quote_weight2 = perquintill_to_f64(res_weights2.get_quote_weight()); + assert_abs_diff_eq!( + actual_quote_weight1, + actual_quote_weight2, + epsilon = WEIGHT_PRECISION + ); + }); + }); + } + + /// Should work ok when initial alpha is zero + /// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::dispatchables::test_adjust_protocol_liquidity_zero_alpha --exact --nocapture + #[test] + fn test_adjust_protocol_liquidity_zero_alpha() { + // test case: tao_delta, alpha_delta + [ + (0_u64, 0_u64), + (0_u64, 1_u64), + (1_u64, 0_u64), + (1_u64, 1_u64), + (0_u64, 10_u64), + (10_u64, 0_u64), + (10_u64, 10_u64), + (0_u64, 100_u64), + (100_u64, 0_u64), + (100_u64, 100_u64), + (0_u64, 1_000_u64), + (1_000_u64, 0_u64), + (1_000_u64, 1_000_u64), + (1_000_000_u64, 0_u64), + (0_u64, 1_000_000_u64), + (1_000_000_u64, 1_000_000_u64), + (1_000_000_000_u64, 0_u64), + (0_u64, 1_000_000_000_u64), + (1_000_000_000_u64, 1_000_000_000_u64), + (1_000_000_000_000_u64, 0_u64), + (0_u64, 1_000_000_000_000_u64), + (1_000_000_000_000_u64, 1_000_000_000_000_u64), + (1_u64, 2_u64), + (2_u64, 1_u64), + (10_u64, 20_u64), + (20_u64, 10_u64), + (100_u64, 200_u64), + (200_u64, 100_u64), + (1_000_u64, 2_000_u64), + (2_000_u64, 1_000_u64), + (1_000_000_u64, 2_000_000_u64), + (2_000_000_u64, 1_000_000_u64), + (1_000_000_000_u64, 2_000_000_000_u64), + (2_000_000_000_u64, 1_000_000_000_u64), + (1_000_000_000_000_u64, 2_000_000_000_000_u64), + (2_000_000_000_000_u64, 1_000_000_000_000_u64), + (1_234_567_u64, 2_432_765_u64), + (1_234_567_u64, 2_432_765_890_u64), + ] + .into_iter() + .for_each(|(tao_delta, alpha_delta)| { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + let tao_delta = TaoCurrency::from(tao_delta); + let alpha_delta = AlphaCurrency::from(alpha_delta); + + // Initialize reserves and price + // broken state: Zero price because of zero alpha reserve + let tao = TaoCurrency::from(1_000_000_000_000_u64); + let alpha = AlphaCurrency::from(0_u64); + TaoReserve::set_mock_reserve(netuid, tao); + AlphaReserve::set_mock_reserve(netuid, alpha); + let price_before = Swap::current_price(netuid); + assert_eq!(price_before, U64F64::from_num(0)); + let new_tao = u64::from(tao + tao_delta) as f64; + let new_alpha = u64::from(alpha + alpha_delta) as f64; + + // Adjust reserves + Swap::adjust_protocol_liquidity(netuid, tao_delta, alpha_delta); + TaoReserve::set_mock_reserve(netuid, tao + tao_delta); + AlphaReserve::set_mock_reserve(netuid, alpha + alpha_delta); + + let res_weights = SwapReserveWeight::::get(netuid); + let actual_quote_weight = perquintill_to_f64(res_weights.get_quote_weight()); + + // Check that price didn't change + let price_after = Swap::current_price(netuid); + if new_alpha == 0. { + // If the pool state is still broken (∆x = 0), no change + assert_eq!(actual_quote_weight, 0.5); + assert_eq!(price_after, U64F64::from_num(0)); + } else { + // Price got fixed + let expected_price = new_tao / new_alpha; + assert_abs_diff_eq!( + expected_price, + price_after.to_num::(), + epsilon = price_before.to_num::() / 1_000_000_000_000. + ); + assert_eq!(actual_quote_weight, 0.5); + } + }); + }); + } + // #[test] // fn test_toggle_user_liquidity() { // new_test_ext().execute_with(|| { @@ -158,597 +414,599 @@ fn test_swap_initialization() { let netuid = NetUid::from(1); // Get reserves from the mock provider - let tao = TaoReserve::reserve(netuid.into()); - let alpha = AlphaReserve::reserve(netuid.into()); + // let tao = TaoReserve::reserve(netuid.into()); + // let alpha = AlphaReserve::reserve(netuid.into()); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - assert!(SwapV3Initialized::::get(netuid)); + assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + assert!(PalSwapInitialized::::get(netuid)); // Verify current price is set - let sqrt_price = AlphaSqrtPrice::::get(netuid); - let expected_sqrt_price = U64F64::from_num(0.5_f64); + let price = Pallet::::current_price(netuid); + let expected_price = U64F64::from_num(0.5_f64); assert_abs_diff_eq!( - sqrt_price.to_num::(), - expected_sqrt_price.to_num::(), + price.to_num::(), + expected_price.to_num::(), epsilon = 0.000000001 ); - // Verify that current tick is set - let current_tick = CurrentTick::::get(netuid); - let expected_current_tick = TickIndex::from_sqrt_price_bounded(expected_sqrt_price); - assert_eq!(current_tick, expected_current_tick); - - // Calculate expected liquidity - let expected_liquidity = - helpers_128bit::sqrt((tao.to_u64() as u128).saturating_mul(alpha.to_u64() as u128)) - as u64; - - // Get the protocol account - let protocol_account_id = Pallet::::protocol_account_id(); - - // Verify position created for protocol account - let positions = Positions::::iter_prefix_values((netuid, protocol_account_id)) - .collect::>(); - assert_eq!(positions.len(), 1); - - let position = &positions[0]; - assert_eq!(position.liquidity, expected_liquidity); - assert_eq!(position.tick_low, TickIndex::MIN); - assert_eq!(position.tick_high, TickIndex::MAX); - assert_eq!(position.fees_tao, 0); - assert_eq!(position.fees_alpha, 0); - - // Verify ticks were created - let tick_low = Ticks::::get(netuid, TickIndex::MIN).unwrap(); - let tick_high = Ticks::::get(netuid, TickIndex::MAX).unwrap(); - - // Check liquidity values - assert_eq!(tick_low.liquidity_net, expected_liquidity as i128); - assert_eq!(tick_low.liquidity_gross, expected_liquidity); - assert_eq!(tick_high.liquidity_net, -(expected_liquidity as i128)); - assert_eq!(tick_high.liquidity_gross, expected_liquidity); - - // Verify current liquidity is set - assert_eq!(CurrentLiquidity::::get(netuid), expected_liquidity); + // Verify that swap reserve weight is initialized + let reserve_weight = SwapReserveWeight::::get(netuid); + assert_eq!( + reserve_weight.get_quote_weight(), + Perquintill::from_rational(1_u64, 2_u64), + ); + + // // Calculate expected liquidity + // let expected_liquidity = + // helpers_128bit::sqrt((tao.to_u64() as u128).saturating_mul(alpha.to_u64() as u128)) + // as u64; + + // // Get the protocol account + // let protocol_account_id = Pallet::::protocol_account_id(); + + // // Verify position created for protocol account + // let positions = Positions::::iter_prefix_values((netuid, protocol_account_id)) + // .collect::>(); + // assert_eq!(positions.len(), 1); + + // let position = &positions[0]; + // assert_eq!(position.liquidity, expected_liquidity); + // assert_eq!(position.fees_tao, 0); + // assert_eq!(position.fees_alpha, 0); + + todo!(); }); } // Test adding liquidity on top of the existing protocol liquidity #[test] fn test_add_liquidity_basic() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - assert_eq!(max_tick, TickIndex::MAX); - - assert_ok!(Pallet::::maybe_initialize_v3(NetUid::from(1))); - let current_price = Pallet::::current_price(NetUid::from(1)).to_num::(); - let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); - - // As a user add liquidity with all possible corner cases - // - Initial price is 0.25 - // - liquidity is expressed in RAO units - // Test case is (price_low, price_high, liquidity, tao, alpha) - [ - // Repeat the protocol liquidity at maximum range: Expect all the same values - ( - min_price, - max_price, - 2_000_000_000_u64, - 1_000_000_000_u64, - 4_000_000_000_u64, - ), - // Repeat the protocol liquidity at current to max range: Expect the same alpha - ( - current_price_high, - max_price, - 2_000_000_000_u64, - 0, - 4_000_000_000, - ), - // Repeat the protocol liquidity at min to current range: Expect all the same tao - ( - min_price, - current_price_low, - 2_000_000_000_u64, - 1_000_000_000, - 0, - ), - // Half to double price - just some sane wothdraw amounts - (0.125, 0.5, 2_000_000_000_u64, 293_000_000, 1_171_000_000), - // Both below price - tao is non-zero, alpha is zero - (0.12, 0.13, 2_000_000_000_u64, 28_270_000, 0), - // Both above price - tao is zero, alpha is non-zero - (0.3, 0.4, 2_000_000_000_u64, 0, 489_200_000), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3, v.4)) - .for_each( - |(netuid, price_low, price_high, liquidity, expected_tao, expected_alpha)| { - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Calculate ticks (assuming tick math is tested separately) - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - - // Get tick infos and liquidity before adding (to account for protocol liquidity) - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = - Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); - - // Add liquidity - let (position_id, tao, alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - assert_abs_diff_eq!(tao, expected_tao, epsilon = tao / 1000); - assert_abs_diff_eq!(alpha, expected_alpha, epsilon = alpha / 1000); - - // Check that low and high ticks appear in the state and are properly updated - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = liquidity as i128; - let expected_liquidity_gross_low = liquidity; - let expected_liquidity_net_high = -(liquidity as i128); - let expected_liquidity_gross_high = liquidity; - - assert_eq!( - tick_low_info.liquidity_net - tick_low_info_before.liquidity_net, - expected_liquidity_net_low, - ); - assert_eq!( - tick_low_info.liquidity_gross - tick_low_info_before.liquidity_gross, - expected_liquidity_gross_low, - ); - assert_eq!( - tick_high_info.liquidity_net - tick_high_info_before.liquidity_net, - expected_liquidity_net_high, - ); - assert_eq!( - tick_high_info.liquidity_gross - tick_high_info_before.liquidity_gross, - expected_liquidity_gross_high, - ); - - // Liquidity position at correct ticks - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 1 - ); - - let position = - Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); - assert_eq!(position.liquidity, liquidity); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); - - // Current liquidity is updated only when price range includes the current price - let expected_liquidity = - if (price_high > current_price) && (price_low <= current_price) { - liquidity_before + liquidity - } else { - liquidity_before - }; - - assert_eq!(CurrentLiquidity::::get(netuid), expected_liquidity) - }, - ); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // let min_price = tick_to_price(TickIndex::MIN); + // let max_price = tick_to_price(TickIndex::MAX); + // let max_tick = price_to_tick(max_price); + // assert_eq!(max_tick, TickIndex::MAX); + + // assert_ok!(Pallet::::maybe_initialize_palswap(NetUid::from(1))); + // let current_price = Pallet::::current_price(NetUid::from(1)).to_num::(); + // let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); + + // // As a user add liquidity with all possible corner cases + // // - Initial price is 0.25 + // // - liquidity is expressed in RAO units + // // Test case is (price_low, price_high, liquidity, tao, alpha) + // [ + // // Repeat the protocol liquidity at maximum range: Expect all the same values + // ( + // min_price, + // max_price, + // 2_000_000_000_u64, + // 1_000_000_000_u64, + // 4_000_000_000_u64, + // ), + // // Repeat the protocol liquidity at current to max range: Expect the same alpha + // ( + // current_price_high, + // max_price, + // 2_000_000_000_u64, + // 0, + // 4_000_000_000, + // ), + // // Repeat the protocol liquidity at min to current range: Expect all the same tao + // ( + // min_price, + // current_price_low, + // 2_000_000_000_u64, + // 1_000_000_000, + // 0, + // ), + // // Half to double price - just some sane wothdraw amounts + // (0.125, 0.5, 2_000_000_000_u64, 293_000_000, 1_171_000_000), + // // Both below price - tao is non-zero, alpha is zero + // (0.12, 0.13, 2_000_000_000_u64, 28_270_000, 0), + // // Both above price - tao is zero, alpha is non-zero + // (0.3, 0.4, 2_000_000_000_u64, 0, 489_200_000), + // ] + // .into_iter() + // .enumerate() + // .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3, v.4)) + // .for_each( + // |(netuid, price_low, price_high, liquidity, expected_tao, expected_alpha)| { + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // // Calculate ticks (assuming tick math is tested separately) + // let tick_low = price_to_tick(price_low); + // let tick_high = price_to_tick(price_high); + + // // Get tick infos and liquidity before adding (to account for protocol liquidity) + // let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); + // let tick_high_info_before = + // Ticks::::get(netuid, tick_high).unwrap_or_default(); + // let liquidity_before = CurrentLiquidity::::get(netuid); + + // // Add liquidity + // let (position_id, tao, alpha) = Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // tick_low, + // tick_high, + // liquidity, + // ) + // .unwrap(); + + // assert_abs_diff_eq!(tao, expected_tao, epsilon = tao / 1000); + // assert_abs_diff_eq!(alpha, expected_alpha, epsilon = alpha / 1000); + + // // Check that low and high ticks appear in the state and are properly updated + // let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); + // let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); + // let expected_liquidity_net_low = liquidity as i128; + // let expected_liquidity_gross_low = liquidity; + // let expected_liquidity_net_high = -(liquidity as i128); + // let expected_liquidity_gross_high = liquidity; + + // assert_eq!( + // tick_low_info.liquidity_net - tick_low_info_before.liquidity_net, + // expected_liquidity_net_low, + // ); + // assert_eq!( + // tick_low_info.liquidity_gross - tick_low_info_before.liquidity_gross, + // expected_liquidity_gross_low, + // ); + // assert_eq!( + // tick_high_info.liquidity_net - tick_high_info_before.liquidity_net, + // expected_liquidity_net_high, + // ); + // assert_eq!( + // tick_high_info.liquidity_gross - tick_high_info_before.liquidity_gross, + // expected_liquidity_gross_high, + // ); + + // // Liquidity position at correct ticks + // assert_eq!( + // Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + // 1 + // ); + + // let position = + // Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); + // assert_eq!(position.liquidity, liquidity); + // assert_eq!(position.tick_low, tick_low); + // assert_eq!(position.tick_high, tick_high); + // assert_eq!(position.fees_alpha, 0); + // assert_eq!(position.fees_tao, 0); + + // // Current liquidity is updated only when price range includes the current price + // let expected_liquidity = + // if (price_high > current_price) && (price_low <= current_price) { + // liquidity_before + liquidity + // } else { + // liquidity_before + // }; + + // assert_eq!(CurrentLiquidity::::get(netuid), expected_liquidity) + // }, + // ); + // }); } #[test] fn test_add_liquidity_max_limit_enforced() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let liquidity = 2_000_000_000_u64; - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - let limit = MaxPositions::get() as usize; - - for _ in 0..limit { - Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - TickIndex::MIN, - TickIndex::MAX, - liquidity, - ) - .unwrap(); - } - - let test_result = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - TickIndex::MIN, - TickIndex::MAX, - liquidity, - ); - - assert_err!(test_result, Error::::MaxPositionsExceeded); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // let netuid = NetUid::from(1); + // let liquidity = 2_000_000_000_u64; + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // let limit = MaxPositions::get() as usize; + + // for _ in 0..limit { + // Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // TickIndex::MIN, + // TickIndex::MAX, + // liquidity, + // ) + // .unwrap(); + // } + + // let test_result = Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // TickIndex::MIN, + // TickIndex::MAX, + // liquidity, + // ); + + // assert_err!(test_result, Error::::MaxPositionsExceeded); + // }); } #[test] fn test_add_liquidity_out_of_bounds() { - new_test_ext().execute_with(|| { - [ - // For our tests, we'll construct TickIndex values that are intentionally - // outside the valid range for testing purposes only - ( - TickIndex::new_unchecked(TickIndex::MIN.get() - 1), - TickIndex::MAX, - 1_000_000_000_u64, - ), - ( - TickIndex::MIN, - TickIndex::new_unchecked(TickIndex::MAX.get() + 1), - 1_000_000_000_u64, - ), - ( - TickIndex::new_unchecked(TickIndex::MIN.get() - 1), - TickIndex::new_unchecked(TickIndex::MAX.get() + 1), - 1_000_000_000_u64, - ), - ( - TickIndex::new_unchecked(TickIndex::MIN.get() - 100), - TickIndex::new_unchecked(TickIndex::MAX.get() + 100), - 1_000_000_000_u64, - ), - // Inverted ticks: high < low - ( - TickIndex::new_unchecked(-900), - TickIndex::new_unchecked(-1000), - 1_000_000_000_u64, - ), - // Equal ticks: high == low - ( - TickIndex::new_unchecked(-10_000), - TickIndex::new_unchecked(-10_000), - 1_000_000_000_u64, - ), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) - .for_each(|(netuid, tick_low, tick_high, liquidity)| { - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add liquidity - assert_err!( - Swap::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity - ), - Error::::InvalidTickRange, - ); - }); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // [ + // // For our tests, we'll construct TickIndex values that are intentionally + // // outside the valid range for testing purposes only + // ( + // TickIndex::new_unchecked(TickIndex::MIN.get() - 1), + // TickIndex::MAX, + // 1_000_000_000_u64, + // ), + // ( + // TickIndex::MIN, + // TickIndex::new_unchecked(TickIndex::MAX.get() + 1), + // 1_000_000_000_u64, + // ), + // ( + // TickIndex::new_unchecked(TickIndex::MIN.get() - 1), + // TickIndex::new_unchecked(TickIndex::MAX.get() + 1), + // 1_000_000_000_u64, + // ), + // ( + // TickIndex::new_unchecked(TickIndex::MIN.get() - 100), + // TickIndex::new_unchecked(TickIndex::MAX.get() + 100), + // 1_000_000_000_u64, + // ), + // // Inverted ticks: high < low + // ( + // TickIndex::new_unchecked(-900), + // TickIndex::new_unchecked(-1000), + // 1_000_000_000_u64, + // ), + // // Equal ticks: high == low + // ( + // TickIndex::new_unchecked(-10_000), + // TickIndex::new_unchecked(-10_000), + // 1_000_000_000_u64, + // ), + // ] + // .into_iter() + // .enumerate() + // .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) + // .for_each(|(netuid, tick_low, tick_high, liquidity)| { + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // // Add liquidity + // assert_err!( + // Swap::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // tick_low, + // tick_high, + // liquidity + // ), + // Error::::InvalidTickRange, + // ); + // }); + // }); } #[test] fn test_add_liquidity_over_balance() { - new_test_ext().execute_with(|| { - let coldkey_account_id = 3; - let hotkey_account_id = 1002; - - [ - // Lower than price (not enough tao) - (0.1, 0.2, 100_000_000_000_u64), - // Higher than price (not enough alpha) - (0.3, 0.4, 100_000_000_000_u64), - // Around the price (not enough both) - (0.1, 0.4, 100_000_000_000_u64), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) - .for_each(|(netuid, price_low, price_high, liquidity)| { - // Calculate ticks - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add liquidity - assert_err!( - Pallet::::do_add_liquidity( - netuid, - &coldkey_account_id, - &hotkey_account_id, - tick_low, - tick_high, - liquidity - ), - Error::::InsufficientBalance, - ); - }); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // let coldkey_account_id = 3; + // let hotkey_account_id = 1002; + + // [ + // // Lower than price (not enough tao) + // (0.1, 0.2, 100_000_000_000_u64), + // // Higher than price (not enough alpha) + // (0.3, 0.4, 100_000_000_000_u64), + // // Around the price (not enough both) + // (0.1, 0.4, 100_000_000_000_u64), + // ] + // .into_iter() + // .enumerate() + // .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) + // .for_each(|(netuid, price_low, price_high, liquidity)| { + // // Calculate ticks + // let tick_low = price_to_tick(price_low); + // let tick_high = price_to_tick(price_high); + + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // // Add liquidity + // assert_err!( + // Pallet::::do_add_liquidity( + // netuid, + // &coldkey_account_id, + // &hotkey_account_id, + // tick_low, + // tick_high, + // liquidity + // ), + // Error::::InsufficientBalance, + // ); + // }); + // }); } // cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_remove_liquidity_basic --exact --show-output #[test] fn test_remove_liquidity_basic() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - assert_eq!(max_tick, TickIndex::MAX); - - let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); - - // As a user add liquidity with all possible corner cases - // - Initial price is 0.25 - // - liquidity is expressed in RAO units - // Test case is (price_low, price_high, liquidity, tao, alpha) - [ - // Repeat the protocol liquidity at maximum range: Expect all the same values - ( - min_price, - max_price, - 2_000_000_000_u64, - 1_000_000_000_u64, - 4_000_000_000_u64, - ), - // Repeat the protocol liquidity at current to max range: Expect the same alpha - ( - current_price_high, - max_price, - 2_000_000_000_u64, - 0, - 4_000_000_000, - ), - // Repeat the protocol liquidity at min to current range: Expect all the same tao - ( - min_price, - current_price_low, - 2_000_000_000_u64, - 1_000_000_000, - 0, - ), - // Half to double price - just some sane wothdraw amounts - (0.125, 0.5, 2_000_000_000_u64, 293_000_000, 1_171_000_000), - // Both below price - tao is non-zero, alpha is zero - (0.12, 0.13, 2_000_000_000_u64, 28_270_000, 0), - // Both above price - tao is zero, alpha is non-zero - (0.3, 0.4, 2_000_000_000_u64, 0, 489_200_000), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3, v.4)) - .for_each(|(netuid, price_low, price_high, liquidity, tao, alpha)| { - // Calculate ticks (assuming tick math is tested separately) - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - let liquidity_before = CurrentLiquidity::::get(netuid); - - // Add liquidity - let (position_id, _, _) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - // Remove liquidity - let remove_result = - Pallet::::do_remove_liquidity(netuid, &OK_COLDKEY_ACCOUNT_ID, position_id) - .unwrap(); - assert_abs_diff_eq!(remove_result.tao.to_u64(), tao, epsilon = tao / 1000); - assert_abs_diff_eq!( - u64::from(remove_result.alpha), - alpha, - epsilon = alpha / 1000 - ); - assert_eq!(remove_result.fee_tao, TaoCurrency::ZERO); - assert_eq!(remove_result.fee_alpha, AlphaCurrency::ZERO); + todo!(); + + // new_test_ext().execute_with(|| { + // let min_price = tick_to_price(TickIndex::MIN); + // let max_price = tick_to_price(TickIndex::MAX); + // let max_tick = price_to_tick(max_price); + // assert_eq!(max_tick, TickIndex::MAX); + + // let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); + + // // As a user add liquidity with all possible corner cases + // // - Initial price is 0.25 + // // - liquidity is expressed in RAO units + // // Test case is (price_low, price_high, liquidity, tao, alpha) + // [ + // // Repeat the protocol liquidity at maximum range: Expect all the same values + // ( + // min_price, + // max_price, + // 2_000_000_000_u64, + // 1_000_000_000_u64, + // 4_000_000_000_u64, + // ), + // // Repeat the protocol liquidity at current to max range: Expect the same alpha + // ( + // current_price_high, + // max_price, + // 2_000_000_000_u64, + // 0, + // 4_000_000_000, + // ), + // // Repeat the protocol liquidity at min to current range: Expect all the same tao + // ( + // min_price, + // current_price_low, + // 2_000_000_000_u64, + // 1_000_000_000, + // 0, + // ), + // // Half to double price - just some sane wothdraw amounts + // (0.125, 0.5, 2_000_000_000_u64, 293_000_000, 1_171_000_000), + // // Both below price - tao is non-zero, alpha is zero + // (0.12, 0.13, 2_000_000_000_u64, 28_270_000, 0), + // // Both above price - tao is zero, alpha is non-zero + // (0.3, 0.4, 2_000_000_000_u64, 0, 489_200_000), + // ] + // .into_iter() + // .enumerate() + // .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3, v.4)) + // .for_each(|(netuid, price_low, price_high, liquidity, tao, alpha)| { + // // Calculate ticks (assuming tick math is tested separately) + // let tick_low = price_to_tick(price_low); + // let tick_high = price_to_tick(price_high); + + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + // let liquidity_before = CurrentLiquidity::::get(netuid); + + // // Add liquidity + // let (position_id, _, _) = Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // tick_low, + // tick_high, + // liquidity, + // ) + // .unwrap(); + + // // Remove liquidity + // let remove_result = + // Pallet::::do_remove_liquidity(netuid, &OK_COLDKEY_ACCOUNT_ID, position_id) + // .unwrap(); + // assert_abs_diff_eq!(remove_result.tao.to_u64(), tao, epsilon = tao / 1000); + // assert_abs_diff_eq!( + // u64::from(remove_result.alpha), + // alpha, + // epsilon = alpha / 1000 + // ); + // assert_eq!(remove_result.fee_tao, TaoCurrency::ZERO); + // assert_eq!(remove_result.fee_alpha, AlphaCurrency::ZERO); - // Liquidity position is removed - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 0 - ); - assert!(Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).is_none()); + // // Liquidity position is removed + // assert_eq!( + // Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + // 0 + // ); + // assert!(Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).is_none()); - // Current liquidity is updated (back where it was) - assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); - }); - }); + // // Current liquidity is updated (back where it was) + // assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); + // }); + // }); } #[test] fn test_remove_liquidity_nonexisting_position() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - assert_eq!(max_tick.get(), TickIndex::MAX.get()); - - let liquidity = 2_000_000_000_u64; - let netuid = NetUid::from(1); - - // Calculate ticks (assuming tick math is tested separately) - let tick_low = price_to_tick(min_price); - let tick_high = price_to_tick(max_price); - - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add liquidity - assert_ok!(Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - )); - - assert!(Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID) > 0); - - // Remove liquidity - assert_err!( - Pallet::::do_remove_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - PositionId::new::() - ), - Error::::LiquidityNotFound, - ); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // let min_price = tick_to_price(TickIndex::MIN); + // let max_price = tick_to_price(TickIndex::MAX); + // let max_tick = price_to_tick(max_price); + // assert_eq!(max_tick.get(), TickIndex::MAX.get()); + + // let liquidity = 2_000_000_000_u64; + // let netuid = NetUid::from(1); + + // // Calculate ticks (assuming tick math is tested separately) + // let tick_low = price_to_tick(min_price); + // let tick_high = price_to_tick(max_price); + + // // Setup swap + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // // Add liquidity + // assert_ok!(Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // tick_low, + // tick_high, + // liquidity, + // )); + + // assert!(Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID) > 0); + + // // Remove liquidity + // assert_err!( + // Pallet::::do_remove_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // PositionId::new::() + // ), + // Error::::LiquidityNotFound, + // ); + // }); } // cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_modify_position_basic --exact --show-output #[test] fn test_modify_position_basic() { - new_test_ext().execute_with(|| { - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - let limit_price = 1000.0_f64; - assert_eq!(max_tick, TickIndex::MAX); - let (current_price_low, _current_price_high) = get_ticked_prices_around_current_price(); - - // As a user add liquidity with all possible corner cases - // - Initial price is 0.25 - // - liquidity is expressed in RAO units - // Test case is (price_low, price_high, liquidity, tao, alpha) - [ - // Repeat the protocol liquidity at current to max range: Expect the same alpha - ( - current_price_low, - max_price, - 2_000_000_000_u64, - 4_000_000_000, - ), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3)) - .for_each(|(netuid, price_low, price_high, liquidity, alpha)| { - // Calculate ticks (assuming tick math is tested separately) - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add liquidity - let (position_id, _, _) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - // Get tick infos before the swap/update - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap(); - - // Swap to create fees on the position - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let order = GetAlphaForTao::with_amount(liquidity / 10); - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - - // Modify liquidity (also causes claiming of fees) - let liquidity_before = CurrentLiquidity::::get(netuid); - let modify_result = Pallet::::do_modify_position( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - position_id, - -((liquidity / 10) as i64), - ) - .unwrap(); - assert_abs_diff_eq!( - u64::from(modify_result.alpha), - alpha / 10, - epsilon = alpha / 1000 - ); - assert!(modify_result.fee_tao > TaoCurrency::ZERO); - assert_eq!(modify_result.fee_alpha, AlphaCurrency::ZERO); - - // Liquidity position is reduced - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 1 - ); + todo!(); + + // new_test_ext().execute_with(|| { + // let max_price = tick_to_price(TickIndex::MAX); + // let max_tick = price_to_tick(max_price); + // let limit_price = 1000.0_f64; + // assert_eq!(max_tick, TickIndex::MAX); + // let (current_price_low, _current_price_high) = get_ticked_prices_around_current_price(); + + // // As a user add liquidity with all possible corner cases + // // - Initial price is 0.25 + // // - liquidity is expressed in RAO units + // // Test case is (price_low, price_high, liquidity, tao, alpha) + // [ + // // Repeat the protocol liquidity at current to max range: Expect the same alpha + // ( + // current_price_low, + // max_price, + // 2_000_000_000_u64, + // 4_000_000_000, + // ), + // ] + // .into_iter() + // .enumerate() + // .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3)) + // .for_each(|(netuid, price_low, price_high, liquidity, alpha)| { + // // Calculate ticks (assuming tick math is tested separately) + // let tick_low = price_to_tick(price_low); + // let tick_high = price_to_tick(price_high); + + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // // Add liquidity + // let (position_id, _, _) = Pallet::::do_add_liquidity( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // tick_low, + // tick_high, + // liquidity, + // ) + // .unwrap(); + + // // Get tick infos before the swap/update + // let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap(); + // let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap(); + + // // Swap to create fees on the position + // let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + // let order = GetAlphaForTao::with_amount(liquidity / 10); + // Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); + + // // Modify liquidity (also causes claiming of fees) + // let liquidity_before = CurrentLiquidity::::get(netuid); + // let modify_result = Pallet::::do_modify_position( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // position_id, + // -((liquidity / 10) as i64), + // ) + // .unwrap(); + // assert_abs_diff_eq!( + // u64::from(modify_result.alpha), + // alpha / 10, + // epsilon = alpha / 1000 + // ); + // assert!(modify_result.fee_tao > TaoCurrency::ZERO); + // assert_eq!(modify_result.fee_alpha, AlphaCurrency::ZERO); - // Current liquidity is reduced with modify_position - assert!(CurrentLiquidity::::get(netuid) < liquidity_before); + // // Liquidity position is reduced + // assert_eq!( + // Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + // 1 + // ); - // Position liquidity is reduced - let position = - Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); - assert_eq!(position.liquidity, liquidity * 9 / 10); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); + // // Current liquidity is reduced with modify_position + // assert!(CurrentLiquidity::::get(netuid) < liquidity_before); - // Tick liquidity is updated properly for low and high position ticks - let tick_low_info_after = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info_after = Ticks::::get(netuid, tick_high).unwrap(); + // // Position liquidity is reduced + // let position = + // Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); + // assert_eq!(position.liquidity, liquidity * 9 / 10); + // assert_eq!(position.tick_low, tick_low); + // assert_eq!(position.tick_high, tick_high); - assert_eq!( - tick_low_info_before.liquidity_net - (liquidity / 10) as i128, - tick_low_info_after.liquidity_net, - ); - assert_eq!( - tick_low_info_before.liquidity_gross - (liquidity / 10), - tick_low_info_after.liquidity_gross, - ); - assert_eq!( - tick_high_info_before.liquidity_net + (liquidity / 10) as i128, - tick_high_info_after.liquidity_net, - ); - assert_eq!( - tick_high_info_before.liquidity_gross - (liquidity / 10), - tick_high_info_after.liquidity_gross, - ); + // // Tick liquidity is updated properly for low and high position ticks + // let tick_low_info_after = Ticks::::get(netuid, tick_low).unwrap(); + // let tick_high_info_after = Ticks::::get(netuid, tick_high).unwrap(); - // Modify liquidity again (ensure fees aren't double-collected) - let modify_result = Pallet::::do_modify_position( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - position_id, - -((liquidity / 100) as i64), - ) - .unwrap(); + // assert_eq!( + // tick_low_info_before.liquidity_net - (liquidity / 10) as i128, + // tick_low_info_after.liquidity_net, + // ); + // assert_eq!( + // tick_low_info_before.liquidity_gross - (liquidity / 10), + // tick_low_info_after.liquidity_gross, + // ); + // assert_eq!( + // tick_high_info_before.liquidity_net + (liquidity / 10) as i128, + // tick_high_info_after.liquidity_net, + // ); + // assert_eq!( + // tick_high_info_before.liquidity_gross - (liquidity / 10), + // tick_high_info_after.liquidity_gross, + // ); - assert_abs_diff_eq!( - u64::from(modify_result.alpha), - alpha / 100, - epsilon = alpha / 1000 - ); - assert_eq!(modify_result.fee_tao, TaoCurrency::ZERO); - assert_eq!(modify_result.fee_alpha, AlphaCurrency::ZERO); - }); - }); + // // Modify liquidity again (ensure fees aren't double-collected) + // let modify_result = Pallet::::do_modify_position( + // netuid, + // &OK_COLDKEY_ACCOUNT_ID, + // &OK_HOTKEY_ACCOUNT_ID, + // position_id, + // -((liquidity / 100) as i64), + // ) + // .unwrap(); + + // assert_abs_diff_eq!( + // u64::from(modify_result.alpha), + // alpha / 100, + // epsilon = alpha / 1000 + // ); + // assert_eq!(modify_result.fee_tao, TaoCurrency::ZERO); + // assert_eq!(modify_result.fee_alpha, AlphaCurrency::ZERO); + // }); + // }); } -// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_swap_basic --exact --show-output +// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_swap_basic --exact --nocapture #[test] fn test_swap_basic() { new_test_ext().execute_with(|| { @@ -756,571 +1014,175 @@ fn test_swap_basic() { netuid: NetUid, order: Order, limit_price: f64, - output_amount: u64, price_should_grow: bool, ) where Order: OrderT, - Order::PaidIn: GlobalFeeInfo, BasicSwapStep: SwapStep, { - // Consumed liquidity ticks - let tick_low = TickIndex::MIN; - let tick_high = TickIndex::MAX; - let liquidity = order.amount().to_u64(); + let swap_amount = order.amount().to_u64(); // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); - // Get tick infos before the swap - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); + // Price is 0.25 + let initial_tao_reserve = TaoCurrency::from(1_000_000_000_u64); + let initial_alpha_reserve = AlphaCurrency::from(4_000_000_000_u64); + TaoReserve::set_mock_reserve(netuid, initial_tao_reserve); + AlphaReserve::set_mock_reserve(netuid, initial_alpha_reserve); // Get current price - let current_price = Pallet::::current_price(netuid); + let current_price_before = Pallet::::current_price(netuid); + + // Get reserves + let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); + let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); + + // Expected fee amount + let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; + let expected_fee = (swap_amount as f64 * fee_rate) as u64; + + // Calculate expected output amount using f64 math + // This is a simple case when w1 = w2 = 0.5, so there's no + // exponentiation needed + let x = alpha_reserve as f64; + let y = tao_reserve as f64; + let expected_output_amount = if price_should_grow { + x * (1.0 - y / (y + (swap_amount - expected_fee) as f64)) + } else { + y * (1.0 - x / (x + (swap_amount - expected_fee) as f64)) + }; // Swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + let limit_price_fixed = U64F64::from_num(limit_price); let swap_result = - Pallet::::do_swap(netuid, order.clone(), sqrt_limit_price, false, false) + Pallet::::do_swap(netuid, order.clone(), limit_price_fixed, false, false) .unwrap(); assert_abs_diff_eq!( - swap_result.amount_paid_out.to_u64(), - output_amount, - epsilon = output_amount / 100 + swap_result.amount_paid_out.to_u64() as u64, + expected_output_amount as u64, + epsilon = 1 ); assert_abs_diff_eq!( swap_result.paid_in_reserve_delta() as u64, - liquidity, - epsilon = liquidity / 10 + (swap_amount - expected_fee) as u64, + epsilon = 1 ); assert_abs_diff_eq!( swap_result.paid_out_reserve_delta() as i64, - -(output_amount as i64), - epsilon = output_amount as i64 / 10 - ); - - // Check that low and high ticks' fees were updated properly, and liquidity values were not updated - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = tick_low_info_before.liquidity_net; - let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; - let expected_liquidity_net_high = tick_high_info_before.liquidity_net; - let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; - assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); - assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); - assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); - assert_eq!( - tick_high_info.liquidity_gross, - expected_liquidity_gross_high, + -(expected_output_amount as i64), + epsilon = 1 ); - // Expected fee amount - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = (liquidity as f64 * fee_rate) as u64; - - // Global fees should be updated - let actual_global_fee = (order.amount().global_fee(netuid).to_num::() - * (liquidity_before as f64)) as u64; - - assert!((swap_result.fee_paid.to_u64() as i64 - expected_fee as i64).abs() <= 1); - assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); - - // Tick fees should be updated + // Update reserves (because it happens outside of do_swap in stake_utils) + if price_should_grow { + TaoReserve::set_mock_reserve( + netuid, + TaoCurrency::from( + (u64::from(initial_tao_reserve) as i128 + + swap_result.paid_in_reserve_delta()) as u64, + ), + ); + AlphaReserve::set_mock_reserve( + netuid, + AlphaCurrency::from( + (u64::from(initial_alpha_reserve) as i128 + swap_result.paid_out_reserve_delta()) + as u64, + ), + ); + } else { + TaoReserve::set_mock_reserve( + netuid, + TaoCurrency::from( + (u64::from(initial_tao_reserve) as i128 + + swap_result.paid_out_reserve_delta()) as u64, + ), + ); + AlphaReserve::set_mock_reserve( + netuid, + AlphaCurrency::from( + (u64::from(initial_alpha_reserve) as i128 + swap_result.paid_in_reserve_delta()) + as u64, + ), + ); + } // Liquidity position should not be updated - let protocol_id = Pallet::::protocol_account_id(); - let positions = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - let position = positions.first().unwrap(); - - assert_eq!( - position.liquidity, - helpers_128bit::sqrt( - TaoReserve::reserve(netuid.into()).to_u64() as u128 - * AlphaReserve::reserve(netuid.into()).to_u64() as u128 - ) as u64 - ); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); - - // Current liquidity is not updated - assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); + // TODO: Revise when user liquidity is in place + // let protocol_id = Pallet::::protocol_account_id(); + // let positions = + // PositionsV2::::iter_prefix_values((netuid, protocol_id)).collect::>(); + // let position = positions.first().unwrap(); + + // assert_eq!( + // position.liquidity, + // helpers_128bit::sqrt( + // TaoReserve::reserve(netuid.into()).to_u64() as u128 + // * AlphaReserve::reserve(netuid.into()).to_u64() as u128 + // ) as u64 + // ); + // assert_eq!(position.fees_alpha, 0); + // assert_eq!(position.fees_tao, 0); // Assert that price movement is in correct direction - let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); let current_price_after = Pallet::::current_price(netuid); - assert_eq!(current_price_after >= current_price, price_should_grow); - - // Assert that current tick is updated - let current_tick = CurrentTick::::get(netuid); - let expected_current_tick = - TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); - assert_eq!(current_tick, expected_current_tick); + assert_eq!( + current_price_after >= current_price_before, + price_should_grow + ); } // Current price is 0.25 // Test case is (order_type, liquidity, limit_price, output_amount) + perform_test(1.into(), GetAlphaForTao::with_amount(1_000), 1000.0, true); + perform_test(1.into(), GetAlphaForTao::with_amount(2_000), 1000.0, true); + perform_test(1.into(), GetAlphaForTao::with_amount(123_456), 1000.0, true); + perform_test(2.into(), GetTaoForAlpha::with_amount(1_000), 0.0001, false); + perform_test(2.into(), GetTaoForAlpha::with_amount(2_000), 0.0001, false); + perform_test(2.into(), GetTaoForAlpha::with_amount(123_456), 0.0001, false); perform_test( - 1.into(), - GetAlphaForTao::with_amount(1_000), + 3.into(), + GetAlphaForTao::with_amount(1_000_000_000), 1000.0, - 3990, true, ); - perform_test( - 2.into(), - GetTaoForAlpha::with_amount(1_000), - 0.0001, - 250, - false, - ); perform_test( 3.into(), - GetAlphaForTao::with_amount(500_000_000), + GetAlphaForTao::with_amount(10_000_000_000), 1000.0, - 2_000_000_000, true, ); }); } -// In this test the swap starts and ends within one (large liquidity) position -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_swap_single_position --exact --show-output +// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_swap_precision_edge_case --exact --show-output #[test] -fn test_swap_single_position() { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - let netuid = NetUid::from(1); - assert_eq!(max_tick, TickIndex::MAX); - - let mut current_price_low = 0_f64; - let mut current_price_high = 0_f64; - let mut current_price = 0_f64; - new_test_ext().execute_with(|| { - let (low, high) = get_ticked_prices_around_current_price(); - current_price_low = low; - current_price_high = high; - current_price = Pallet::::current_price(netuid).to_num::(); - }); - - macro_rules! perform_test { - ($order_t:ident, - $price_low_offset:expr, - $price_high_offset:expr, - $position_liquidity:expr, - $liquidity_fraction:expr, - $limit_price:expr, - $price_should_grow:expr - ) => { - new_test_ext().execute_with(|| { - let price_low_offset = $price_low_offset; - let price_high_offset = $price_high_offset; - let position_liquidity = $position_liquidity; - let order_liquidity_fraction = $liquidity_fraction; - let limit_price = $limit_price; - let price_should_grow = $price_should_grow; - - ////////////////////////////////////////////// - // Initialize pool and add the user position - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); - let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); - let protocol_liquidity = (tao_reserve as f64 * alpha_reserve as f64).sqrt(); - - // Add liquidity - let current_price = Pallet::::current_price(netuid).to_num::(); - let sqrt_current_price = AlphaSqrtPrice::::get(netuid).to_num::(); - - let price_low = price_low_offset + current_price; - let price_high = price_high_offset + current_price; - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - position_liquidity, - ) - .unwrap(); - - // Liquidity position at correct ticks - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 1 - ); - - // Get tick infos before the swap - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = - Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); - assert_abs_diff_eq!( - liquidity_before as f64, - protocol_liquidity + position_liquidity as f64, - epsilon = liquidity_before as f64 / 1000. - ); - - ////////////////////////////////////////////// - // Swap - - // Calculate the expected output amount for the cornercase of one step - let order_liquidity = order_liquidity_fraction * position_liquidity as f64; - - let output_amount = >::approx_expected_swap_output( - sqrt_current_price, - liquidity_before as f64, - order_liquidity, - ); - - // Do the swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let order = $order_t::with_amount(order_liquidity as u64); - let swap_result = - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out.to_u64() as f64, - output_amount, - epsilon = output_amount / 10. - ); - - if order_liquidity_fraction <= 0.001 { - assert_abs_diff_eq!( - swap_result.paid_in_reserve_delta() as i64, - order_liquidity as i64, - epsilon = order_liquidity as i64 / 10 - ); - assert_abs_diff_eq!( - swap_result.paid_out_reserve_delta() as i64, - -(output_amount as i64), - epsilon = output_amount as i64 / 10 - ); - } - - // Assert that price movement is in correct direction - let current_price_after = Pallet::::current_price(netuid); - assert_eq!(price_should_grow, current_price_after > current_price); - - // Assert that for small amounts price stays within the user position - if (order_liquidity_fraction <= 0.001) - && (price_low_offset > 0.0001) - && (price_high_offset > 0.0001) - { - assert!(current_price_after <= price_high); - assert!(current_price_after >= price_low); - } - - // Check that low and high ticks' fees were updated properly - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = tick_low_info_before.liquidity_net; - let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; - let expected_liquidity_net_high = tick_high_info_before.liquidity_net; - let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; - assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); - assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); - assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); - assert_eq!( - tick_high_info.liquidity_gross, - expected_liquidity_gross_high, - ); - - // Expected fee amount - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = (order_liquidity - order_liquidity / (1.0 + fee_rate)) as u64; - - // // Global fees should be updated - let actual_global_fee = ($order_t::with_amount(0) - .amount() - .global_fee(netuid) - .to_num::() - * (liquidity_before as f64)) as u64; - - assert_abs_diff_eq!( - swap_result.fee_paid.to_u64(), - expected_fee, - epsilon = expected_fee / 10 - ); - assert_abs_diff_eq!(actual_global_fee, expected_fee, epsilon = expected_fee / 10); - - // Tick fees should be updated - - // Liquidity position should not be updated - let positions = - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .collect::>(); - let position = positions.first().unwrap(); - - assert_eq!(position.liquidity, position_liquidity,); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); - }); - }; - } - - // Current price is 0.25 - // The test case is based on the current price and position prices are defined as a price - // offset from the current price - // Outer part of test case is Position: (price_low_offset, price_high_offset, liquidity) +fn test_swap_precision_edge_case() { + // Test case: tao_reserve, alpha_reserve, swap_amount [ - // Very localized position at the current price - (-0.1, 0.1, 500_000_000_000_u64), - // Repeat the protocol liquidity at maximum range - ( - min_price - current_price, - max_price - current_price, - 2_000_000_000_u64, - ), - // Repeat the protocol liquidity at current to max range - ( - current_price_high - current_price, - max_price - current_price, - 2_000_000_000_u64, - ), - // Repeat the protocol liquidity at min to current range - ( - min_price - current_price, - current_price_low - current_price, - 2_000_000_000_u64, - ), - // Half to double price - (-0.125, 0.25, 2_000_000_000_u64), - // A few other price ranges and liquidity volumes - (-0.1, 0.1, 2_000_000_000_u64), - (-0.1, 0.1, 10_000_000_000_u64), - (-0.1, 0.1, 100_000_000_000_u64), - (-0.01, 0.01, 100_000_000_000_u64), - (-0.001, 0.001, 100_000_000_000_u64), + (1_000_u64, 1_000_u64, 999_500_u64), + (1_000_000_u64, 1_000_000_u64, 999_500_000_u64) ] .into_iter() - .for_each( - |(price_low_offset, price_high_offset, position_liquidity)| { - // Inner part of test case is Order: (order_type, order_liquidity, limit_price) - // order_liquidity is represented as a fraction of position_liquidity - for liquidity_fraction in [0.0001, 0.001, 0.01, 0.1, 0.2, 0.5] { - perform_test!( - GetAlphaForTao, - price_low_offset, - price_high_offset, - position_liquidity, - liquidity_fraction, - 1000.0_f64, - true - ); - perform_test!( - GetTaoForAlpha, - price_low_offset, - price_high_offset, - position_liquidity, - liquidity_fraction, - 0.0001_f64, - false - ); - } - }, - ); -} - -// This test is a sanity check for swap and multiple positions -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_swap_multiple_positions --exact --show-output --nocapture -#[test] -fn test_swap_multiple_positions() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - let netuid = NetUid::from(1); - assert_eq!(max_tick, TickIndex::MAX); - - ////////////////////////////////////////////// - // Initialize pool and add the user position - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add liquidity - let current_price = Pallet::::current_price(netuid).to_num::(); - - // Current price is 0.25 - // All positions below are placed at once - [ - // Very localized position at the current price - (-0.1, 0.1, 500_000_000_000_u64), - // Repeat the protocol liquidity at maximum range - ( - min_price - current_price, - max_price - current_price, - 2_000_000_000_u64, - ), - // Repeat the protocol liquidity at current to max range - (0.0, max_price - current_price, 2_000_000_000_u64), - // Repeat the protocol liquidity at min to current range - (min_price - current_price, 0.0, 2_000_000_000_u64), - // Half to double price - (-0.125, 0.25, 2_000_000_000_u64), - // A few other price ranges and liquidity volumes - (-0.1, 0.1, 2_000_000_000_u64), - (-0.1, 0.1, 10_000_000_000_u64), - (-0.1, 0.1, 100_000_000_000_u64), - (-0.01, 0.01, 100_000_000_000_u64), - (-0.001, 0.001, 100_000_000_000_u64), - // A few (overlapping) positions up the range - (0.01, 0.02, 100_000_000_000_u64), - (0.02, 0.03, 100_000_000_000_u64), - (0.03, 0.04, 100_000_000_000_u64), - (0.03, 0.05, 100_000_000_000_u64), - // A few (overlapping) positions down the range - (-0.02, -0.01, 100_000_000_000_u64), - (-0.03, -0.02, 100_000_000_000_u64), - (-0.04, -0.03, 100_000_000_000_u64), - (-0.05, -0.03, 100_000_000_000_u64), - ] - .into_iter() - .for_each( - |(price_low_offset, price_high_offset, position_liquidity)| { - let price_low = price_low_offset + current_price; - let price_high = price_high_offset + current_price; - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - position_liquidity, - ) - .unwrap(); - }, - ); - - macro_rules! perform_test { - ($order_t:ident, $order_liquidity:expr, $limit_price:expr, $should_price_grow:expr) => { - ////////////////////////////////////////////// - // Swap - let order_liquidity = $order_liquidity; - let limit_price = $limit_price; - let should_price_grow = $should_price_grow; - - let sqrt_current_price = AlphaSqrtPrice::::get(netuid); - let current_price = (sqrt_current_price * sqrt_current_price).to_num::(); - let liquidity_before = CurrentLiquidity::::get(netuid); - let output_amount = >::approx_expected_swap_output( - sqrt_current_price.to_num(), - liquidity_before as f64, - order_liquidity as f64, - ); - - // Do the swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let order = $order_t::with_amount(order_liquidity); - let swap_result = - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out.to_u64() as f64, - output_amount, - epsilon = output_amount / 10. - ); - - let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); - let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); - let output_amount = output_amount as u64; - - assert!(output_amount > 0); - - if alpha_reserve > order_liquidity && tao_reserve > order_liquidity { - assert_abs_diff_eq!( - swap_result.paid_in_reserve_delta() as i64, - order_liquidity as i64, - epsilon = order_liquidity as i64 / 100 - ); - assert_abs_diff_eq!( - swap_result.paid_out_reserve_delta() as i64, - -(output_amount as i64), - epsilon = output_amount as i64 / 100 - ); - } - - // Assert that price movement is in correct direction - let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); - let current_price_after = - (sqrt_current_price_after * sqrt_current_price_after).to_num::(); - assert_eq!(should_price_grow, current_price_after > current_price); - }; - } - - // All these orders are executed without swap reset - for order_liquidity in [ - (100_000_u64), - (1_000_000), - (10_000_000), - (100_000_000), - (200_000_000), - (500_000_000), - (1_000_000_000), - (10_000_000_000), - ] { - perform_test!(GetAlphaForTao, order_liquidity, 1000.0_f64, true); - perform_test!(GetTaoForAlpha, order_liquidity, 0.0001_f64, false); - } - - // Current price shouldn't be much different from the original - let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); - let current_price_after = - (sqrt_current_price_after * sqrt_current_price_after).to_num::(); - assert_abs_diff_eq!( - current_price, - current_price_after, - epsilon = current_price / 10. - ) - }); -} - -// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_swap_precision_edge_case --exact --show-output -#[test] -fn test_swap_precision_edge_case() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(123); // 123 is netuid with low edge case liquidity - let order = GetTaoForAlpha::with_amount(1_000_000_000_000_000_000); - let tick_low = TickIndex::MIN; - - let sqrt_limit_price: SqrtPrice = tick_low.try_to_sqrt_price().unwrap(); - - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Swap - let swap_result = - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, true).unwrap(); - - assert!(swap_result.amount_paid_out > TaoCurrency::ZERO); - }); -} - -// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_price_tick_price_roundtrip --exact --show-output -#[test] -fn test_price_tick_price_roundtrip() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); + .for_each(|(tao_reserve, alpha_reserve, swap_amount)| { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let order = GetTaoForAlpha::with_amount(swap_amount); - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + // Very low reserves + TaoReserve::set_mock_reserve(netuid, TaoCurrency::from(tao_reserve)); + AlphaReserve::set_mock_reserve(netuid, AlphaCurrency::from(alpha_reserve)); - let current_price = SqrtPrice::from_num(0.500_000_512_192_122_7); - let tick = TickIndex::try_from_sqrt_price(current_price).unwrap(); + // Minimum possible limit price + let limit_price: U64F64 = get_min_price(); + println!("limit_price = {:?}", limit_price); - let round_trip_price = TickIndex::try_to_sqrt_price(&tick).unwrap(); - assert!(round_trip_price <= current_price); + // Swap + let swap_result = Pallet::::do_swap(netuid, order, limit_price, false, true).unwrap(); - let roundtrip_tick = TickIndex::try_from_sqrt_price(round_trip_price).unwrap(); - assert!(tick == roundtrip_tick); + assert!(swap_result.amount_paid_out > TaoCurrency::ZERO); + }); }); } @@ -1328,72 +1190,82 @@ fn test_price_tick_price_roundtrip() { fn test_convert_deltas() { new_test_ext().execute_with(|| { let netuid = NetUid::from(1); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - for (sqrt_price, delta_in, expected_buy, expected_sell) in [ - (SqrtPrice::from_num(1.5), 1, 0, 2), - (SqrtPrice::from_num(1.5), 10000, 4444, 22500), - (SqrtPrice::from_num(1.5), 1000000, 444444, 2250000), - ( - SqrtPrice::from_num(1.5), - u64::MAX, - 2000000000000, - 3000000000000, - ), - ( - TickIndex::MIN.as_sqrt_price_bounded(), - 1, - 18406523739291577836, - 465, - ), - (TickIndex::MIN.as_sqrt_price_bounded(), 10000, u64::MAX, 465), - ( - TickIndex::MIN.as_sqrt_price_bounded(), - 1000000, - u64::MAX, - 465, - ), - ( - TickIndex::MIN.as_sqrt_price_bounded(), - u64::MAX, - u64::MAX, - 464, - ), - ( - TickIndex::MAX.as_sqrt_price_bounded(), - 1, - 0, - 18406523745214495085, - ), - (TickIndex::MAX.as_sqrt_price_bounded(), 10000, 0, u64::MAX), - (TickIndex::MAX.as_sqrt_price_bounded(), 1000000, 0, u64::MAX), - ( - TickIndex::MAX.as_sqrt_price_bounded(), - u64::MAX, - 2000000000000, - u64::MAX, - ), + assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + + // TODO: Add more test cases with different weights and edge cases for reserves + for (tao, alpha, w_quote, delta_in) in [ + (1500, 1000, 0.5, 1), + (1500, 1000, 0.5, 10000), + (1500, 1000, 0.5, 1000000), + (1500, 1000, 0.5, u64::MAX), + (1, 1000000, 0.5, 1), + (1, 1000000, 0.5, 10000), + (1, 1000000, 0.5, 1000000), + (1, 1000000, 0.5, u64::MAX), + (1000000, 1, 0.5, 1), + (1000000, 1, 0.5, 10000), + (1000000, 1, 0.5, 1000000), + (1000000, 1, 0.5, u64::MAX), + // Low quote weight + (1500, 1000, 0.1, 1), + (1500, 1000, 0.1, 10000), + (1500, 1000, 0.1, 1000000), + (1500, 1000, 0.1, u64::MAX), + (1, 1000000, 0.1, 1), + (1, 1000000, 0.1, 10000), + (1, 1000000, 0.1, 1000000), + (1, 1000000, 0.1, u64::MAX), + (1000000, 1, 0.1, 1), + (1000000, 1, 0.1, 10000), + (1000000, 1, 0.1, 1000000), + (1000000, 1, 0.1, u64::MAX), + // High quote weight + (1500, 1000, 0.9, 1), + (1500, 1000, 0.9, 10000), + (1500, 1000, 0.9, 1000000), + (1500, 1000, 0.9, u64::MAX), + (1, 1000000, 0.9, 1), + (1, 1000000, 0.9, 10000), + (1, 1000000, 0.9, 1000000), + (1, 1000000, 0.9, u64::MAX), + (1000000, 1, 0.9, 1), + (1000000, 1, 0.9, 10000), + (1000000, 1, 0.9, 1000000), + (1000000, 1, 0.9, u64::MAX), ] { - { - AlphaSqrtPrice::::insert(netuid, sqrt_price); + // Initialize reserves and weights + TaoReserve::set_mock_reserve(netuid, TaoCurrency::from(tao)); + AlphaReserve::set_mock_reserve(netuid, AlphaCurrency::from(alpha)); + let w_accuracy = 1_000_000_000_f64; + let w_quote_pt = Perquintill::from_rational((w_quote as f64 * w_accuracy) as u128, w_accuracy as u128); + let rw = ReserveWeight::new(w_quote_pt).unwrap(); + SwapReserveWeight::::insert(netuid, rw); + + // Calculate expected swap results (buy and sell) using f64 math + let y = tao as f64; + let x = alpha as f64; + let d = delta_in as f64; + let w1_div_w2 = (1. - w_quote) / w_quote; + let w2_div_w1 = w_quote / (1. - w_quote); + let expected_sell = y * (1. - (x / (x + d)).powf(w1_div_w2)); + let expected_buy = x * (1. - (y / (y + d)).powf(w2_div_w1)); - assert_abs_diff_eq!( - BasicSwapStep::::convert_deltas( - netuid, - delta_in.into() - ), - expected_sell.into(), - epsilon = 2.into() - ); - assert_abs_diff_eq!( - BasicSwapStep::::convert_deltas( - netuid, - delta_in.into() - ), - expected_buy.into(), - epsilon = 2.into() - ); - } + assert_abs_diff_eq!( + u64::from(BasicSwapStep::::convert_deltas( + netuid, + delta_in.into() + )), + expected_sell as u64, + epsilon = 2u64 + ); + assert_abs_diff_eq!( + u64::from(BasicSwapStep::::convert_deltas( + netuid, + delta_in.into() + )), + expected_buy as u64, + epsilon = 2u64 + ); } }); } @@ -1472,135 +1344,67 @@ fn test_convert_deltas() { // }); // } -/// Test correctness of swap fees: -/// - Fees are distribued to (concentrated) liquidity providers -/// -#[test] -fn test_swap_fee_correctness() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let netuid = NetUid::from(1); - - // Provide very spread liquidity at the range from min to max that matches protocol liquidity - let liquidity = 2_000_000_000_000_u64; // 1x of protocol liquidity - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Calculate ticks - let tick_low = price_to_tick(min_price); - let tick_high = price_to_tick(max_price); - - // Add user liquidity - let (position_id, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - // Swap buy and swap sell - Pallet::::do_swap( - netuid, - GetAlphaForTao::with_amount(liquidity / 10), - u64::MAX.into(), - false, - false, - ) - .unwrap(); - Pallet::::do_swap( - netuid, - GetTaoForAlpha::with_amount(liquidity / 10), - 0_u64.into(), - false, - false, - ) - .unwrap(); - - // Get user position - let mut position = - Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); - assert_eq!(position.liquidity, liquidity); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - - // Check that 50% of fees were credited to the position - let fee_rate = FeeRate::::get(NetUid::from(netuid)) as f64 / u16::MAX as f64; - let (actual_fee_tao, actual_fee_alpha) = position.collect_fees(); - let expected_fee = (fee_rate * (liquidity / 10) as f64 * 0.5) as u64; - - assert_abs_diff_eq!(actual_fee_tao, expected_fee, epsilon = 1,); - assert_abs_diff_eq!(actual_fee_alpha, expected_fee, epsilon = 1,); - }); -} - -#[test] -fn test_current_liquidity_updates() { - let netuid = NetUid::from(1); - let liquidity = 1_000_000_000; - - // Get current price - let (current_price, current_price_low, current_price_high) = - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - let sqrt_current_price = AlphaSqrtPrice::::get(netuid); - let current_price = (sqrt_current_price * sqrt_current_price).to_num::(); - let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); - (current_price, current_price_low, current_price_high) - }); - - // Test case: (price_low, price_high, expect_to_update) - [ - // Current price is out of position range (lower), no current lq update - (current_price * 2., current_price * 3., false), - // Current price is out of position range (higher), no current lq update - (current_price / 3., current_price / 2., false), - // Current price is just below position range, no current lq update - (current_price_high, current_price * 3., false), - // Position lower edge is just below the current price, current lq updates - (current_price_low, current_price * 3., true), - // Current price is exactly at lower edge of position range, current lq updates - (current_price, current_price * 3., true), - // Current price is exactly at higher edge of position range, no current lq update - (current_price / 2., current_price, false), - ] - .into_iter() - .for_each(|(price_low, price_high, expect_to_update)| { - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); +// TODO: revise when user liquidity is available +// Test correctness of swap fees: +// - Fees are distribued to (concentrated) liquidity providers +// +// #[test] +// fn test_swap_fee_correctness() { +// new_test_ext().execute_with(|| { +// let min_price = get_min_price(); +// let max_price = get_max_price(); +// let netuid = NetUid::from(1); - // Calculate ticks (assuming tick math is tested separately) - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - let liquidity_before = CurrentLiquidity::::get(netuid); +// // Provide very spread liquidity at the range from min to max that matches protocol liquidity +// let liquidity = 2_000_000_000_000_u64; // 1x of protocol liquidity - // Add liquidity - assert_ok!(Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - )); +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); - // Current liquidity is updated only when price range includes the current price - let expected_liquidity = if (price_high > current_price) && (price_low <= current_price) - { - assert!(expect_to_update); - liquidity_before + liquidity - } else { - assert!(!expect_to_update); - liquidity_before - }; +// // Add user liquidity +// let (position_id, _tao, _alpha) = Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// liquidity, +// ) +// .unwrap(); - assert_eq!(CurrentLiquidity::::get(netuid), expected_liquidity) - }); - }); -} +// // Swap buy and swap sell +// Pallet::::do_swap( +// netuid, +// GetAlphaForTao::with_amount(liquidity / 10), +// u64::MAX.into(), +// false, +// false, +// ) +// .unwrap(); +// Pallet::::do_swap( +// netuid, +// GetTaoForAlpha::with_amount(liquidity / 10), +// 0_u64.into(), +// false, +// false, +// ) +// .unwrap(); + +// // Get user position +// let mut position = +// Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID, position_id)).unwrap(); +// assert_eq!(position.liquidity, liquidity); +// assert_eq!(position.tick_low, tick_low); +// assert_eq!(position.tick_high, tick_high); + +// // Check that 50% of fees were credited to the position +// let fee_rate = FeeRate::::get(NetUid::from(netuid)) as f64 / u16::MAX as f64; +// let (actual_fee_tao, actual_fee_alpha) = position.collect_fees(); +// let expected_fee = (fee_rate * (liquidity / 10) as f64 * 0.5) as u64; + +// assert_abs_diff_eq!(actual_fee_tao, expected_fee, epsilon = 1,); +// assert_abs_diff_eq!(actual_fee_alpha, expected_fee, epsilon = 1,); +// }); +// } #[test] fn test_rollback_works() { @@ -1628,80 +1432,82 @@ fn test_rollback_works() { }) } -/// Test correctness of swap fees: -/// - New LP is not eligible to previously accrued fees -/// -/// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_new_lp_doesnt_get_old_fees --exact --show-output -#[test] -fn test_new_lp_doesnt_get_old_fees() { - new_test_ext().execute_with(|| { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let netuid = NetUid::from(1); +// TODO: Revise when user liquidity is available +// Test correctness of swap fees: +// - New LP is not eligible to previously accrued fees +// +// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_new_lp_doesnt_get_old_fees --exact --show-output +// #[test] +// fn test_new_lp_doesnt_get_old_fees() { +// new_test_ext().execute_with(|| { +// let min_price = tick_to_price(TickIndex::MIN); +// let max_price = tick_to_price(TickIndex::MAX); +// let netuid = NetUid::from(1); - // Provide very spread liquidity at the range from min to max that matches protocol liquidity - let liquidity = 2_000_000_000_000_u64; // 1x of protocol liquidity - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Calculate ticks - let tick_low = price_to_tick(min_price); - let tick_high = price_to_tick(max_price); - - // Add user liquidity - Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .unwrap(); - - // Swap buy and swap sell - Pallet::::do_swap( - netuid, - GetAlphaForTao::with_amount(liquidity / 10), - u64::MAX.into(), - false, - false, - ) - .unwrap(); - Pallet::::do_swap( - netuid, - GetTaoForAlpha::with_amount(liquidity / 10), - 0_u64.into(), - false, - false, - ) - .unwrap(); - - // Add liquidity from a different user to a new tick - let (position_id_2, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID_2, - &OK_HOTKEY_ACCOUNT_ID_2, - tick_low.next().unwrap(), - tick_high.prev().unwrap(), - liquidity, - ) - .unwrap(); - - // Get user position - let mut position = - Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID_2, position_id_2)).unwrap(); - assert_eq!(position.liquidity, liquidity); - assert_eq!(position.tick_low, tick_low.next().unwrap()); - assert_eq!(position.tick_high, tick_high.prev().unwrap()); - - // Check that collected fees are 0 - let (actual_fee_tao, actual_fee_alpha) = position.collect_fees(); - assert_abs_diff_eq!(actual_fee_tao, 0, epsilon = 1); - assert_abs_diff_eq!(actual_fee_alpha, 0, epsilon = 1); - }); -} +// // Provide very spread liquidity at the range from min to max that matches protocol liquidity +// let liquidity = 2_000_000_000_000_u64; // 1x of protocol liquidity +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // Calculate ticks +// let tick_low = price_to_tick(min_price); +// let tick_high = price_to_tick(max_price); + +// // Add user liquidity +// Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// liquidity, +// ) +// .unwrap(); + +// // Swap buy and swap sell +// Pallet::::do_swap( +// netuid, +// GetAlphaForTao::with_amount(liquidity / 10), +// u64::MAX.into(), +// false, +// false, +// ) +// .unwrap(); +// Pallet::::do_swap( +// netuid, +// GetTaoForAlpha::with_amount(liquidity / 10), +// 0_u64.into(), +// false, +// false, +// ) +// .unwrap(); + +// // Add liquidity from a different user to a new tick +// let (position_id_2, _tao, _alpha) = Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID_2, +// &OK_HOTKEY_ACCOUNT_ID_2, +// tick_low.next().unwrap(), +// tick_high.prev().unwrap(), +// liquidity, +// ) +// .unwrap(); + +// // Get user position +// let mut position = +// Positions::::get((netuid, OK_COLDKEY_ACCOUNT_ID_2, position_id_2)).unwrap(); +// assert_eq!(position.liquidity, liquidity); +// assert_eq!(position.tick_low, tick_low.next().unwrap()); +// assert_eq!(position.tick_high, tick_high.prev().unwrap()); + +// // Check that collected fees are 0 +// let (actual_fee_tao, actual_fee_alpha) = position.collect_fees(); +// assert_abs_diff_eq!(actual_fee_tao, 0, epsilon = 1); +// assert_abs_diff_eq!(actual_fee_alpha, 0, epsilon = 1); +// }); +// } + +#[allow(dead_code)] fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { if t < a { a @@ -1712,214 +1518,210 @@ fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { } } +#[allow(dead_code)] fn print_current_price(netuid: NetUid) { - let current_sqrt_price = AlphaSqrtPrice::::get(netuid).to_num::(); - let current_price = current_sqrt_price * current_sqrt_price; + let current_price = Pallet::::current_price(netuid); log::trace!("Current price: {current_price:.6}"); } -/// RUST_LOG=pallet_subtensor_swap=trace cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_wrapping_fees --exact --show-output --nocapture -#[test] -fn test_wrapping_fees() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(WRAPPING_FEES_NETUID); - let position_1_low_price = 0.20; - let position_1_high_price = 0.255; - let position_2_low_price = 0.255; - let position_2_high_price = 0.257; - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID_RICH, - &OK_COLDKEY_ACCOUNT_ID_RICH, - price_to_tick(position_1_low_price), - price_to_tick(position_1_high_price), - 1_000_000_000_u64, - ) - .unwrap(); - - print_current_price(netuid); - - let order = GetTaoForAlpha::with_amount(800_000_000); - let sqrt_limit_price = SqrtPrice::from_num(0.000001); - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - - let order = GetAlphaForTao::with_amount(1_850_000_000); - let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); - - print_current_price(netuid); - - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - - print_current_price(netuid); - - let add_liquidity_result = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID_RICH, - &OK_COLDKEY_ACCOUNT_ID_RICH, - price_to_tick(position_2_low_price), - price_to_tick(position_2_high_price), - 1_000_000_000_u64, - ) - .unwrap(); - - let order = GetTaoForAlpha::with_amount(1_800_000_000); - let sqrt_limit_price = SqrtPrice::from_num(0.000001); - - let initial_sqrt_price = AlphaSqrtPrice::::get(netuid); - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - let final_sqrt_price = AlphaSqrtPrice::::get(netuid); - - print_current_price(netuid); - - let mut position = - Positions::::get((netuid, &OK_COLDKEY_ACCOUNT_ID_RICH, add_liquidity_result.0)) - .unwrap(); - - let initial_box_price = bbox( - initial_sqrt_price, - position.tick_low.try_to_sqrt_price().unwrap(), - position.tick_high.try_to_sqrt_price().unwrap(), - ); +// TODO: Revise when user liquidity is available +// RUST_LOG=pallet_subtensor_swap=trace cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_wrapping_fees --exact --show-output --nocapture +// #[test] +// fn test_wrapping_fees() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(WRAPPING_FEES_NETUID); +// let position_1_low_price = 0.20; +// let position_1_high_price = 0.255; +// let position_2_low_price = 0.255; +// let position_2_high_price = 0.257; +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID_RICH, +// &OK_COLDKEY_ACCOUNT_ID_RICH, +// price_to_tick(position_1_low_price), +// price_to_tick(position_1_high_price), +// 1_000_000_000_u64, +// ) +// .unwrap(); - let final_box_price = bbox( - final_sqrt_price, - position.tick_low.try_to_sqrt_price().unwrap(), - position.tick_high.try_to_sqrt_price().unwrap(), - ); +// print_current_price(netuid); - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; +// let order = GetTaoForAlpha::with_amount(800_000_000); +// let sqrt_limit_price = SqrtPrice::from_num(0.000001); +// Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - log::trace!("fee_rate: {fee_rate:.6}"); - log::trace!("position.liquidity: {}", position.liquidity); - log::trace!( - "initial_box_price: {:.6}", - initial_box_price.to_num::() - ); - log::trace!("final_box_price: {:.6}", final_box_price.to_num::()); +// let order = GetAlphaForTao::with_amount(1_850_000_000); +// let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); - let expected_fee_tao = ((fee_rate / (1.0 - fee_rate)) - * (position.liquidity as f64) - * (final_box_price.to_num::() - initial_box_price.to_num::())) - as u64; +// print_current_price(netuid); - let expected_fee_alpha = ((fee_rate / (1.0 - fee_rate)) - * (position.liquidity as f64) - * ((1.0 / final_box_price.to_num::()) - (1.0 / initial_box_price.to_num::()))) - as u64; +// Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - log::trace!("Expected ALPHA fee: {:.6}", expected_fee_alpha as f64); +// print_current_price(netuid); - let (fee_tao, fee_alpha) = position.collect_fees(); +// let add_liquidity_result = Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID_RICH, +// &OK_COLDKEY_ACCOUNT_ID_RICH, +// price_to_tick(position_2_low_price), +// price_to_tick(position_2_high_price), +// 1_000_000_000_u64, +// ) +// .unwrap(); - log::trace!("Collected fees: TAO: {fee_tao}, ALPHA: {fee_alpha}"); +// let order = GetTaoForAlpha::with_amount(1_800_000_000); +// let sqrt_limit_price = SqrtPrice::from_num(0.000001); - assert_abs_diff_eq!(fee_tao, expected_fee_tao, epsilon = 1); - assert_abs_diff_eq!(fee_alpha, expected_fee_alpha, epsilon = 1); - }); -} +// let initial_sqrt_price = AlphaSqrtPrice::::get(netuid); +// Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); +// let final_sqrt_price = AlphaSqrtPrice::::get(netuid); -/// Test that price moves less with provided liquidity -/// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_less_price_movement --exact --show-output -#[test] -fn test_less_price_movement() { - let netuid = NetUid::from(1); - let mut last_end_price = U96F32::from_num(0); - let initial_stake_liquidity = 1_000_000_000; - let swapped_liquidity = 1_000_000; - - // Test case is (order_type, provided_liquidity) - // Testing algorithm: - // - Stake initial_stake_liquidity - // - Provide liquidity if iteration provides lq - // - Buy or sell - // - Save end price if iteration doesn't provide lq - macro_rules! perform_test { - ($order_t:ident, $provided_liquidity:expr, $limit_price:expr, $should_price_shrink:expr) => { - let provided_liquidity = $provided_liquidity; - let should_price_shrink = $should_price_shrink; - let limit_price = $limit_price; - new_test_ext().execute_with(|| { - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); +// print_current_price(netuid); - // Buy Alpha - assert_ok!(Pallet::::do_swap( - netuid, - GetAlphaForTao::with_amount(initial_stake_liquidity), - SqrtPrice::from_num(10_000_000_000_u64), - false, - false - )); - - // Get current price - let start_price = Pallet::::current_price(netuid); - - // Add liquidity if this test iteration provides - if provided_liquidity > 0 { - let tick_low = price_to_tick(start_price.to_num::() * 0.5); - let tick_high = price_to_tick(start_price.to_num::() * 1.5); - assert_ok!(Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - provided_liquidity, - )); - } +// let mut position = +// Positions::::get((netuid, &OK_COLDKEY_ACCOUNT_ID_RICH, add_liquidity_result.0)) +// .unwrap(); - // Swap - let sqrt_limit_price = SqrtPrice::from_num(limit_price); - assert_ok!(Pallet::::do_swap( - netuid, - $order_t::with_amount(swapped_liquidity), - sqrt_limit_price, - false, - false - )); - - let end_price = Pallet::::current_price(netuid); - - // Save end price if iteration doesn't provide or compare with previous end price if - // it does - if provided_liquidity > 0 { - assert_eq!(should_price_shrink, end_price < last_end_price); - } else { - last_end_price = end_price; - } - }); - }; - } +// let initial_box_price = bbox( +// initial_sqrt_price, +// position.tick_low.try_to_sqrt_price().unwrap(), +// position.tick_high.try_to_sqrt_price().unwrap(), +// ); - for provided_liquidity in [0, 1_000_000_000_000_u64] { - perform_test!(GetAlphaForTao, provided_liquidity, 1000.0_f64, true); - } - for provided_liquidity in [0, 1_000_000_000_000_u64] { - perform_test!(GetTaoForAlpha, provided_liquidity, 0.001_f64, false); - } -} +// let final_box_price = bbox( +// final_sqrt_price, +// position.tick_low.try_to_sqrt_price().unwrap(), +// position.tick_high.try_to_sqrt_price().unwrap(), +// ); + +// let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; + +// log::trace!("fee_rate: {fee_rate:.6}"); +// log::trace!("position.liquidity: {}", position.liquidity); +// log::trace!( +// "initial_box_price: {:.6}", +// initial_box_price.to_num::() +// ); +// log::trace!("final_box_price: {:.6}", final_box_price.to_num::()); + +// let expected_fee_tao = ((fee_rate / (1.0 - fee_rate)) +// * (position.liquidity as f64) +// * (final_box_price.to_num::() - initial_box_price.to_num::())) +// as u64; + +// let expected_fee_alpha = ((fee_rate / (1.0 - fee_rate)) +// * (position.liquidity as f64) +// * ((1.0 / final_box_price.to_num::()) - (1.0 / initial_box_price.to_num::()))) +// as u64; + +// log::trace!("Expected ALPHA fee: {:.6}", expected_fee_alpha as f64); + +// let (fee_tao, fee_alpha) = position.collect_fees(); + +// log::trace!("Collected fees: TAO: {fee_tao}, ALPHA: {fee_alpha}"); + +// assert_abs_diff_eq!(fee_tao, expected_fee_tao, epsilon = 1); +// assert_abs_diff_eq!(fee_alpha, expected_fee_alpha, epsilon = 1); +// }); +// } + +// TODO: Revise when user liquidity is available +// Test that price moves less with more liquidity +// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_less_price_movement --exact --show-output +// #[test] +// fn test_less_price_movement() { +// let netuid = NetUid::from(1); +// let mut last_end_price = U64F64::from_num(0); +// let initial_stake_liquidity = 1_000_000_000; +// let swapped_liquidity = 1_000_000; + +// // Test case is (order_type, provided_liquidity) +// // Testing algorithm: +// // - Stake initial_stake_liquidity +// // - Provide liquidity if iteration provides lq +// // - Buy or sell +// // - Save end price if iteration doesn't provide lq +// macro_rules! perform_test { +// ($order_t:ident, $provided_liquidity:expr, $limit_price:expr, $should_price_shrink:expr) => { +// let provided_liquidity = $provided_liquidity; +// let should_price_shrink = $should_price_shrink; +// let limit_price = $limit_price; +// new_test_ext().execute_with(|| { +// // Setup swap +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // Buy Alpha +// assert_ok!(Pallet::::do_swap( +// netuid, +// GetAlphaForTao::with_amount(initial_stake_liquidity), +// SqrtPrice::from_num(10_000_000_000_u64), +// false, +// false +// )); + +// // Get current price +// let start_price = Pallet::::current_price(netuid); + +// // Add liquidity if this test iteration provides +// if provided_liquidity > 0 { +// let tick_low = price_to_tick(start_price.to_num::() * 0.5); +// let tick_high = price_to_tick(start_price.to_num::() * 1.5); +// assert_ok!(Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// provided_liquidity, +// )); +// } + +// // Swap +// let sqrt_limit_price = SqrtPrice::from_num(limit_price); +// assert_ok!(Pallet::::do_swap( +// netuid, +// $order_t::with_amount(swapped_liquidity), +// sqrt_limit_price, +// false, +// false +// )); + +// let end_price = Pallet::::current_price(netuid); + +// // Save end price if iteration doesn't provide or compare with previous end price if +// // it does +// if provided_liquidity > 0 { +// assert_eq!(should_price_shrink, end_price < last_end_price); +// } else { +// last_end_price = end_price; +// } +// }); +// }; +// } + +// for provided_liquidity in [0, 1_000_000_000_000_u64] { +// perform_test!(GetAlphaForTao, provided_liquidity, 1000.0_f64, true); +// } +// for provided_liquidity in [0, 1_000_000_000_000_u64] { +// perform_test!(GetTaoForAlpha, provided_liquidity, 0.001_f64, false); +// } +// } #[test] fn test_swap_subtoken_disabled() { new_test_ext().execute_with(|| { let netuid = NetUid::from(SUBTOKEN_DISABLED_NETUID); // Use a netuid not used elsewhere - let price_low = 0.1; - let price_high = 0.2; - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); let liquidity = 1_000_000_u64; - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); assert_noop!( Pallet::::add_liquidity( RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), OK_HOTKEY_ACCOUNT_ID, netuid, - tick_low, - tick_high, liquidity, ), Error::::SubtokenDisabled @@ -1938,149 +1740,147 @@ fn test_swap_subtoken_disabled() { }); } -#[test] -fn test_liquidate_v3_removes_positions_ticks_and_state() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - - // Initialize V3 (creates protocol position, ticks, price, liquidity) - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - assert!(SwapV3Initialized::::get(netuid)); - - // Enable user LP - assert_ok!(Swap::toggle_user_liquidity( - RuntimeOrigin::root(), - netuid.into(), - true - )); - - // Add a user position across the full range to ensure ticks/bitmap are populated. - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let tick_low = price_to_tick(min_price); - let tick_high = price_to_tick(max_price); - let liquidity = 2_000_000_000_u64; - - let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - liquidity, - ) - .expect("add liquidity"); - - // Accrue some global fees so we can verify fee storage is cleared later. - let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); - assert_ok!(Pallet::::do_swap( - netuid, - GetAlphaForTao::with_amount(1_000_000), - sqrt_limit_price, - false, - false - )); - - // Sanity: protocol & user positions exist, ticks exist, liquidity > 0 - let protocol_id = Pallet::::protocol_account_id(); - let prot_positions = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - assert!(!prot_positions.is_empty()); - - let user_positions = Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .collect::>(); - assert_eq!(user_positions.len(), 1); - - assert!(Ticks::::get(netuid, TickIndex::MIN).is_some()); - assert!(Ticks::::get(netuid, TickIndex::MAX).is_some()); - assert!(CurrentLiquidity::::get(netuid) > 0); - - let had_bitmap_words = TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_some(); - assert!(had_bitmap_words); - - // ACT: users-only liquidation then protocol clear - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - - // ASSERT: positions cleared (both user and protocol) - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 0 - ); - let prot_positions_after = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - assert!(prot_positions_after.is_empty()); - let user_positions_after = - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .collect::>(); - assert!(user_positions_after.is_empty()); - - // ASSERT: ticks cleared - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); - assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); - - // ASSERT: fee globals cleared - assert!(!FeeGlobalTao::::contains_key(netuid)); - assert!(!FeeGlobalAlpha::::contains_key(netuid)); - - // ASSERT: price/tick/liquidity flags cleared - assert!(!AlphaSqrtPrice::::contains_key(netuid)); - assert!(!CurrentTick::::contains_key(netuid)); - assert!(!CurrentLiquidity::::contains_key(netuid)); - assert!(!SwapV3Initialized::::contains_key(netuid)); - - // ASSERT: active tick bitmap cleared - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none() - ); - - // ASSERT: knobs removed on dereg - assert!(!FeeRate::::contains_key(netuid)); - assert!(!EnabledUserLiquidity::::contains_key(netuid)); - }); -} - -// V3 path with user liquidity disabled at teardown: -// must still remove positions and clear state (after protocol clear). +// TODO: Revise when user liquidity is available // #[test] -// fn test_liquidate_v3_with_user_liquidity_disabled() { +// fn test_liquidate_v3_removes_positions_ticks_and_state() { // new_test_ext().execute_with(|| { -// let netuid = NetUid::from(101); +// let netuid = NetUid::from(1); -// assert_ok!(Pallet::::maybe_initialize_v3(netuid)); -// assert!(SwapV3Initialized::::get(netuid)); +// // Initialize V3 (creates protocol position, ticks, price, liquidity) +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); +// assert!(PalSwapInitialized::::get(netuid)); -// // Enable temporarily to add a user position +// // Enable user LP // assert_ok!(Swap::toggle_user_liquidity( // RuntimeOrigin::root(), // netuid.into(), // true // )); -// let min_price = tick_to_price(TickIndex::MIN); -// let max_price = tick_to_price(TickIndex::MAX); -// let tick_low = price_to_tick(min_price); -// let tick_high = price_to_tick(max_price); -// let liquidity = 1_000_000_000_u64; +// // Add a user position across the full range to ensure ticks/bitmap are populated. +// let min_price = get_min_price(); +// let max_price = get_max_price(); +// let liquidity = 2_000_000_000_u64; // let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( // netuid, // &OK_COLDKEY_ACCOUNT_ID, // &OK_HOTKEY_ACCOUNT_ID, -// tick_low, -// tick_high, // liquidity, // ) // .expect("add liquidity"); -// // Disable user LP *before* liquidation; removal must ignore this flag. -// assert_ok!(Swap::toggle_user_liquidity( -// RuntimeOrigin::root(), +// // Accrue some global fees so we can verify fee storage is cleared later. +// let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); +// assert_ok!(Pallet::::do_swap( +// netuid, +// GetAlphaForTao::with_amount(1_000_000), +// sqrt_limit_price, +// false, +// false +// )); + +// // Sanity: protocol & user positions exist, ticks exist, liquidity > 0 +// let protocol_id = Pallet::::protocol_account_id(); +// let prot_positions = +// Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); +// assert!(!prot_positions.is_empty()); + +// let user_positions = Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .collect::>(); +// assert_eq!(user_positions.len(), 1); + +// assert!(Ticks::::get(netuid, TickIndex::MIN).is_some()); +// assert!(Ticks::::get(netuid, TickIndex::MAX).is_some()); +// assert!(CurrentLiquidity::::get(netuid) > 0); + +// let had_bitmap_words = TickIndexBitmapWords::::iter_prefix((netuid,)) +// .next() +// .is_some(); +// assert!(had_bitmap_words); + +// // ACT: users-only liquidation then protocol clear +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + +// // ASSERT: positions cleared (both user and protocol) +// assert_eq!( +// Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), +// 0 +// ); +// let prot_positions_after = +// Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); +// assert!(prot_positions_after.is_empty()); +// let user_positions_after = +// Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .collect::>(); +// assert!(user_positions_after.is_empty()); + +// // ASSERT: ticks cleared +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); +// assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); + +// // ASSERT: fee globals cleared +// assert!(!FeeGlobalTao::::contains_key(netuid)); +// assert!(!FeeGlobalAlpha::::contains_key(netuid)); + +// // ASSERT: price/tick/liquidity flags cleared +// assert!(!AlphaSqrtPrice::::contains_key(netuid)); +// assert!(!CurrentTick::::contains_key(netuid)); +// assert!(!CurrentLiquidity::::contains_key(netuid)); +// assert!(!PalSwapInitialized::::contains_key(netuid)); + +// // ASSERT: active tick bitmap cleared +// assert!( +// TickIndexBitmapWords::::iter_prefix((netuid,)) +// .next() +// .is_none() +// ); + +// // ASSERT: knobs removed on dereg +// assert!(!FeeRate::::contains_key(netuid)); +// assert!(!EnabledUserLiquidity::::contains_key(netuid)); +// }); +// } + +// TODO: Revise when user liquidity is available +// V3 path with user liquidity disabled at teardown: +// must still remove positions and clear state (after protocol clear). +// #[test] +// fn test_liquidate_v3_with_user_liquidity_disabled() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(101); + +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); +// assert!(PalSwapInitialized::::get(netuid)); + +// // Enable temporarily to add a user position +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid.into(), +// true +// )); + +// let min_price = tick_to_price(TickIndex::MIN); +// let max_price = tick_to_price(TickIndex::MAX); +// let tick_low = price_to_tick(min_price); +// let tick_high = price_to_tick(max_price); +// let liquidity = 1_000_000_000_u64; + +// let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// liquidity, +// ) +// .expect("add liquidity"); + +// // Disable user LP *before* liquidation; removal must ignore this flag. +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), // netuid.into(), // false // )); @@ -2105,7 +1905,7 @@ fn test_liquidate_v3_removes_positions_ticks_and_state() { // .next() // .is_none() // ); -// assert!(!SwapV3Initialized::::contains_key(netuid)); +// assert!(!PalSwapInitialized::::contains_key(netuid)); // assert!(!AlphaSqrtPrice::::contains_key(netuid)); // assert!(!CurrentTick::::contains_key(netuid)); // assert!(!CurrentLiquidity::::contains_key(netuid)); @@ -2117,634 +1917,686 @@ fn test_liquidate_v3_removes_positions_ticks_and_state() { // }); // } -/// Non‑V3 path: V3 not initialized (no positions); function must still clear any residual storages and succeed. +// Non‑palswap path: PalSwap not initialized (no positions, no map values); function +// must still clear any residual storages and succeed. +// TODO: Revise when user liquidity is available +// #[test] +// fn test_liquidate_pal_uninitialized_ok_and_clears() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(202); + +// // Insert map values +// PalSwapInitialized::::insert(netuid, false); + +// // Sanity: PalSwap is not initialized +// assert!(!PalSwapInitialized::::get(netuid)); +// assert!( +// PositionsV2::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .next() +// .is_none() +// ); + +// // ACT +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + +// // ASSERT: Defensive clears leave no residues and do not panic +// assert!( +// PositionsV2::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .next() +// .is_none() +// ); + +// // All single-key maps should not have the key after liquidation +// assert!(!FeeRate::::contains_key(netuid)); +// assert!(!EnabledUserLiquidity::::contains_key(netuid)); +// assert!(!FeesTao::::contains_key(netuid)); +// assert!(!FeesAlpha::::contains_key(netuid)); +// assert!(!PalSwapInitialized::::contains_key(netuid)); +// assert!(!SwapReserveWeight::::contains_key(netuid)); +// }); +// } + +/// Simple palswap path: PalSwap is initialized, but no positions, only protocol; function +/// must still clear any residual storages and succeed. +/// TODO: Revise when user liquidity is available #[test] -fn test_liquidate_non_v3_uninitialized_ok_and_clears() { +fn test_liquidate_pal_simple_ok_and_clears() { new_test_ext().execute_with(|| { let netuid = NetUid::from(202); - // Sanity: V3 is not initialized - assert!(!SwapV3Initialized::::get(netuid)); + // Insert map values + FeeRate::::insert(netuid, 1_000); + EnabledUserLiquidity::::insert(netuid, false); + FeesTao::::insert(netuid, TaoCurrency::from(1_000)); + FeesAlpha::::insert(netuid, AlphaCurrency::from(1_000)); + PalSwapInitialized::::insert(netuid, true); + let w_quote_pt = Perquintill::from_rational(1u128, 2u128); + let rw = ReserveWeight::new(w_quote_pt).unwrap(); + SwapReserveWeight::::insert(netuid, rw); + + // Sanity: PalSwap is not initialized + assert!(PalSwapInitialized::::get(netuid)); assert!( - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + PositionsV2::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) .next() .is_none() ); // ACT - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); // ASSERT: Defensive clears leave no residues and do not panic assert!( - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .next() - .is_none() - ); - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) + PositionsV2::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) .next() .is_none() ); // All single-key maps should not have the key after liquidation - assert!(!FeeGlobalTao::::contains_key(netuid)); - assert!(!FeeGlobalAlpha::::contains_key(netuid)); - assert!(!CurrentLiquidity::::contains_key(netuid)); - assert!(!CurrentTick::::contains_key(netuid)); - assert!(!AlphaSqrtPrice::::contains_key(netuid)); - assert!(!SwapV3Initialized::::contains_key(netuid)); - assert!(!FeeRate::::contains_key(netuid)); + assert!(!FeeRate::::contains_key(netuid)); assert!(!EnabledUserLiquidity::::contains_key(netuid)); + assert!(!FeesTao::::contains_key(netuid)); + assert!(!FeesAlpha::::contains_key(netuid)); + assert!(!PalSwapInitialized::::contains_key(netuid)); + assert!(!SwapReserveWeight::::contains_key(netuid)); }); } -#[test] -fn test_liquidate_idempotent() { - // V3 flavor - new_test_ext().execute_with(|| { - let netuid = NetUid::from(7); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Add a small user position - assert_ok!(Swap::toggle_user_liquidity( - RuntimeOrigin::root(), - netuid.into(), - true - )); - let tick_low = price_to_tick(0.2); - let tick_high = price_to_tick(0.3); - assert_ok!(Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - 123_456_789 - )); - - // Users-only liquidations are idempotent. - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // Now clear protocol liquidity/state—also idempotent. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// TODO: Revise when user liquidity is available +// #[test] +// fn test_liquidate_idempotent() { +// // V3 flavor +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(7); +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); - // State remains empty - assert!( - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .next() - .is_none() - ); - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none() - ); - assert!(!SwapV3Initialized::::contains_key(netuid)); - }); +// // Add a small user position +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid.into(), +// true +// )); +// let tick_low = price_to_tick(0.2); +// let tick_high = price_to_tick(0.3); +// assert_ok!(Pallet::::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// 123_456_789 +// )); - // Non‑V3 flavor - new_test_ext().execute_with(|| { - let netuid = NetUid::from(8); +// // Users-only liquidations are idempotent. +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - // Never initialize V3; both calls no-op and succeed. - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); +// // Now clear protocol liquidity/state—also idempotent. +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - assert!( - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .next() - .is_none() - ); - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none() - ); - assert!(!SwapV3Initialized::::contains_key(netuid)); - }); -} +// // State remains empty +// assert!( +// Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .next() +// .is_none() +// ); +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert!( +// TickIndexBitmapWords::::iter_prefix((netuid,)) +// .next() +// .is_none() +// ); +// assert!(!PalSwapInitialized::::contains_key(netuid)); +// }); -#[test] -fn liquidate_v3_refunds_user_funds_and_clears_state() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); +// // Non‑V3 flavor +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(8); - // Enable V3 path & initialize price/ticks (also creates a protocol position). - assert_ok!(Pallet::::toggle_user_liquidity( - RuntimeOrigin::root(), - netuid, - true - )); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Use distinct cold/hot to demonstrate alpha refund/stake accounting. - let cold = OK_COLDKEY_ACCOUNT_ID; - let hot = OK_HOTKEY_ACCOUNT_ID; - - // Tight in‑range band around current tick. - let ct = CurrentTick::::get(netuid); - let tick_low = ct.saturating_sub(10); - let tick_high = ct.saturating_add(10); - let liquidity: u64 = 1_000_000; - - // Snapshot balances BEFORE. - let tao_before = ::BalanceOps::tao_balance(&cold); - let alpha_before_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_before_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_before_total = alpha_before_hot + alpha_before_owner; - - // Create the user position (storage & v3 state only; no balances moved yet). - let (_pos_id, need_tao, need_alpha) = - Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) - .expect("add liquidity"); - - // Mirror extrinsic bookkeeping: withdraw funds & bump provided‑reserve counters. - let tao_taken = ::BalanceOps::decrease_balance(&cold, need_tao.into()) - .expect("decrease TAO"); - let alpha_taken = ::BalanceOps::decrease_stake( - &cold, - &hot, - netuid.into(), - need_alpha.into(), - ) - .expect("decrease ALPHA"); - TaoReserve::increase_provided(netuid.into(), tao_taken); - AlphaReserve::increase_provided(netuid.into(), alpha_taken); - - // Users‑only liquidation. - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // Expect balances restored to BEFORE snapshots (no swaps ran -> zero fees). - let tao_after = ::BalanceOps::tao_balance(&cold); - assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); - - // ALPHA totals conserved to owner (distribution may differ). - let alpha_after_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_after_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_after_total = alpha_after_hot + alpha_after_owner; - assert_eq!( - alpha_after_total, alpha_before_total, - "ALPHA principal must be refunded/staked for the account (check totals)" - ); +// // Never initialize V3; both calls no-op and succeed. +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - // Clear protocol liquidity and V3 state now. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// assert!( +// Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) +// .next() +// .is_none() +// ); +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert!( +// TickIndexBitmapWords::::iter_prefix((netuid,)) +// .next() +// .is_none() +// ); +// assert!(!PalSwapInitialized::::contains_key(netuid)); +// }); +// } - // User position(s) are gone and all V3 state cleared. - assert_eq!(Pallet::::count_positions(netuid, &cold), 0); - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!(!SwapV3Initialized::::contains_key(netuid)); - }); -} +// TODO: Revise when user liquidity is available +// #[test] +// fn liquidate_v3_refunds_user_funds_and_clears_state() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(1); -#[test] -fn refund_alpha_single_provider_exact() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(11); - let cold = OK_COLDKEY_ACCOUNT_ID; - let hot = OK_HOTKEY_ACCOUNT_ID; +// // Enable V3 path & initialize price/ticks (also creates a protocol position). +// assert_ok!(Pallet::::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid, +// true +// )); +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // Use distinct cold/hot to demonstrate alpha refund/stake accounting. +// let cold = OK_COLDKEY_ACCOUNT_ID; +// let hot = OK_HOTKEY_ACCOUNT_ID; + +// // Tight in‑range band around current tick. +// let ct = CurrentTick::::get(netuid); +// let tick_low = ct.saturating_sub(10); +// let tick_high = ct.saturating_add(10); +// let liquidity: u64 = 1_000_000; + +// // Snapshot balances BEFORE. +// let tao_before = ::BalanceOps::tao_balance(&cold); +// let alpha_before_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_before_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_before_total = alpha_before_hot + alpha_before_owner; + +// // Create the user position (storage & v3 state only; no balances moved yet). +// let (_pos_id, need_tao, need_alpha) = +// Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) +// .expect("add liquidity"); + +// // Mirror extrinsic bookkeeping: withdraw funds & bump provided‑reserve counters. +// let tao_taken = ::BalanceOps::decrease_balance(&cold, need_tao.into()) +// .expect("decrease TAO"); +// let alpha_taken = ::BalanceOps::decrease_stake( +// &cold, +// &hot, +// netuid.into(), +// need_alpha.into(), +// ) +// .expect("decrease ALPHA"); +// TaoReserve::increase_provided(netuid.into(), tao_taken); +// AlphaReserve::increase_provided(netuid.into(), alpha_taken); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); +// // Users‑only liquidation. +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - // --- Create an alpha‑only position (range entirely above current tick → TAO = 0, ALPHA > 0). - let ct = CurrentTick::::get(netuid); - let tick_low = ct.next().expect("current tick should not be MAX in tests"); - let tick_high = TickIndex::MAX; +// // Expect balances restored to BEFORE snapshots (no swaps ran -> zero fees). +// let tao_after = ::BalanceOps::tao_balance(&cold); +// assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); - let liquidity = 1_000_000_u64; - let (_pos_id, tao_needed, alpha_needed) = - Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) - .expect("add alpha-only liquidity"); - assert_eq!(tao_needed, 0, "alpha-only position must not require TAO"); - assert!(alpha_needed > 0, "alpha-only position must require ALPHA"); - - // --- Snapshot BEFORE we withdraw funds (baseline for conservation). - let alpha_before_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_before_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_before_total = alpha_before_hot + alpha_before_owner; - - // --- Mimic extrinsic bookkeeping: withdraw α and record provided reserve. - let alpha_taken = ::BalanceOps::decrease_stake( - &cold, - &hot, - netuid.into(), - alpha_needed.into(), - ) - .expect("decrease ALPHA"); - AlphaReserve::increase_provided(netuid.into(), alpha_taken); - - // --- Act: users‑only dissolve. - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // --- Assert: total α conserved to owner (may be staked to validator). - let alpha_after_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_after_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_after_total = alpha_after_hot + alpha_after_owner; - assert_eq!( - alpha_after_total, alpha_before_total, - "ALPHA principal must be conserved to the account" - ); +// // ALPHA totals conserved to owner (distribution may differ). +// let alpha_after_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_after_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_after_total = alpha_after_hot + alpha_after_owner; +// assert_eq!( +// alpha_after_total, alpha_before_total, +// "ALPHA principal must be refunded/staked for the account (check totals)" +// ); - // Clear protocol liquidity and V3 state now. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// // Clear protocol liquidity and V3 state now. +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - // --- State is cleared. - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert_eq!(Pallet::::count_positions(netuid, &cold), 0); - assert!(!SwapV3Initialized::::contains_key(netuid)); - }); -} +// // User position(s) are gone and all V3 state cleared. +// assert_eq!(Pallet::::count_positions(netuid, &cold), 0); +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert!(!PalSwapInitialized::::contains_key(netuid)); +// }); +// } -#[test] -fn refund_alpha_multiple_providers_proportional_to_principal() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(12); - let c1 = OK_COLDKEY_ACCOUNT_ID; - let h1 = OK_HOTKEY_ACCOUNT_ID; - let c2 = OK_COLDKEY_ACCOUNT_ID_2; - let h2 = OK_HOTKEY_ACCOUNT_ID_2; - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Use the same "above current tick" trick for alpha‑only positions. - let ct = CurrentTick::::get(netuid); - let tick_low = ct.next().expect("current tick should not be MAX in tests"); - let tick_high = TickIndex::MAX; - - // Provider #1 (smaller α) - let liq1 = 700_000_u64; - let (_p1, t1, a1) = - Pallet::::do_add_liquidity(netuid, &c1, &h1, tick_low, tick_high, liq1) - .expect("add alpha-only liquidity #1"); - assert_eq!(t1, 0); - assert!(a1 > 0); - - // Provider #2 (larger α) - let liq2 = 2_100_000_u64; - let (_p2, t2, a2) = - Pallet::::do_add_liquidity(netuid, &c2, &h2, tick_low, tick_high, liq2) - .expect("add alpha-only liquidity #2"); - assert_eq!(t2, 0); - assert!(a2 > 0); - - // Baselines BEFORE withdrawing - let a1_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); - let a1_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); - let a1_before = a1_before_hot + a1_before_owner; - - let a2_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); - let a2_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); - let a2_before = a2_before_hot + a2_before_owner; - - // Withdraw α and account reserves for each provider. - let a1_taken = - ::BalanceOps::decrease_stake(&c1, &h1, netuid.into(), a1.into()) - .expect("decrease α #1"); - AlphaReserve::increase_provided(netuid.into(), a1_taken); - - let a2_taken = - ::BalanceOps::decrease_stake(&c2, &h2, netuid.into(), a2.into()) - .expect("decrease α #2"); - AlphaReserve::increase_provided(netuid.into(), a2_taken); - - // Act - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // Each owner is restored to their exact baseline. - let a1_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); - let a1_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); - let a1_after = a1_after_hot + a1_after_owner; - assert_eq!( - a1_after, a1_before, - "owner #1 must receive their α principal back" - ); +// TODO: Revise when user liquidity is available +// #[test] +// fn refund_alpha_single_provider_exact() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(11); +// let cold = OK_COLDKEY_ACCOUNT_ID; +// let hot = OK_HOTKEY_ACCOUNT_ID; + +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // --- Create an alpha‑only position (range entirely above current tick → TAO = 0, ALPHA > 0). +// let ct = CurrentTick::::get(netuid); +// let tick_low = ct.next().expect("current tick should not be MAX in tests"); +// let tick_high = TickIndex::MAX; + +// let liquidity = 1_000_000_u64; +// let (_pos_id, tao_needed, alpha_needed) = +// Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) +// .expect("add alpha-only liquidity"); +// assert_eq!(tao_needed, 0, "alpha-only position must not require TAO"); +// assert!(alpha_needed > 0, "alpha-only position must require ALPHA"); + +// // --- Snapshot BEFORE we withdraw funds (baseline for conservation). +// let alpha_before_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_before_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_before_total = alpha_before_hot + alpha_before_owner; + +// // --- Mimic extrinsic bookkeeping: withdraw α and record provided reserve. +// let alpha_taken = ::BalanceOps::decrease_stake( +// &cold, +// &hot, +// netuid.into(), +// alpha_needed.into(), +// ) +// .expect("decrease ALPHA"); +// AlphaReserve::increase_provided(netuid.into(), alpha_taken); - let a2_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); - let a2_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); - let a2_after = a2_after_hot + a2_after_owner; - assert_eq!( - a2_after, a2_before, - "owner #2 must receive their α principal back" - ); - }); -} +// // --- Act: users‑only dissolve. +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); -#[test] -fn refund_alpha_same_cold_multiple_hotkeys_conserved_to_owner() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(13); - let cold = OK_COLDKEY_ACCOUNT_ID; - let hot1 = OK_HOTKEY_ACCOUNT_ID; - let hot2 = OK_HOTKEY_ACCOUNT_ID_2; - - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - - // Two alpha‑only positions on different hotkeys of the same owner. - let ct = CurrentTick::::get(netuid); - let tick_low = ct.next().expect("current tick should not be MAX in tests"); - let tick_high = TickIndex::MAX; - - let (_p1, _t1, a1) = - Pallet::::do_add_liquidity(netuid, &cold, &hot1, tick_low, tick_high, 900_000) - .expect("add alpha-only pos (hot1)"); - let (_p2, _t2, a2) = - Pallet::::do_add_liquidity(netuid, &cold, &hot2, tick_low, tick_high, 1_500_000) - .expect("add alpha-only pos (hot2)"); - assert!(a1 > 0 && a2 > 0); - - // Baseline BEFORE: sum over (cold,hot1) + (cold,hot2) + (cold,cold). - let before_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); - let before_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); - let before_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let before_total = before_hot1 + before_hot2 + before_owner; - - // Withdraw α from both hotkeys; track provided‑reserve. - let t1 = - ::BalanceOps::decrease_stake(&cold, &hot1, netuid.into(), a1.into()) - .expect("decr α #hot1"); - AlphaReserve::increase_provided(netuid.into(), t1); - - let t2 = - ::BalanceOps::decrease_stake(&cold, &hot2, netuid.into(), a2.into()) - .expect("decr α #hot2"); - AlphaReserve::increase_provided(netuid.into(), t2); - - // Act - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // The total α "owned" by the coldkey is conserved (credit may land on (cold,cold)). - let after_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); - let after_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); - let after_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let after_total = after_hot1 + after_hot2 + after_owner; +// // --- Assert: total α conserved to owner (may be staked to validator). +// let alpha_after_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_after_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_after_total = alpha_after_hot + alpha_after_owner; +// assert_eq!( +// alpha_after_total, alpha_before_total, +// "ALPHA principal must be conserved to the account" +// ); - assert_eq!( - after_total, before_total, - "owner’s α must be conserved across hot ledgers + (owner,owner)" - ); - }); -} +// // Clear protocol liquidity and V3 state now. +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); -#[test] -fn test_dissolve_v3_green_path_refund_tao_stake_alpha_and_clear_state() { - new_test_ext().execute_with(|| { - // --- Setup --- - let netuid = NetUid::from(42); - let cold = OK_COLDKEY_ACCOUNT_ID; - let hot = OK_HOTKEY_ACCOUNT_ID; - - assert_ok!(Swap::toggle_user_liquidity( - RuntimeOrigin::root(), - netuid.into(), - true - )); - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - assert!(SwapV3Initialized::::get(netuid)); - - // Tight in‑range band so BOTH τ and α are required. - let ct = CurrentTick::::get(netuid); - let tick_low = ct.saturating_sub(10); - let tick_high = ct.saturating_add(10); - let liquidity: u64 = 1_250_000; - - // Add liquidity and capture required τ/α. - let (_pos_id, tao_needed, alpha_needed) = - Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) - .expect("add in-range liquidity"); - assert!(tao_needed > 0, "in-range pos must require TAO"); - assert!(alpha_needed > 0, "in-range pos must require ALPHA"); - - // Determine the permitted validator with the highest trust (green path). - let trust = ::SubnetInfo::get_validator_trust(netuid.into()); - let permit = ::SubnetInfo::get_validator_permit(netuid.into()); - assert_eq!(trust.len(), permit.len(), "trust/permit must align"); - let target_uid: u16 = trust - .iter() - .zip(permit.iter()) - .enumerate() - .filter(|(_, (_t, p))| **p) - .max_by_key(|(_, (t, _))| *t) - .map(|(i, _)| i as u16) - .expect("at least one permitted validator"); - let validator_hotkey: ::AccountId = - ::SubnetInfo::hotkey_of_uid(netuid.into(), target_uid) - .expect("uid -> hotkey mapping must exist"); - - // --- Snapshot BEFORE we withdraw τ/α to fund the position --- - let tao_before = ::BalanceOps::tao_balance(&cold); - - let alpha_before_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_before_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_before_val = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &validator_hotkey); - - let alpha_before_total = if validator_hotkey == hot { - alpha_before_hot + alpha_before_owner - } else { - alpha_before_hot + alpha_before_owner + alpha_before_val - }; - - // --- Mirror extrinsic bookkeeping: withdraw τ & α; bump provided reserves --- - let tao_taken = ::BalanceOps::decrease_balance(&cold, tao_needed.into()) - .expect("decrease TAO"); - let alpha_taken = ::BalanceOps::decrease_stake( - &cold, - &hot, - netuid.into(), - alpha_needed.into(), - ) - .expect("decrease ALPHA"); - - TaoReserve::increase_provided(netuid.into(), tao_taken); - AlphaReserve::increase_provided(netuid.into(), alpha_taken); - - // --- Act: dissolve (GREEN PATH: permitted validators exist) --- - assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - - // --- Assert: τ principal refunded to user --- - let tao_after = ::BalanceOps::tao_balance(&cold); - assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); - - // --- α ledger assertions --- - let alpha_after_hot = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); - let alpha_after_owner = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); - let alpha_after_val = - ::BalanceOps::alpha_balance(netuid.into(), &cold, &validator_hotkey); - - // Owner ledger must be unchanged in the green path. - assert_eq!( - alpha_after_owner, alpha_before_owner, - "Owner α ledger must be unchanged (staked to validator, not refunded)" - ); +// // --- State is cleared. +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert_eq!(Pallet::::count_positions(netuid, &cold), 0); +// assert!(!PalSwapInitialized::::contains_key(netuid)); +// }); +// } - if validator_hotkey == hot { - assert_eq!( - alpha_after_hot, alpha_before_hot, - "When validator == hotkey, user's hot ledger must net back to its original balance" - ); - let alpha_after_total = alpha_after_hot + alpha_after_owner; - assert_eq!( - alpha_after_total, alpha_before_total, - "Total α for the coldkey must be conserved (validator==hotkey)" - ); - } else { - assert!( - alpha_before_hot >= alpha_after_hot, - "hot ledger should not increase" - ); - assert!( - alpha_after_val >= alpha_before_val, - "validator ledger should not decrease" - ); +// TODO: Revise when user liquidity is available +// #[test] +// fn refund_alpha_multiple_providers_proportional_to_principal() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(12); +// let c1 = OK_COLDKEY_ACCOUNT_ID; +// let h1 = OK_HOTKEY_ACCOUNT_ID; +// let c2 = OK_COLDKEY_ACCOUNT_ID_2; +// let h2 = OK_HOTKEY_ACCOUNT_ID_2; + +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // Use the same "above current tick" trick for alpha‑only positions. +// let ct = CurrentTick::::get(netuid); +// let tick_low = ct.next().expect("current tick should not be MAX in tests"); +// let tick_high = TickIndex::MAX; + +// // Provider #1 (smaller α) +// let liq1 = 700_000_u64; +// let (_p1, t1, a1) = +// Pallet::::do_add_liquidity(netuid, &c1, &h1, tick_low, tick_high, liq1) +// .expect("add alpha-only liquidity #1"); +// assert_eq!(t1, 0); +// assert!(a1 > 0); + +// // Provider #2 (larger α) +// let liq2 = 2_100_000_u64; +// let (_p2, t2, a2) = +// Pallet::::do_add_liquidity(netuid, &c2, &h2, tick_low, tick_high, liq2) +// .expect("add alpha-only liquidity #2"); +// assert_eq!(t2, 0); +// assert!(a2 > 0); + +// // Baselines BEFORE withdrawing +// let a1_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); +// let a1_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); +// let a1_before = a1_before_hot + a1_before_owner; + +// let a2_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); +// let a2_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); +// let a2_before = a2_before_hot + a2_before_owner; + +// // Withdraw α and account reserves for each provider. +// let a1_taken = +// ::BalanceOps::decrease_stake(&c1, &h1, netuid.into(), a1.into()) +// .expect("decrease α #1"); +// AlphaReserve::increase_provided(netuid.into(), a1_taken); + +// let a2_taken = +// ::BalanceOps::decrease_stake(&c2, &h2, netuid.into(), a2.into()) +// .expect("decrease α #2"); +// AlphaReserve::increase_provided(netuid.into(), a2_taken); + +// // Act +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - let hot_loss = alpha_before_hot - alpha_after_hot; - let val_gain = alpha_after_val - alpha_before_val; - assert_eq!( - val_gain, hot_loss, - "α that left the user's hot ledger must equal α credited to the validator ledger" - ); +// // Each owner is restored to their exact baseline. +// let a1_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); +// let a1_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); +// let a1_after = a1_after_hot + a1_after_owner; +// assert_eq!( +// a1_after, a1_before, +// "owner #1 must receive their α principal back" +// ); - let alpha_after_total = alpha_after_hot + alpha_after_owner + alpha_after_val; - assert_eq!( - alpha_after_total, alpha_before_total, - "Total α for the coldkey must be conserved" - ); - } +// let a2_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); +// let a2_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); +// let a2_after = a2_after_hot + a2_after_owner; +// assert_eq!( +// a2_after, a2_before, +// "owner #2 must receive their α principal back" +// ); +// }); +// } - // Now clear protocol liquidity & state and assert full reset. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// TODO: Revise when user liquidity is available +// #[test] +// fn refund_alpha_same_cold_multiple_hotkeys_conserved_to_owner() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(13); +// let cold = OK_COLDKEY_ACCOUNT_ID; +// let hot1 = OK_HOTKEY_ACCOUNT_ID; +// let hot2 = OK_HOTKEY_ACCOUNT_ID_2; + +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + +// // Two alpha‑only positions on different hotkeys of the same owner. +// let ct = CurrentTick::::get(netuid); +// let tick_low = ct.next().expect("current tick should not be MAX in tests"); +// let tick_high = TickIndex::MAX; + +// let (_p1, _t1, a1) = +// Pallet::::do_add_liquidity(netuid, &cold, &hot1, tick_low, tick_high, 900_000) +// .expect("add alpha-only pos (hot1)"); +// let (_p2, _t2, a2) = +// Pallet::::do_add_liquidity(netuid, &cold, &hot2, tick_low, tick_high, 1_500_000) +// .expect("add alpha-only pos (hot2)"); +// assert!(a1 > 0 && a2 > 0); + +// // Baseline BEFORE: sum over (cold,hot1) + (cold,hot2) + (cold,cold). +// let before_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); +// let before_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); +// let before_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let before_total = before_hot1 + before_hot2 + before_owner; + +// // Withdraw α from both hotkeys; track provided‑reserve. +// let t1 = +// ::BalanceOps::decrease_stake(&cold, &hot1, netuid.into(), a1.into()) +// .expect("decr α #hot1"); +// AlphaReserve::increase_provided(netuid.into(), t1); + +// let t2 = +// ::BalanceOps::decrease_stake(&cold, &hot2, netuid.into(), a2.into()) +// .expect("decr α #hot2"); +// AlphaReserve::increase_provided(netuid.into(), t2); + +// // Act +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - let protocol_id = Pallet::::protocol_account_id(); - assert_eq!(Pallet::::count_positions(netuid, &cold), 0); - let prot_positions_after = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - assert!( - prot_positions_after.is_empty(), - "protocol positions must be removed" - ); +// // The total α "owned" by the coldkey is conserved (credit may land on (cold,cold)). +// let after_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); +// let after_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); +// let after_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let after_total = after_hot1 + after_hot2 + after_owner; - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); - assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); - assert!(!CurrentLiquidity::::contains_key(netuid)); - assert!(!CurrentTick::::contains_key(netuid)); - assert!(!AlphaSqrtPrice::::contains_key(netuid)); - assert!(!SwapV3Initialized::::contains_key(netuid)); +// assert_eq!( +// after_total, before_total, +// "owner’s α must be conserved across hot ledgers + (owner,owner)" +// ); +// }); +// } - assert!(!FeeGlobalTao::::contains_key(netuid)); - assert!(!FeeGlobalAlpha::::contains_key(netuid)); +// TODO: Revise when user liquidity is available +// #[test] +// fn test_dissolve_v3_green_path_refund_tao_stake_alpha_and_clear_state() { +// new_test_ext().execute_with(|| { +// // --- Setup --- +// let netuid = NetUid::from(42); +// let cold = OK_COLDKEY_ACCOUNT_ID; +// let hot = OK_HOTKEY_ACCOUNT_ID; - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none(), - "active tick bitmap words must be cleared" - ); +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid.into(), +// true +// )); +// assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); +// assert!(PalSwapInitialized::::get(netuid)); + +// // Tight in‑range band so BOTH τ and α are required. +// let ct = CurrentTick::::get(netuid); +// let tick_low = ct.saturating_sub(10); +// let tick_high = ct.saturating_add(10); +// let liquidity: u64 = 1_250_000; + +// // Add liquidity and capture required τ/α. +// let (_pos_id, tao_needed, alpha_needed) = +// Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) +// .expect("add in-range liquidity"); +// assert!(tao_needed > 0, "in-range pos must require TAO"); +// assert!(alpha_needed > 0, "in-range pos must require ALPHA"); + +// // Determine the permitted validator with the highest trust (green path). +// let trust = ::SubnetInfo::get_validator_trust(netuid.into()); +// let permit = ::SubnetInfo::get_validator_permit(netuid.into()); +// assert_eq!(trust.len(), permit.len(), "trust/permit must align"); +// let target_uid: u16 = trust +// .iter() +// .zip(permit.iter()) +// .enumerate() +// .filter(|(_, (_t, p))| **p) +// .max_by_key(|(_, (t, _))| *t) +// .map(|(i, _)| i as u16) +// .expect("at least one permitted validator"); +// let validator_hotkey: ::AccountId = +// ::SubnetInfo::hotkey_of_uid(netuid.into(), target_uid) +// .expect("uid -> hotkey mapping must exist"); + +// // --- Snapshot BEFORE we withdraw τ/α to fund the position --- +// let tao_before = ::BalanceOps::tao_balance(&cold); + +// let alpha_before_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_before_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_before_val = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &validator_hotkey); + +// let alpha_before_total = if validator_hotkey == hot { +// alpha_before_hot + alpha_before_owner +// } else { +// alpha_before_hot + alpha_before_owner + alpha_before_val +// }; + +// // --- Mirror extrinsic bookkeeping: withdraw τ & α; bump provided reserves --- +// let tao_taken = ::BalanceOps::decrease_balance(&cold, tao_needed.into()) +// .expect("decrease TAO"); +// let alpha_taken = ::BalanceOps::decrease_stake( +// &cold, +// &hot, +// netuid.into(), +// alpha_needed.into(), +// ) +// .expect("decrease ALPHA"); - assert!(!FeeRate::::contains_key(netuid)); - assert!(!EnabledUserLiquidity::::contains_key(netuid)); - }); -} +// TaoReserve::increase_provided(netuid.into(), tao_taken); +// AlphaReserve::increase_provided(netuid.into(), alpha_taken); -#[test] -fn test_clear_protocol_liquidity_green_path() { - new_test_ext().execute_with(|| { - // --- Arrange --- - let netuid = NetUid::from(55); - - // Ensure the "user liquidity enabled" flag exists so we can verify it's removed later. - assert_ok!(Pallet::::toggle_user_liquidity( - RuntimeOrigin::root(), - netuid, - true - )); - - // Initialize V3 state; this should set price/tick flags and create a protocol position. - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - assert!( - SwapV3Initialized::::get(netuid), - "V3 must be initialized" - ); +// // --- Act: dissolve (GREEN PATH: permitted validators exist) --- +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); - // Sanity: protocol positions exist before clearing. - let protocol_id = Pallet::::protocol_account_id(); - let prot_positions_before = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - assert!( - !prot_positions_before.is_empty(), - "protocol positions should exist after V3 init" - ); +// // --- Assert: τ principal refunded to user --- +// let tao_after = ::BalanceOps::tao_balance(&cold); +// assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); - // --- Act --- - // Green path: just clear protocol liquidity and wipe all V3 state. - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); +// // --- α ledger assertions --- +// let alpha_after_hot = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); +// let alpha_after_owner = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); +// let alpha_after_val = +// ::BalanceOps::alpha_balance(netuid.into(), &cold, &validator_hotkey); - // --- Assert: all protocol positions removed --- - let prot_positions_after = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - assert!( - prot_positions_after.is_empty(), - "protocol positions must be removed by do_clear_protocol_liquidity" - ); +// // Owner ledger must be unchanged in the green path. +// assert_eq!( +// alpha_after_owner, alpha_before_owner, +// "Owner α ledger must be unchanged (staked to validator, not refunded)" +// ); - // --- Assert: V3 data wiped (idempotent even if some maps were empty) --- - // Ticks / active tick bitmap - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none(), - "active tick bitmap words must be cleared" - ); +// if validator_hotkey == hot { +// assert_eq!( +// alpha_after_hot, alpha_before_hot, +// "When validator == hotkey, user's hot ledger must net back to its original balance" +// ); +// let alpha_after_total = alpha_after_hot + alpha_after_owner; +// assert_eq!( +// alpha_after_total, alpha_before_total, +// "Total α for the coldkey must be conserved (validator==hotkey)" +// ); +// } else { +// assert!( +// alpha_before_hot >= alpha_after_hot, +// "hot ledger should not increase" +// ); +// assert!( +// alpha_after_val >= alpha_before_val, +// "validator ledger should not decrease" +// ); + +// let hot_loss = alpha_before_hot - alpha_after_hot; +// let val_gain = alpha_after_val - alpha_before_val; +// assert_eq!( +// val_gain, hot_loss, +// "α that left the user's hot ledger must equal α credited to the validator ledger" +// ); + +// let alpha_after_total = alpha_after_hot + alpha_after_owner + alpha_after_val; +// assert_eq!( +// alpha_after_total, alpha_before_total, +// "Total α for the coldkey must be conserved" +// ); +// } + +// // Now clear protocol liquidity & state and assert full reset. +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - // Fee globals - assert!(!FeeGlobalTao::::contains_key(netuid)); - assert!(!FeeGlobalAlpha::::contains_key(netuid)); +// let protocol_id = Pallet::::protocol_account_id(); +// assert_eq!(Pallet::::count_positions(netuid, &cold), 0); +// let prot_positions_after = +// Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); +// assert!( +// prot_positions_after.is_empty(), +// "protocol positions must be removed" +// ); - // Price / tick / liquidity / flags - assert!(!AlphaSqrtPrice::::contains_key(netuid)); - assert!(!CurrentTick::::contains_key(netuid)); - assert!(!CurrentLiquidity::::contains_key(netuid)); - assert!(!SwapV3Initialized::::contains_key(netuid)); +// assert!(Ticks::::iter_prefix(netuid).next().is_none()); +// assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); +// assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); +// assert!(!CurrentLiquidity::::contains_key(netuid)); +// assert!(!CurrentTick::::contains_key(netuid)); +// assert!(!AlphaSqrtPrice::::contains_key(netuid)); +// assert!(!PalSwapInitialized::::contains_key(netuid)); - // Knobs removed - assert!(!FeeRate::::contains_key(netuid)); - assert!(!EnabledUserLiquidity::::contains_key(netuid)); +// assert!(!FeeGlobalTao::::contains_key(netuid)); +// assert!(!FeeGlobalAlpha::::contains_key(netuid)); - // --- And it's idempotent --- - assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); - assert!( - Positions::::iter_prefix_values((netuid, protocol_id)) - .next() - .is_none() - ); - assert!(Ticks::::iter_prefix(netuid).next().is_none()); - assert!( - TickIndexBitmapWords::::iter_prefix((netuid,)) - .next() - .is_none() - ); - assert!(!SwapV3Initialized::::contains_key(netuid)); - }); +// assert!( +// TickIndexBitmapWords::::iter_prefix((netuid,)) +// .next() +// .is_none(), +// "active tick bitmap words must be cleared" +// ); + +// assert!(!FeeRate::::contains_key(netuid)); +// assert!(!EnabledUserLiquidity::::contains_key(netuid)); +// }); +// } + +#[test] +fn test_clear_protocol_liquidity_green_path() { + todo!(); + + // new_test_ext().execute_with(|| { + // // --- Arrange --- + // let netuid = NetUid::from(55); + + // // Ensure the "user liquidity enabled" flag exists so we can verify it's removed later. + // assert_ok!(Pallet::::toggle_user_liquidity( + // RuntimeOrigin::root(), + // netuid, + // true + // )); + + // // Initialize V3 state; this should set price/tick flags and create a protocol position. + // assert_ok!(Pallet::::maybe_initialize_palswap(netuid)); + // assert!( + // PalSwapInitialized::::get(netuid), + // "V3 must be initialized" + // ); + + // // Sanity: protocol positions exist before clearing. + // let protocol_id = Pallet::::protocol_account_id(); + // let prot_positions_before = + // Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + // assert!( + // !prot_positions_before.is_empty(), + // "protocol positions should exist after V3 init" + // ); + + // // --- Act --- + // // Green path: just clear protocol liquidity and wipe all V3 state. + // assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + + // // --- Assert: all protocol positions removed --- + // let prot_positions_after = + // Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + // assert!( + // prot_positions_after.is_empty(), + // "protocol positions must be removed by do_clear_protocol_liquidity" + // ); + + // // --- Assert: V3 data wiped (idempotent even if some maps were empty) --- + // // Ticks / active tick bitmap + // assert!(Ticks::::iter_prefix(netuid).next().is_none()); + // assert!( + // TickIndexBitmapWords::::iter_prefix((netuid,)) + // .next() + // .is_none(), + // "active tick bitmap words must be cleared" + // ); + + // // Fee globals + // assert!(!FeeGlobalTao::::contains_key(netuid)); + // assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + // // Price / tick / liquidity / flags + // assert!(!AlphaSqrtPrice::::contains_key(netuid)); + // assert!(!CurrentTick::::contains_key(netuid)); + // assert!(!CurrentLiquidity::::contains_key(netuid)); + // assert!(!PalSwapInitialized::::contains_key(netuid)); + + // // Knobs removed + // assert!(!FeeRate::::contains_key(netuid)); + // assert!(!EnabledUserLiquidity::::contains_key(netuid)); + + // // --- And it's idempotent --- + // assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + // assert!( + // Positions::::iter_prefix_values((netuid, protocol_id)) + // .next() + // .is_none() + // ); + // assert!(Ticks::::iter_prefix(netuid).next().is_none()); + // assert!( + // TickIndexBitmapWords::::iter_prefix((netuid,)) + // .next() + // .is_none() + // ); + // assert!(!PalSwapInitialized::::contains_key(netuid)); + // }); } +#[allow(dead_code)] fn as_tuple( (t_used, a_used, t_rem, a_rem): (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency), ) -> (u64, u64, u64, u64) { @@ -2755,161 +2607,3 @@ fn as_tuple( u64::from(a_rem), ) } - -#[test] -fn proportional_when_price_is_one_and_tao_is_plenty() { - // sqrt_price = 1.0 => price = 1.0 - let sqrt = U64F64::from_num(1u64); - let amount_tao: TaoCurrency = 10u64.into(); - let amount_alpha: AlphaCurrency = 3u64.into(); - - // alpha * price = 3 * 1 = 3 <= amount_tao(10) - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (3, 3, 7, 0)); -} - -#[test] -fn proportional_when_price_is_one_and_alpha_is_excess() { - // sqrt_price = 1.0 => price = 1.0 - let sqrt = U64F64::from_num(1u64); - let amount_tao: TaoCurrency = 5u64.into(); - let amount_alpha: AlphaCurrency = 10u64.into(); - - // tao is limiting: alpha_equiv = floor(5 / 1) = 5 - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (5, 5, 0, 5)); -} - -#[test] -fn proportional_with_higher_price_and_alpha_limiting() { - // Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it) - let sqrt = U64F64::from_num(2u64); - let amount_tao: TaoCurrency = 85u64.into(); - let amount_alpha: AlphaCurrency = 20u64.into(); - - // tao_equivalent = alpha * price = 20 * 4 = 80 < 85 => alpha limits tao - // remainders: tao 5, alpha 0 - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (80, 20, 5, 0)); -} - -#[test] -fn proportional_with_higher_price_and_tao_limiting() { - // Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it) - let sqrt = U64F64::from_num(2u64); - let amount_tao: TaoCurrency = 50u64.into(); - let amount_alpha: AlphaCurrency = 20u64.into(); - - // tao_equivalent = alpha * price = 20 * 4 = 80 > 50 => tao limits alpha - // alpha_equivalent = floor(50 / 4) = 12 - // remainders: tao 0, alpha 20 - 12 = 8 - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (50, 12, 0, 8)); -} - -#[test] -fn zero_price_uses_no_tao_and_all_alpha() { - // sqrt_price = 0 => price = 0 - let sqrt = U64F64::from_num(0u64); - let amount_tao: TaoCurrency = 42u64.into(); - let amount_alpha: AlphaCurrency = 17u64.into(); - - // tao_equivalent = 17 * 0 = 0 <= 42 - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (0, 17, 42, 0)); -} - -#[test] -fn rounding_down_behavior_when_dividing_by_price() { - // sqrt_price = 2.0 => price = 4.0 - let sqrt = U64F64::from_num(2u64); - let amount_tao: TaoCurrency = 13u64.into(); - let amount_alpha: AlphaCurrency = 100u64.into(); - - // tao is limiting; alpha_equiv = floor(13 / 4) = 3 - // remainders: tao 0, alpha 100 - 3 = 97 - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (13, 3, 0, 97)); -} - -#[test] -fn exact_fit_when_tao_matches_alpha_times_price() { - // sqrt_price = 1.0 => price = 1.0 - let sqrt = U64F64::from_num(1u64); - let amount_tao: TaoCurrency = 9u64.into(); - let amount_alpha: AlphaCurrency = 9u64.into(); - - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha); - assert_eq!(as_tuple(out), (9, 9, 0, 0)); -} - -#[test] -fn handles_zero_balances() { - let sqrt = U64F64::from_num(1u64); - - // Zero TAO, some alpha - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 7u64.into()); - // tao limits; alpha_equiv = floor(0 / 1) = 0 - assert_eq!(as_tuple(out), (0, 0, 0, 7)); - - // Some TAO, zero alpha - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, 7u64.into(), 0u64.into()); - // tao_equiv = 0 * 1 = 0 <= 7 - assert_eq!(as_tuple(out), (0, 0, 7, 0)); - - // Both zero - let out = - Pallet::::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 0u64.into()); - assert_eq!(as_tuple(out), (0, 0, 0, 0)); -} - -#[test] -fn adjust_protocol_liquidity_uses_and_sets_scrap_reservoirs() { - new_test_ext().execute_with(|| { - // --- Arrange - let netuid: NetUid = 1u16.into(); - // Price = 1.0 (since sqrt_price^2 = 1), so proportional match is 1:1 - AlphaSqrtPrice::::insert(netuid, U64F64::saturating_from_num(1u64)); - - // Start with some non-zero scrap reservoirs - ScrapReservoirTao::::insert(netuid, TaoCurrency::from(7u64)); - ScrapReservoirAlpha::::insert(netuid, AlphaCurrency::from(5u64)); - - // Create a minimal protocol position so the function’s body executes. - let protocol = Pallet::::protocol_account_id(); - let position = Position::new( - PositionId::from(0), - netuid, - TickIndex::MIN, - TickIndex::MAX, - 0, - ); - // Ensure collect_fees() returns (0,0) via zeroed fees in `position` (default). - Positions::::insert((netuid, protocol, position.id), position.clone()); - - // --- Act - // No external deltas or fees; only reservoirs should be considered. - // With price=1, the exact proportional pair uses 5 alpha and 5 tao, - // leaving tao scrap = 7 - 5 = 2, alpha scrap = 5 - 5 = 0. - Pallet::::adjust_protocol_liquidity(netuid, 0u64.into(), 0u64.into()); - - // --- Assert: reservoirs were READ (used in proportional calc) and then SET (updated) - assert_eq!( - ScrapReservoirTao::::get(netuid), - TaoCurrency::from(2u64) - ); - assert_eq!( - ScrapReservoirAlpha::::get(netuid), - AlphaCurrency::from(0u64) - ); - }); -} diff --git a/pallets/swap/src/position.rs b/pallets/swap/src/position.rs index 5a57928a93..a514e60e8f 100644 --- a/pallets/swap/src/position.rs +++ b/pallets/swap/src/position.rs @@ -1,21 +1,19 @@ use core::marker::PhantomData; -use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::pallet_prelude::*; -use safe_math::*; -use substrate_fixed::types::{I64F64, U64F64}; +// use safe_math::*; +use substrate_fixed::types::U64F64; use subtensor_macros::freeze_struct; use subtensor_runtime_common::NetUid; -use crate::SqrtPrice; -use crate::pallet::{Config, Error, FeeGlobalAlpha, FeeGlobalTao, LastPositionId}; -use crate::tick::TickIndex; +use crate::pallet::{Config, Error, LastPositionId}; /// Position designates one liquidity position. /// /// Alpha price is expressed in rao units per one 10^9 unit. For example, /// price 1_000_000 is equal to 0.001 TAO per Alpha. -#[freeze_struct("27a1bf8c59480f0")] +#[freeze_struct("3f68e54e8969f976")] #[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)] #[scale_info(skip_type_params(T))] pub struct Position { @@ -23,16 +21,12 @@ pub struct Position { pub id: PositionId, /// Network identifier pub netuid: NetUid, - /// Tick index for lower boundary of price - pub tick_low: TickIndex, - /// Tick index for higher boundary of price - pub tick_high: TickIndex, - /// Position liquidity - pub liquidity: u64, - /// Fees accrued by the position in quote currency (TAO) relative to global fees - pub fees_tao: I64F64, - /// Fees accrued by the position in base currency (Alpha) relative to global fees - pub fees_alpha: I64F64, + // /// Position liquidity + // pub liquidity: u64, + // /// Fees accrued by the position in quote currency (TAO) relative to global fees + // pub fees_tao: I64F64, + // /// Fees accrued by the position in base currency (Alpha) relative to global fees + // pub fees_alpha: I64F64, /// Phantom marker for generic Config type pub _phantom: PhantomData, } @@ -41,23 +35,19 @@ impl Position { pub fn new( id: PositionId, netuid: NetUid, - tick_low: TickIndex, - tick_high: TickIndex, - liquidity: u64, + // liquidity: u64, ) -> Self { - let mut position = Position { + let position = Position { id, netuid, - tick_low, - tick_high, - liquidity, - fees_tao: I64F64::saturating_from_num(0), - fees_alpha: I64F64::saturating_from_num(0), + // liquidity, + // fees_tao: I64F64::saturating_from_num(0), + // fees_alpha: I64F64::saturating_from_num(0), _phantom: PhantomData, }; - position.fees_tao = position.fees_in_range(true); - position.fees_alpha = position.fees_in_range(false); + // position.fees_tao = position.fees_in_range(true); + // position.fees_alpha = position.fees_in_range(false); position } @@ -66,96 +56,57 @@ impl Position { /// /// returns tuple of (TAO, Alpha) /// - /// Pseudocode: - /// if self.sqrt_price_curr < sqrt_pa: - /// tao = 0 - /// alpha = L * (1 / sqrt_pa - 1 / sqrt_pb) - /// elif self.sqrt_price_curr > sqrt_pb: - /// tao = L * (sqrt_pb - sqrt_pa) - /// alpha = 0 - /// else: - /// tao = L * (self.sqrt_price_curr - sqrt_pa) - /// alpha = L * (1 / self.sqrt_price_curr - 1 / sqrt_pb) - /// - pub fn to_token_amounts(&self, sqrt_price_curr: SqrtPrice) -> Result<(u64, u64), Error> { - let one = U64F64::saturating_from_num(1); - - let sqrt_price_low = self - .tick_low - .try_to_sqrt_price() - .map_err(|_| Error::::InvalidTickRange)?; - let sqrt_price_high = self - .tick_high - .try_to_sqrt_price() - .map_err(|_| Error::::InvalidTickRange)?; - let liquidity_fixed = U64F64::saturating_from_num(self.liquidity); - - Ok(if sqrt_price_curr < sqrt_price_low { - ( - 0, - liquidity_fixed - .saturating_mul( - one.safe_div(sqrt_price_low) - .saturating_sub(one.safe_div(sqrt_price_high)), - ) - .saturating_to_num::(), - ) - } else if sqrt_price_curr > sqrt_price_high { - ( - liquidity_fixed - .saturating_mul(sqrt_price_high.saturating_sub(sqrt_price_low)) - .saturating_to_num::(), - 0, - ) - } else { - ( - liquidity_fixed - .saturating_mul(sqrt_price_curr.saturating_sub(sqrt_price_low)) - .saturating_to_num::(), - liquidity_fixed - .saturating_mul( - one.safe_div(sqrt_price_curr) - .saturating_sub(one.safe_div(sqrt_price_high)), - ) - .saturating_to_num::(), - ) - }) + pub fn to_token_amounts(&self, _price_curr: U64F64) -> Result<(u64, u64), Error> { + // let one = U64F64::saturating_from_num(1); + + // let sqrt_price_low = self + // .tick_low + // .try_to_sqrt_price() + // .map_err(|_| Error::::InvalidTickRange)?; + // let sqrt_price_high = self + // .tick_high + // .try_to_sqrt_price() + // .map_err(|_| Error::::InvalidTickRange)?; + // let liquidity_fixed = U64F64::saturating_from_num(self.liquidity); + + // Ok(if sqrt_price_curr < sqrt_price_low { + // ( + // 0, + // liquidity_fixed + // .saturating_mul( + // one.safe_div(sqrt_price_low) + // .saturating_sub(one.safe_div(sqrt_price_high)), + // ) + // .saturating_to_num::(), + // ) + // } else if sqrt_price_curr > sqrt_price_high { + // ( + // liquidity_fixed + // .saturating_mul(sqrt_price_high.saturating_sub(sqrt_price_low)) + // .saturating_to_num::(), + // 0, + // ) + // } else { + // ( + // liquidity_fixed + // .saturating_mul(sqrt_price_curr.saturating_sub(sqrt_price_low)) + // .saturating_to_num::(), + // liquidity_fixed + // .saturating_mul( + // one.safe_div(sqrt_price_curr) + // .saturating_sub(one.safe_div(sqrt_price_high)), + // ) + // .saturating_to_num::(), + // ) + // }) + + todo!() } /// Collect fees for a position /// Updates the position pub fn collect_fees(&mut self) -> (u64, u64) { - let fee_tao_agg = self.fees_in_range(true); - let fee_alpha_agg = self.fees_in_range(false); - - let mut fee_tao = fee_tao_agg.saturating_sub(self.fees_tao); - let mut fee_alpha = fee_alpha_agg.saturating_sub(self.fees_alpha); - - self.fees_tao = fee_tao_agg; - self.fees_alpha = fee_alpha_agg; - - let liquidity_frac = I64F64::saturating_from_num(self.liquidity); - - fee_tao = liquidity_frac.saturating_mul(fee_tao); - fee_alpha = liquidity_frac.saturating_mul(fee_alpha); - - ( - fee_tao.saturating_to_num::(), - fee_alpha.saturating_to_num::(), - ) - } - - /// Get fees in a position's range - /// - /// If quote flag is true, Tao is returned, otherwise alpha. - fn fees_in_range(&self, quote: bool) -> I64F64 { - if quote { - I64F64::saturating_from_num(FeeGlobalTao::::get(self.netuid)) - } else { - I64F64::saturating_from_num(FeeGlobalAlpha::::get(self.netuid)) - } - .saturating_sub(self.tick_low.fees_below::(self.netuid, quote)) - .saturating_sub(self.tick_high.fees_above::(self.netuid, quote)) + todo!() } } diff --git a/pallets/swap/src/tick.rs b/pallets/swap/src/tick.rs deleted file mode 100644 index d3493fde45..0000000000 --- a/pallets/swap/src/tick.rs +++ /dev/null @@ -1,2198 +0,0 @@ -//! The math is adapted from github.com/0xKitsune/uniswap-v3-math -use core::cmp::Ordering; -use core::convert::TryFrom; -use core::error::Error; -use core::fmt; -use core::hash::Hash; -use core::ops::{Add, AddAssign, BitOr, Deref, Neg, Shl, Shr, Sub, SubAssign}; - -use alloy_primitives::{I256, U256}; -use codec::{Decode, DecodeWithMemTracking, Encode, Error as CodecError, Input, MaxEncodedLen}; -use frame_support::pallet_prelude::*; -use safe_math::*; -use sp_std::vec; -use sp_std::vec::Vec; -use substrate_fixed::types::{I64F64, U64F64}; -use subtensor_macros::freeze_struct; -use subtensor_runtime_common::NetUid; - -use crate::SqrtPrice; -use crate::pallet::{ - Config, CurrentTick, FeeGlobalAlpha, FeeGlobalTao, TickIndexBitmapWords, Ticks, -}; - -const U256_1: U256 = U256::from_limbs([1, 0, 0, 0]); -const U256_2: U256 = U256::from_limbs([2, 0, 0, 0]); -const U256_3: U256 = U256::from_limbs([3, 0, 0, 0]); -const U256_4: U256 = U256::from_limbs([4, 0, 0, 0]); -const U256_5: U256 = U256::from_limbs([5, 0, 0, 0]); -const U256_6: U256 = U256::from_limbs([6, 0, 0, 0]); -const U256_7: U256 = U256::from_limbs([7, 0, 0, 0]); -const U256_8: U256 = U256::from_limbs([8, 0, 0, 0]); -const U256_15: U256 = U256::from_limbs([15, 0, 0, 0]); -const U256_16: U256 = U256::from_limbs([16, 0, 0, 0]); -const U256_32: U256 = U256::from_limbs([32, 0, 0, 0]); -const U256_64: U256 = U256::from_limbs([64, 0, 0, 0]); -const U256_127: U256 = U256::from_limbs([127, 0, 0, 0]); -const U256_128: U256 = U256::from_limbs([128, 0, 0, 0]); -const U256_255: U256 = U256::from_limbs([255, 0, 0, 0]); - -const U256_256: U256 = U256::from_limbs([256, 0, 0, 0]); -const U256_512: U256 = U256::from_limbs([512, 0, 0, 0]); -const U256_1024: U256 = U256::from_limbs([1024, 0, 0, 0]); -const U256_2048: U256 = U256::from_limbs([2048, 0, 0, 0]); -const U256_4096: U256 = U256::from_limbs([4096, 0, 0, 0]); -const U256_8192: U256 = U256::from_limbs([8192, 0, 0, 0]); -const U256_16384: U256 = U256::from_limbs([16384, 0, 0, 0]); -const U256_32768: U256 = U256::from_limbs([32768, 0, 0, 0]); -const U256_65536: U256 = U256::from_limbs([65536, 0, 0, 0]); -const U256_131072: U256 = U256::from_limbs([131072, 0, 0, 0]); -const U256_262144: U256 = U256::from_limbs([262144, 0, 0, 0]); -const U256_524288: U256 = U256::from_limbs([524288, 0, 0, 0]); - -const U256_MAX_TICK: U256 = U256::from_limbs([887272, 0, 0, 0]); - -const MIN_TICK: i32 = -887272; -const MAX_TICK: i32 = -MIN_TICK; - -const MIN_SQRT_RATIO: U256 = U256::from_limbs([4295128739, 0, 0, 0]); -const MAX_SQRT_RATIO: U256 = - U256::from_limbs([6743328256752651558, 17280870778742802505, 4294805859, 0]); - -const SQRT_10001: I256 = I256::from_raw(U256::from_limbs([11745905768312294533, 13863, 0, 0])); -const TICK_LOW: I256 = I256::from_raw(U256::from_limbs([ - 6552757943157144234, - 184476617836266586, - 0, - 0, -])); -const TICK_HIGH: I256 = I256::from_raw(U256::from_limbs([ - 4998474450511881007, - 15793544031827761793, - 0, - 0, -])); - -/// Tick is the price range determined by tick index (not part of this struct, but is the key at -/// which the Tick is stored in state hash maps). Tick struct stores liquidity and fee information. -/// -/// - Net liquidity -/// - Gross liquidity -/// - Fees (above global) in both currencies -#[freeze_struct("ff1bce826e64c4aa")] -#[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq)] -pub struct Tick { - pub liquidity_net: i128, - pub liquidity_gross: u64, - pub fees_out_tao: I64F64, - pub fees_out_alpha: I64F64, -} - -impl Tick { - pub fn liquidity_net_as_u64(&self) -> u64 { - self.liquidity_net.abs().min(u64::MAX as i128) as u64 - } -} - -/// Struct representing a tick index -#[freeze_struct("13c1f887258657f2")] -#[derive( - Debug, - Default, - Clone, - Copy, - Encode, - DecodeWithMemTracking, - TypeInfo, - MaxEncodedLen, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, -)] -pub struct TickIndex(i32); - -impl Decode for TickIndex { - fn decode(input: &mut I) -> Result { - let raw = i32::decode(input)?; - TickIndex::new(raw).map_err(|_| "TickIndex out of bounds".into()) - } -} - -impl Add for TickIndex { - type Output = Self; - - #[allow(clippy::arithmetic_side_effects)] - fn add(self, rhs: Self) -> Self::Output { - // Note: This assumes the result is within bounds. - // For a safer implementation, consider using checked_add. - Self::new_unchecked(self.get() + rhs.get()) - } -} - -impl Sub for TickIndex { - type Output = Self; - - #[allow(clippy::arithmetic_side_effects)] - fn sub(self, rhs: Self) -> Self::Output { - // Note: This assumes the result is within bounds. - // For a safer implementation, consider using checked_sub. - Self::new_unchecked(self.get() - rhs.get()) - } -} - -impl AddAssign for TickIndex { - #[allow(clippy::arithmetic_side_effects)] - fn add_assign(&mut self, rhs: Self) { - *self = Self::new_unchecked(self.get() + rhs.get()); - } -} - -impl SubAssign for TickIndex { - #[allow(clippy::arithmetic_side_effects)] - fn sub_assign(&mut self, rhs: Self) { - *self = Self::new_unchecked(self.get() - rhs.get()); - } -} - -impl TryFrom for TickIndex { - type Error = TickMathError; - - fn try_from(value: i32) -> Result { - Self::new(value) - } -} - -impl Deref for TickIndex { - type Target = i32; - - fn deref(&self) -> &Self::Target { - // Using get() would create an infinite recursion, so this is one place where we need direct - // field access. This is safe because Self::Target is i32, which is exactly what we're - // storing - &self.0 - } -} - -/// Extension trait to make working with TryFrom more ergonomic -pub trait TryIntoTickIndex { - /// Convert an i32 into a TickIndex, with bounds checking - fn into_tick_index(self) -> Result; -} - -impl TryIntoTickIndex for i32 { - fn into_tick_index(self) -> Result { - TickIndex::try_from(self) - } -} - -impl TickIndex { - /// Minimum value of the tick index - /// The tick_math library uses different bitness, so we have to divide by 2. - /// It's unsafe to change this value to something else. - pub const MIN: Self = Self(MIN_TICK.saturating_div(2)); - - /// Maximum value of the tick index - /// The tick_math library uses different bitness, so we have to divide by 2. - /// It's unsafe to change this value to something else. - pub const MAX: Self = Self(MAX_TICK.saturating_div(2)); - - /// All tick indexes are offset by this value for storage needs - /// so that tick indexes are positive, which simplifies bit logic - const OFFSET: Self = Self(MAX_TICK); - - /// The MIN sqrt price, which is caclculated at Self::MIN - pub fn min_sqrt_price() -> SqrtPrice { - SqrtPrice::saturating_from_num(0.0000000002328350195) - } - - /// The MAX sqrt price, which is calculated at Self::MAX - #[allow(clippy::excessive_precision)] - pub fn max_sqrt_price() -> SqrtPrice { - SqrtPrice::saturating_from_num(4294886577.20989222513899790805) - } - - /// Get fees above a tick - pub fn fees_above(&self, netuid: NetUid, quote: bool) -> I64F64 { - let current_tick = Self::current_bounded::(netuid); - - let tick = Ticks::::get(netuid, *self).unwrap_or_default(); - if *self <= current_tick { - if quote { - I64F64::saturating_from_num(FeeGlobalTao::::get(netuid)) - .saturating_sub(tick.fees_out_tao) - } else { - I64F64::saturating_from_num(FeeGlobalAlpha::::get(netuid)) - .saturating_sub(tick.fees_out_alpha) - } - } else if quote { - tick.fees_out_tao - } else { - tick.fees_out_alpha - } - } - - /// Get fees below a tick - pub fn fees_below(&self, netuid: NetUid, quote: bool) -> I64F64 { - let current_tick = Self::current_bounded::(netuid); - - let tick = Ticks::::get(netuid, *self).unwrap_or_default(); - if *self <= current_tick { - if quote { - tick.fees_out_tao - } else { - tick.fees_out_alpha - } - } else if quote { - I64F64::saturating_from_num(FeeGlobalTao::::get(netuid)) - .saturating_sub(tick.fees_out_tao) - } else { - I64F64::saturating_from_num(FeeGlobalAlpha::::get(netuid)) - .saturating_sub(tick.fees_out_alpha) - } - } - - /// Get the current tick index for a subnet, ensuring it's within valid bounds - pub fn current_bounded(netuid: NetUid) -> Self { - let current_tick = CurrentTick::::get(netuid); - if current_tick > Self::MAX { - Self::MAX - } else if current_tick < Self::MIN { - Self::MIN - } else { - current_tick - } - } - - /// Converts a sqrt price to a tick index, ensuring it's within valid bounds - /// - /// If the price is outside the valid range, this function will return the appropriate boundary - /// tick index (MIN or MAX) instead of an error. - /// - /// # Arguments - /// * `sqrt_price` - The square root price to convert to a tick index - /// - /// # Returns - /// * `TickIndex` - A tick index that is guaranteed to be within valid bounds - pub fn from_sqrt_price_bounded(sqrt_price: SqrtPrice) -> Self { - match Self::try_from_sqrt_price(sqrt_price) { - Ok(index) => index, - Err(_) => { - let max_price = Self::MAX.as_sqrt_price_bounded(); - - if sqrt_price > max_price { - Self::MAX - } else { - Self::MIN - } - } - } - } - - /// Converts a tick index to a sqrt price, ensuring it's within valid bounds - /// - /// Unlike try_to_sqrt_price which returns an error for boundary indices, this function - /// guarantees a valid sqrt price by using fallback values if conversion fails. - /// - /// # Returns - /// * `SqrtPrice` - A sqrt price that is guaranteed to be a valid value - pub fn as_sqrt_price_bounded(&self) -> SqrtPrice { - self.try_to_sqrt_price().unwrap_or_else(|_| { - if *self >= Self::MAX { - Self::max_sqrt_price() - } else { - Self::min_sqrt_price() - } - }) - } - - /// Creates a new TickIndex instance with bounds checking - pub fn new(value: i32) -> Result { - if !(Self::MIN.0..=Self::MAX.0).contains(&value) { - Err(TickMathError::TickOutOfBounds) - } else { - Ok(Self(value)) - } - } - - /// Creates a new TickIndex without bounds checking - /// Use this function with caution, only when you're certain the value is valid - pub fn new_unchecked(value: i32) -> Self { - Self(value) - } - - /// Get the inner value - pub fn get(&self) -> i32 { - self.0 - } - - /// Creates a TickIndex from an offset representation (u32) - /// - /// # Arguments - /// * `offset_index` - An offset index (u32 value) representing a tick index - /// - /// # Returns - /// * `Result` - The corresponding TickIndex if within valid bounds - pub fn from_offset_index(offset_index: u32) -> Result { - // while it's safe, we use saturating math to mute the linter and just in case - let signed_index = ((offset_index as i64).saturating_sub(Self::OFFSET.get() as i64)) as i32; - Self::new(signed_index) - } - - /// Get the next tick index (incrementing by 1) - pub fn next(&self) -> Result { - Self::new(self.0.saturating_add(1)) - } - - /// Get the previous tick index (decrementing by 1) - pub fn prev(&self) -> Result { - Self::new(self.0.saturating_sub(1)) - } - - /// Add a value to this tick index with bounds checking - pub fn checked_add(&self, value: i32) -> Result { - Self::new(self.0.saturating_add(value)) - } - - /// Subtract a value from this tick index with bounds checking - pub fn checked_sub(&self, value: i32) -> Result { - Self::new(self.0.saturating_sub(value)) - } - - /// Add a value to this tick index, saturating at the bounds instead of overflowing - pub fn saturating_add(&self, value: i32) -> Self { - match self.checked_add(value) { - Ok(result) => result, - Err(_) => { - if value > 0 { - Self::MAX - } else { - Self::MIN - } - } - } - } - - /// Subtract a value from this tick index, saturating at the bounds instead of overflowing - pub fn saturating_sub(&self, value: i32) -> Self { - match self.checked_sub(value) { - Ok(result) => result, - Err(_) => { - if value > 0 { - Self::MIN - } else { - Self::MAX - } - } - } - } - - /// Divide the tick index by a value with bounds checking - #[allow(clippy::arithmetic_side_effects)] - pub fn checked_div(&self, value: i32) -> Result { - if value == 0 { - return Err(TickMathError::DivisionByZero); - } - Self::new(self.0.saturating_div(value)) - } - - /// Divide the tick index by a value, saturating at the bounds - pub fn saturating_div(&self, value: i32) -> Self { - if value == 0 { - return Self::MAX; // Return MAX for division by zero - } - match self.checked_div(value) { - Ok(result) => result, - Err(_) => { - if (self.0 < 0 && value > 0) || (self.0 > 0 && value < 0) { - Self::MIN - } else { - Self::MAX - } - } - } - } - - /// Multiply the tick index by a value with bounds checking - pub fn checked_mul(&self, value: i32) -> Result { - // Check for potential overflow - match self.0.checked_mul(value) { - Some(result) => Self::new(result), - None => Err(TickMathError::Overflow), - } - } - - /// Multiply the tick index by a value, saturating at the bounds - pub fn saturating_mul(&self, value: i32) -> Self { - match self.checked_mul(value) { - Ok(result) => result, - Err(_) => { - if (self.0 < 0 && value > 0) || (self.0 > 0 && value < 0) { - Self::MIN - } else { - Self::MAX - } - } - } - } - - /// Converts tick index into SQRT of lower price of this tick In order to find the higher price - /// of this tick, call tick_index_to_sqrt_price(tick_idx + 1) - pub fn try_to_sqrt_price(&self) -> Result { - // because of u256->u128 conversion we have twice less values for min/max ticks - if !(Self::MIN..=Self::MAX).contains(self) { - return Err(TickMathError::TickOutOfBounds); - } - get_sqrt_ratio_at_tick(self.0).and_then(u256_q64_96_to_u64f64) - } - - /// Converts SQRT price to tick index - /// Because the tick is the range of prices [sqrt_lower_price, sqrt_higher_price), the resulting - /// tick index matches the price by the following inequality: - /// sqrt_lower_price <= sqrt_price < sqrt_higher_price - pub fn try_from_sqrt_price(sqrt_price: SqrtPrice) -> Result { - // price in the native Q64.96 integer format - let price_x96 = u64f64_to_u256_q64_96(sqrt_price); - - // first‑pass estimate from the log calculation - let mut tick = get_tick_at_sqrt_ratio(price_x96)?; - - // post‑verification, *both* directions - let price_at_tick = get_sqrt_ratio_at_tick(tick)?; - if price_at_tick > price_x96 { - tick = tick.saturating_sub(1); // estimate was too high - } else { - // it may still be one too low - let price_at_tick_plus = get_sqrt_ratio_at_tick(tick.saturating_add(1))?; - if price_at_tick_plus <= price_x96 { - tick = tick.saturating_add(1); // step up when required - } - } - - tick.into_tick_index() - } -} - -pub struct ActiveTickIndexManager(PhantomData); - -impl ActiveTickIndexManager { - pub fn insert(netuid: NetUid, index: TickIndex) { - // Check the range - if (index < TickIndex::MIN) || (index > TickIndex::MAX) { - return; - } - - // Convert to bitmap representation - let bitmap = TickIndexBitmap::from(index); - - // Update layer words - let mut word0_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Top, - bitmap.word_at(LayerLevel::Top), - )); - let mut word1_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Middle, - bitmap.word_at(LayerLevel::Middle), - )); - let mut word2_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Bottom, - bitmap.word_at(LayerLevel::Bottom), - )); - - // Set bits in each layer - word0_value |= bitmap.bit_mask(LayerLevel::Top); - word1_value |= bitmap.bit_mask(LayerLevel::Middle); - word2_value |= bitmap.bit_mask(LayerLevel::Bottom); - - // Update the storage - TickIndexBitmapWords::::set( - (netuid, LayerLevel::Top, bitmap.word_at(LayerLevel::Top)), - word0_value, - ); - TickIndexBitmapWords::::set( - ( - netuid, - LayerLevel::Middle, - bitmap.word_at(LayerLevel::Middle), - ), - word1_value, - ); - TickIndexBitmapWords::::set( - ( - netuid, - LayerLevel::Bottom, - bitmap.word_at(LayerLevel::Bottom), - ), - word2_value, - ); - } - - pub fn remove(netuid: NetUid, index: TickIndex) { - // Check the range - if (index < TickIndex::MIN) || (index > TickIndex::MAX) { - return; - } - - // Convert to bitmap representation - let bitmap = TickIndexBitmap::from(index); - - // Update layer words - let mut word0_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Top, - bitmap.word_at(LayerLevel::Top), - )); - let mut word1_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Middle, - bitmap.word_at(LayerLevel::Middle), - )); - let mut word2_value = TickIndexBitmapWords::::get(( - netuid, - LayerLevel::Bottom, - bitmap.word_at(LayerLevel::Bottom), - )); - - // Turn the bit off (& !bit) and save as needed - word2_value &= !bitmap.bit_mask(LayerLevel::Bottom); - TickIndexBitmapWords::::set( - ( - netuid, - LayerLevel::Bottom, - bitmap.word_at(LayerLevel::Bottom), - ), - word2_value, - ); - - if word2_value == 0 { - word1_value &= !bitmap.bit_mask(LayerLevel::Middle); - TickIndexBitmapWords::::set( - ( - netuid, - LayerLevel::Middle, - bitmap.word_at(LayerLevel::Middle), - ), - word1_value, - ); - } - - if word1_value == 0 { - word0_value &= !bitmap.bit_mask(LayerLevel::Top); - TickIndexBitmapWords::::set( - (netuid, LayerLevel::Top, bitmap.word_at(LayerLevel::Top)), - word0_value, - ); - } - } - - pub fn find_closest_lower(netuid: NetUid, index: TickIndex) -> Option { - Self::find_closest(netuid, index, true) - } - - pub fn find_closest_higher(netuid: NetUid, index: TickIndex) -> Option { - Self::find_closest(netuid, index, false) - } - - fn find_closest(netuid: NetUid, index: TickIndex, lower: bool) -> Option { - // Check the range - if (index < TickIndex::MIN) || (index > TickIndex::MAX) { - return None; - } - - // Convert to bitmap representation - let bitmap = TickIndexBitmap::from(index); - let mut found = false; - let mut result: u32 = 0; - - // Layer positions from bitmap - let layer0_word = bitmap.word_at(LayerLevel::Top); - let layer0_bit = bitmap.bit_at(LayerLevel::Top); - let layer1_word = bitmap.word_at(LayerLevel::Middle); - let layer1_bit = bitmap.bit_at(LayerLevel::Middle); - let layer2_word = bitmap.word_at(LayerLevel::Bottom); - let layer2_bit = bitmap.bit_at(LayerLevel::Bottom); - - // Find the closest active bits in layer 0, then 1, then 2 - - /////////////// - // Level 0 - let word0 = TickIndexBitmapWords::::get((netuid, LayerLevel::Top, layer0_word)); - let closest_bits_l0 = - TickIndexBitmap::find_closest_active_bit_candidates(word0, layer0_bit, lower); - - for closest_bit_l0 in closest_bits_l0.iter() { - /////////////// - // Level 1 - let word1_index = TickIndexBitmap::layer_to_index(BitmapLayer::new(0, *closest_bit_l0)); - - // Layer 1 words are different, shift the bit to the word edge - let start_from_l1_bit = match word1_index.cmp(&layer1_word) { - Ordering::Less => 127, - Ordering::Greater => 0, - _ => layer1_bit, - }; - let word1_value = - TickIndexBitmapWords::::get((netuid, LayerLevel::Middle, word1_index)); - let closest_bits_l1 = TickIndexBitmap::find_closest_active_bit_candidates( - word1_value, - start_from_l1_bit, - lower, - ); - - for closest_bit_l1 in closest_bits_l1.iter() { - /////////////// - // Level 2 - let word2_index = - TickIndexBitmap::layer_to_index(BitmapLayer::new(word1_index, *closest_bit_l1)); - - // Layer 2 words are different, shift the bit to the word edge - let start_from_l2_bit = match word2_index.cmp(&layer2_word) { - Ordering::Less => 127, - Ordering::Greater => 0, - _ => layer2_bit, - }; - - let word2_value = - TickIndexBitmapWords::::get((netuid, LayerLevel::Bottom, word2_index)); - - let closest_bits_l2 = TickIndexBitmap::find_closest_active_bit_candidates( - word2_value, - start_from_l2_bit, - lower, - ); - - if !closest_bits_l2.is_empty() { - // The active tick is found, restore its full index and return - let offset_found_index = TickIndexBitmap::layer_to_index(BitmapLayer::new( - word2_index, - // it's safe to unwrap, because the len is > 0, but to prevent errors in - // refactoring, we use default fallback here for extra safety - closest_bits_l2.first().copied().unwrap_or_default(), - )); - - if lower { - if (offset_found_index > result) || (!found) { - result = offset_found_index; - found = true; - } - } else if (offset_found_index < result) || (!found) { - result = offset_found_index; - found = true; - } - } - } - } - - if !found { - return None; - } - - // Convert the result offset_index back to a tick index - TickIndex::from_offset_index(result).ok() - } - - pub fn tick_is_active(netuid: NetUid, tick: TickIndex) -> bool { - Self::find_closest_lower(netuid, tick).unwrap_or(TickIndex::MAX) == tick - } -} - -/// Represents the three layers in the Uniswap V3 bitmap structure -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub enum LayerLevel { - /// Top layer (highest level of the hierarchy) - Top = 0, - /// Middle layer - Middle = 1, - /// Bottom layer (contains the actual ticks) - Bottom = 2, -} - -#[freeze_struct("4015a04919eb5e2e")] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub(crate) struct BitmapLayer { - word: u32, - bit: u32, -} - -impl BitmapLayer { - pub fn new(word: u32, bit: u32) -> Self { - Self { word, bit } - } -} - -/// A bitmap representation of a tick index position across the three-layer structure -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct TickIndexBitmap { - /// The position in layer 0 (top layer) - layer0: BitmapLayer, - /// The position in layer 1 (middle layer) - layer1: BitmapLayer, - /// The position in layer 2 (bottom layer) - layer2: BitmapLayer, -} - -impl TickIndexBitmap { - /// Helper function to convert a bitmap index to a (word, bit) tuple in a bitmap layer using - /// safe methods - /// - /// Note: This function operates on bitmap navigation indices, NOT tick indices. - /// It converts a flat index within the bitmap structure to a (word, bit) position. - fn index_to_layer(index: u32) -> BitmapLayer { - let word = index.safe_div(128); - let bit = index.checked_rem(128).unwrap_or_default(); - BitmapLayer { word, bit } - } - - /// Converts a position (word, bit) within a layer to a word index in the next layer down - /// Note: This returns a bitmap navigation index, NOT a tick index - pub(crate) fn layer_to_index(layer: BitmapLayer) -> u32 { - layer.word.saturating_mul(128).saturating_add(layer.bit) - } - - /// Get the mask for a bit in the specified layer - pub(crate) fn bit_mask(&self, layer: LayerLevel) -> u128 { - match layer { - LayerLevel::Top => 1u128 << self.layer0.bit, - LayerLevel::Middle => 1u128 << self.layer1.bit, - LayerLevel::Bottom => 1u128 << self.layer2.bit, - } - } - - /// Get the word for the specified layer - pub(crate) fn word_at(&self, layer: LayerLevel) -> u32 { - match layer { - LayerLevel::Top => self.layer0.word, - LayerLevel::Middle => self.layer1.word, - LayerLevel::Bottom => self.layer2.word, - } - } - - /// Get the bit for the specified layer - pub(crate) fn bit_at(&self, layer: LayerLevel) -> u32 { - match layer { - LayerLevel::Top => self.layer0.bit, - LayerLevel::Middle => self.layer1.bit, - LayerLevel::Bottom => self.layer2.bit, - } - } - - /// Finds the closest active bit in a bitmap word, and if the active bit exactly matches the - /// requested bit, then it finds the next one as well - /// - /// # Arguments - /// * `word` - The bitmap word to search within - /// * `bit` - The bit position to start searching from - /// * `lower` - If true, search for lower bits (decreasing bit position), if false, search for - /// higher bits (increasing bit position) - /// - /// # Returns - /// * Exact match: Vec with [next_bit, bit] - /// * Non-exact match: Vec with [closest_bit] - /// * No match: Empty Vec - pub(crate) fn find_closest_active_bit_candidates( - word: u128, - bit: u32, - lower: bool, - ) -> Vec { - let mut result = vec![]; - let mut mask: u128 = 1_u128.wrapping_shl(bit); - let mut active_bit: u32 = bit; - - while mask > 0 { - if mask & word != 0 { - result.push(active_bit); - if active_bit != bit { - break; - } - } - - mask = if lower { - active_bit = active_bit.saturating_sub(1); - mask.wrapping_shr(1) - } else { - active_bit = active_bit.saturating_add(1); - mask.wrapping_shl(1) - }; - } - - result - } -} - -impl From for TickIndexBitmap { - fn from(tick_index: TickIndex) -> Self { - // Convert to offset index (internal operation only) - let offset_index = (tick_index.get().saturating_add(TickIndex::OFFSET.get())) as u32; - - // Calculate layer positions - let layer2 = Self::index_to_layer(offset_index); - let layer1 = Self::index_to_layer(layer2.word); - let layer0 = Self::index_to_layer(layer1.word); - - Self { - layer0, - layer1, - layer2, - } - } -} - -#[allow(clippy::arithmetic_side_effects)] -fn get_sqrt_ratio_at_tick(tick: i32) -> Result { - let abs_tick = if tick < 0 { - U256::from(tick.neg()) - } else { - U256::from(tick) - }; - - if abs_tick > U256_MAX_TICK { - return Err(TickMathError::TickOutOfBounds); - } - - let mut ratio = if abs_tick & (U256_1) != U256::ZERO { - U256::from_limbs([12262481743371124737, 18445821805675392311, 0, 0]) - } else { - U256::from_limbs([0, 0, 1, 0]) - }; - - if !(abs_tick & U256_2).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 6459403834229662010, - 18444899583751176498, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_4).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 17226890335427755468, - 18443055278223354162, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_8).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 2032852871939366096, - 18439367220385604838, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_16).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 14545316742740207172, - 18431993317065449817, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_32).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 5129152022828963008, - 18417254355718160513, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_64).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 4894419605888772193, - 18387811781193591352, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_128).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 1280255884321894483, - 18329067761203520168, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_256).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 15924666964335305636, - 18212142134806087854, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_512).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 8010504389359918676, - 17980523815641551639, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_1024).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 10668036004952895731, - 17526086738831147013, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_2048).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 4878133418470705625, - 16651378430235024244, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_4096).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 9537173718739605541, - 15030750278693429944, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_8192).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 9972618978014552549, - 12247334978882834399, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_16384).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 10428997489610666743, - 8131365268884726200, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_32768).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 9305304367709015974, - 3584323654723342297, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_65536).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 14301143598189091785, - 696457651847595233, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_131072).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 7393154844743099908, - 26294789957452057, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_262144).is_zero() { - ratio = (ratio.saturating_mul(U256::from_limbs([ - 2209338891292245656, - 37481735321082, - 0, - 0, - ]))) >> 128 - } - if !(abs_tick & U256_524288).is_zero() { - ratio = - (ratio.saturating_mul(U256::from_limbs([10518117631919034274, 76158723, 0, 0]))) >> 128 - } - - if tick > 0 { - ratio = U256::MAX / ratio; - } - - let shifted: U256 = ratio >> 32; - let ceil = if ratio & U256::from((1u128 << 32) - 1) != U256::ZERO { - shifted.saturating_add(U256_1) - } else { - shifted - }; - Ok(ceil) -} - -#[allow(clippy::arithmetic_side_effects)] -fn get_tick_at_sqrt_ratio(sqrt_price_x_96: U256) -> Result { - if !(sqrt_price_x_96 >= MIN_SQRT_RATIO && sqrt_price_x_96 < MAX_SQRT_RATIO) { - return Err(TickMathError::SqrtPriceOutOfBounds); - } - - let ratio: U256 = sqrt_price_x_96.shl(32); - let mut r = ratio; - let mut msb = U256::ZERO; - - let mut f = if r > U256::from_limbs([18446744073709551615, 18446744073709551615, 0, 0]) { - U256_1.shl(U256_7) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256::from_limbs([18446744073709551615, 0, 0, 0]) { - U256_1.shl(U256_6) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256::from_limbs([4294967295, 0, 0, 0]) { - U256_1.shl(U256_5) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256::from_limbs([65535, 0, 0, 0]) { - U256_1.shl(U256_4) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256_255 { - U256_1.shl(U256_3) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256_15 { - U256_1.shl(U256_2) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256_3 { - U256_1.shl(U256_1) - } else { - U256::ZERO - }; - msb = msb.bitor(f); - r = r.shr(f); - - f = if r > U256_1 { U256_1 } else { U256::ZERO }; - - msb = msb.bitor(f); - - r = if msb >= U256_128 { - ratio.shr(msb.saturating_sub(U256_127)) - } else { - ratio.shl(U256_127.saturating_sub(msb)) - }; - - let mut log_2: I256 = - (I256::from_raw(msb).saturating_sub(I256::from_limbs([128, 0, 0, 0]))).shl(64); - - for i in (51..=63).rev() { - r = r.overflowing_mul(r).0.shr(U256_127); - let f: U256 = r.shr(128); - log_2 = log_2.bitor(I256::from_raw(f.shl(i))); - - r = r.shr(f); - } - - r = r.overflowing_mul(r).0.shr(U256_127); - let f: U256 = r.shr(128); - log_2 = log_2.bitor(I256::from_raw(f.shl(50))); - - let log_sqrt10001 = log_2.wrapping_mul(SQRT_10001); - - let tick_low = (log_sqrt10001.saturating_sub(TICK_LOW) >> 128_u8).low_i32(); - - let tick_high = (log_sqrt10001.saturating_add(TICK_HIGH) >> 128_u8).low_i32(); - - let tick = if tick_low == tick_high { - tick_low - } else if get_sqrt_ratio_at_tick(tick_high)? <= sqrt_price_x_96 { - tick_high - } else { - tick_low - }; - - Ok(tick) -} - -// Convert U64F64 to U256 in Q64.96 format (Uniswap's sqrt price format) -fn u64f64_to_u256_q64_96(value: U64F64) -> U256 { - u64f64_to_u256(value, 96) -} - -/// Convert U64F64 to U256 -/// -/// # Arguments -/// * `value` - The U64F64 value to convert -/// * `target_fractional_bits` - Number of fractional bits in the target U256 format -/// -/// # Returns -/// * `U256` - Converted value -#[allow(clippy::arithmetic_side_effects)] -fn u64f64_to_u256(value: U64F64, target_fractional_bits: u32) -> U256 { - let raw = U256::from(value.to_bits()); - - match target_fractional_bits.cmp(&64) { - Ordering::Less => raw >> (64 - target_fractional_bits), - Ordering::Greater => raw.saturating_shl((target_fractional_bits - 64) as usize), - Ordering::Equal => raw, - } -} - -/// Convert U256 in Q64.96 format (Uniswap's sqrt price format) to U64F64 -fn u256_q64_96_to_u64f64(value: U256) -> Result { - q_to_u64f64(value, 96) -} - -#[allow(clippy::arithmetic_side_effects)] -fn q_to_u64f64(x: U256, frac_bits: u32) -> Result { - let diff = frac_bits.saturating_sub(64) as usize; - - // 1. shift right diff bits - let shifted = if diff != 0 { x >> diff } else { x }; - - // 2. **round up** if we threw away any 1‑bits - let mask = if diff != 0 { - (U256_1.saturating_shl(diff)).saturating_sub(U256_1) - } else { - U256::ZERO - }; - let rounded = if diff != 0 && (x & mask) != U256::ZERO { - shifted.saturating_add(U256_1) - } else { - shifted - }; - - // 3. check that it fits in 128 bits and transmute - if (rounded >> 128) != U256::ZERO { - return Err(TickMathError::Overflow); - } - Ok(U64F64::from_bits(rounded.to::())) -} - -#[derive(Debug, PartialEq, Eq)] -pub enum TickMathError { - TickOutOfBounds, - SqrtPriceOutOfBounds, - ConversionError, - Overflow, - DivisionByZero, -} - -impl fmt::Display for TickMathError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::TickOutOfBounds => f.write_str("The given tick is outside of the minimum/maximum values."), - Self::SqrtPriceOutOfBounds =>f.write_str("Second inequality must be < because the price can never reach the price at the max tick"), - Self::ConversionError => f.write_str("Error converting from one number type into another"), - Self::Overflow => f.write_str("Number overflow in arithmetic operation"), - Self::DivisionByZero => f.write_str("Division by zero is not allowed") - } - } -} - -impl Error for TickMathError {} - -#[allow(clippy::unwrap_used)] -#[cfg(test)] -mod tests { - use safe_math::FixedExt; - use std::{ops::Sub, str::FromStr}; - - use super::*; - use crate::mock::*; - - #[test] - fn test_get_sqrt_ratio_at_tick_bounds() { - // the function should return an error if the tick is out of bounds - if let Err(err) = get_sqrt_ratio_at_tick(MIN_TICK - 1) { - assert!(matches!(err, TickMathError::TickOutOfBounds)); - } else { - panic!("get_qrt_ratio_at_tick did not respect lower tick bound") - } - if let Err(err) = get_sqrt_ratio_at_tick(MAX_TICK + 1) { - assert!(matches!(err, TickMathError::TickOutOfBounds)); - } else { - panic!("get_qrt_ratio_at_tick did not respect upper tick bound") - } - } - - #[test] - fn test_get_sqrt_ratio_at_tick_values() { - // test individual values for correct results - assert_eq!( - get_sqrt_ratio_at_tick(MIN_TICK).unwrap(), - U256::from(4295128739u64), - "sqrt ratio at min incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(MIN_TICK + 1).unwrap(), - U256::from(4295343490u64), - "sqrt ratio at min + 1 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(MAX_TICK - 1).unwrap(), - U256::from_str("1461373636630004318706518188784493106690254656249").unwrap(), - "sqrt ratio at max - 1 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(MAX_TICK).unwrap(), - U256::from_str("1461446703485210103287273052203988822378723970342").unwrap(), - "sqrt ratio at max incorrect" - ); - // checking hard coded values against solidity results - assert_eq!( - get_sqrt_ratio_at_tick(50).unwrap(), - U256::from(79426470787362580746886972461u128), - "sqrt ratio at 50 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(100).unwrap(), - U256::from(79625275426524748796330556128u128), - "sqrt ratio at 100 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(250).unwrap(), - U256::from(80224679980005306637834519095u128), - "sqrt ratio at 250 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(500).unwrap(), - U256::from(81233731461783161732293370115u128), - "sqrt ratio at 500 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(1000).unwrap(), - U256::from(83290069058676223003182343270u128), - "sqrt ratio at 1000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(2500).unwrap(), - U256::from(89776708723587163891445672585u128), - "sqrt ratio at 2500 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(3000).unwrap(), - U256::from(92049301871182272007977902845u128), - "sqrt ratio at 3000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(4000).unwrap(), - U256::from(96768528593268422080558758223u128), - "sqrt ratio at 4000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(5000).unwrap(), - U256::from(101729702841318637793976746270u128), - "sqrt ratio at 5000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(50000).unwrap(), - U256::from(965075977353221155028623082916u128), - "sqrt ratio at 50000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(150000).unwrap(), - U256::from(143194173941309278083010301478497u128), - "sqrt ratio at 150000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(250000).unwrap(), - U256::from(21246587762933397357449903968194344u128), - "sqrt ratio at 250000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(500000).unwrap(), - U256::from_str("5697689776495288729098254600827762987878").unwrap(), - "sqrt ratio at 500000 incorrect" - ); - assert_eq!( - get_sqrt_ratio_at_tick(738203).unwrap(), - U256::from_str("847134979253254120489401328389043031315994541").unwrap(), - "sqrt ratio at 738203 incorrect" - ); - } - - #[test] - fn test_get_tick_at_sqrt_ratio() { - //throws for too low - let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO.sub(U256_1)); - assert_eq!( - result.unwrap_err().to_string(), - "Second inequality must be < because the price can never reach the price at the max tick" - ); - - //throws for too high - let result = get_tick_at_sqrt_ratio(MAX_SQRT_RATIO); - assert_eq!( - result.unwrap_err().to_string(), - "Second inequality must be < because the price can never reach the price at the max tick" - ); - - //ratio of min tick - let result = get_tick_at_sqrt_ratio(MIN_SQRT_RATIO).unwrap(); - assert_eq!(result, MIN_TICK); - - //ratio of min tick + 1 - let result = get_tick_at_sqrt_ratio(U256::from_str("4295343490").unwrap()).unwrap(); - assert_eq!(result, MIN_TICK + 1); - } - - #[test] - fn test_roundtrip() { - for tick_index in [ - MIN_TICK + 1, // we can't use extremes because of rounding during roundtrip conversion - -1000, - -100, - -10, - -4, - -2, - 0, - 2, - 4, - 10, - 100, - 1000, - MAX_TICK - 1, - ] - .iter() - { - let sqrt_price = get_sqrt_ratio_at_tick(*tick_index).unwrap(); - let round_trip_tick_index = get_tick_at_sqrt_ratio(sqrt_price).unwrap(); - assert_eq!(round_trip_tick_index, *tick_index); - } - } - - #[test] - fn test_u256_to_u64f64_q64_96() { - // Test tick 0 (sqrt price = 1.0 * 2^96) - let tick0_sqrt_price = U256::from(1u128 << 96); - let fixed_price = u256_q64_96_to_u64f64(tick0_sqrt_price).unwrap(); - - // Should be 1.0 in U64F64 - assert_eq!(fixed_price, U64F64::from_num(1.0)); - - // Round trip back to U256 Q64.96 - let back_to_u256 = u64f64_to_u256_q64_96(fixed_price); - assert_eq!(back_to_u256, tick0_sqrt_price); - } - - #[test] - fn test_tick_index_to_sqrt_price() { - let tick_spacing = SqrtPrice::from_num(1.0001); - - // check tick bounds - assert_eq!( - TickIndex(MIN_TICK).try_to_sqrt_price(), - Err(TickMathError::TickOutOfBounds) - ); - - assert_eq!( - TickIndex(MAX_TICK).try_to_sqrt_price(), - Err(TickMathError::TickOutOfBounds), - ); - - assert!( - TickIndex::MAX.try_to_sqrt_price().unwrap().abs_diff( - TickIndex::new_unchecked(TickIndex::MAX.get() + 1).as_sqrt_price_bounded() - ) < SqrtPrice::from_num(1e-6) - ); - - assert!( - TickIndex::MIN.try_to_sqrt_price().unwrap().abs_diff( - TickIndex::new_unchecked(TickIndex::MIN.get() - 1).as_sqrt_price_bounded() - ) < SqrtPrice::from_num(1e-6) - ); - - // At tick index 0, the sqrt price should be 1.0 - let sqrt_price = TickIndex(0).try_to_sqrt_price().unwrap(); - assert_eq!(sqrt_price, SqrtPrice::from_num(1.0)); - - let sqrt_price = TickIndex(2).try_to_sqrt_price().unwrap(); - assert!(sqrt_price.abs_diff(tick_spacing) < SqrtPrice::from_num(1e-10)); - - let sqrt_price = TickIndex(4).try_to_sqrt_price().unwrap(); - // Calculate the expected value: (1 + TICK_SPACING/1e9 + 1.0)^2 - let expected = tick_spacing * tick_spacing; - assert!(sqrt_price.abs_diff(expected) < SqrtPrice::from_num(1e-10)); - - // Test with tick index 10 - let sqrt_price = TickIndex(10).try_to_sqrt_price().unwrap(); - // Calculate the expected value: (1 + TICK_SPACING/1e9 + 1.0)^5 - let expected = tick_spacing.checked_pow(5).unwrap(); - assert!( - sqrt_price.abs_diff(expected) < SqrtPrice::from_num(1e-10), - "diff: {}", - sqrt_price.abs_diff(expected), - ); - } - - #[test] - fn test_sqrt_price_to_tick_index() { - let tick_spacing = SqrtPrice::from_num(1.0001); - let tick_index = TickIndex::try_from_sqrt_price(SqrtPrice::from_num(1.0)).unwrap(); - assert_eq!(tick_index, TickIndex::new_unchecked(0)); - - // Test with sqrt price equal to tick_spacing_tao (should be tick index 2) - let epsilon = SqrtPrice::from_num(0.0000000000000001); - assert!( - TickIndex::new_unchecked(2) - .as_sqrt_price_bounded() - .abs_diff(tick_spacing) - < epsilon - ); - - // Test with sqrt price equal to tick_spacing_tao^2 (should be tick index 4) - let sqrt_price = tick_spacing * tick_spacing; - assert!( - TickIndex::new_unchecked(4) - .as_sqrt_price_bounded() - .abs_diff(sqrt_price) - < epsilon - ); - - // Test with sqrt price equal to tick_spacing_tao^5 (should be tick index 10) - let sqrt_price = tick_spacing.checked_pow(5).unwrap(); - assert!( - TickIndex::new_unchecked(10) - .as_sqrt_price_bounded() - .abs_diff(sqrt_price) - < epsilon - ); - } - - #[test] - fn test_roundtrip_tick_index_sqrt_price() { - for i32_value in [ - TickIndex::MIN.get(), - -1000, - -100, - -10, - -4, - -2, - 0, - 2, - 4, - 10, - 100, - 1000, - TickIndex::MAX.get(), - ] - .into_iter() - { - let tick_index = TickIndex::new_unchecked(i32_value); - let sqrt_price = tick_index.try_to_sqrt_price().unwrap(); - let round_trip_tick_index = TickIndex::try_from_sqrt_price(sqrt_price).unwrap(); - assert_eq!(round_trip_tick_index, tick_index); - } - } - - #[test] - fn test_from_offset_index() { - // Test various tick indices - for i32_value in [ - TickIndex::MIN.get(), - -1000, - -100, - -10, - 0, - 10, - 100, - 1000, - TickIndex::MAX.get(), - ] { - let original_tick = TickIndex::new_unchecked(i32_value); - - // Calculate the offset index (adding OFFSET) - let offset_index = (i32_value + TickIndex::OFFSET.get()) as u32; - - // Convert back from offset index to tick index - let roundtrip_tick = TickIndex::from_offset_index(offset_index).unwrap(); - - // Check that we get the same tick index back - assert_eq!(original_tick, roundtrip_tick); - } - - // Test out of bounds values - let too_large = (TickIndex::MAX.get() + TickIndex::OFFSET.get() + 1) as u32; - assert!(TickIndex::from_offset_index(too_large).is_err()); - } - - #[test] - fn test_tick_price_sanity_check() { - let min_price = TickIndex::MIN.try_to_sqrt_price().unwrap(); - let max_price = TickIndex::MAX.try_to_sqrt_price().unwrap(); - - assert!(min_price > 0.); - assert!(max_price > 0.); - assert!(max_price > min_price); - assert!(min_price < 0.000001); - assert!(max_price > 10.); - - // Roundtrip conversions - let min_price_sqrt = TickIndex::MIN.try_to_sqrt_price().unwrap(); - let min_tick = TickIndex::try_from_sqrt_price(min_price_sqrt).unwrap(); - assert_eq!(min_tick, TickIndex::MIN); - - let max_price_sqrt: SqrtPrice = TickIndex::MAX.try_to_sqrt_price().unwrap(); - let max_tick = TickIndex::try_from_sqrt_price(max_price_sqrt).unwrap(); - assert_eq!(max_tick, TickIndex::MAX); - } - - #[test] - fn test_to_sqrt_price_bounded() { - assert_eq!( - TickIndex::MAX.as_sqrt_price_bounded(), - TickIndex::MAX.try_to_sqrt_price().unwrap() - ); - - assert_eq!( - TickIndex::MIN.as_sqrt_price_bounded(), - TickIndex::MIN.try_to_sqrt_price().unwrap() - ); - } - - mod active_tick_index_manager { - - use super::*; - - #[test] - fn test_tick_search_basic() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - - ActiveTickIndexManager::::insert(netuid, TickIndex::MIN); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MIN) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MAX) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.saturating_div(2) - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.prev().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.next().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, TickIndex::MIN) - .unwrap(), - TickIndex::MIN - ); - assert!( - ActiveTickIndexManager::::find_closest_higher(netuid, TickIndex::MAX) - .is_none() - ); - assert!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MAX.saturating_div(2) - ) - .is_none() - ); - assert!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MAX.prev().unwrap() - ) - .is_none() - ); - assert!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MIN.next().unwrap() - ) - .is_none() - ); - - ActiveTickIndexManager::::insert(netuid, TickIndex::MAX); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MIN) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MAX) - .unwrap(), - TickIndex::MAX - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.saturating_div(2) - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.prev().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.next().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - }); - } - - #[test] - fn test_tick_search_sparse_queries() { - new_test_ext().execute_with(|| { - let active_index = TickIndex::MIN.saturating_add(10); - let netuid = NetUid::from(1); - - ActiveTickIndexManager::::insert(netuid, active_index); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, active_index) - .unwrap(), - active_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.saturating_add(11) - ) - .unwrap(), - active_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.saturating_add(12) - ) - .unwrap(), - active_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MIN), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.saturating_add(9) - ), - None - ); - - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, active_index) - .unwrap(), - active_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MIN.saturating_add(11) - ), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MIN.saturating_add(12) - ), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, TickIndex::MIN) - .unwrap(), - active_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MIN.saturating_add(9) - ) - .unwrap(), - active_index - ); - }); - } - - #[test] - fn test_tick_search_many_lows() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - - (0..1000).for_each(|i| { - ActiveTickIndexManager::::insert( - netuid, - TickIndex::MIN.saturating_add(i), - ); - }); - - for i in 0..1000 { - let test_index = TickIndex::MIN.saturating_add(i); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, test_index) - .unwrap(), - test_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, test_index) - .unwrap(), - test_index - ); - } - }); - } - - #[test] - fn test_tick_search_many_sparse() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let count = 1000; - - for i in 0..=count { - ActiveTickIndexManager::::insert( - netuid, - TickIndex::new_unchecked(i * 10), - ); - } - - for i in 1..count { - let tick = TickIndex::new_unchecked(i * 10); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, tick).unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, tick).unwrap(), - tick - ); - for j in 1..=9 { - let before_tick = TickIndex::new_unchecked(i * 10 - j); - let after_tick = TickIndex::new_unchecked(i * 10 + j); - let prev_tick = TickIndex::new_unchecked((i - 1) * 10); - let next_tick = TickIndex::new_unchecked((i + 1) * 10); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, before_tick) - .unwrap(), - prev_tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, after_tick) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - before_tick - ) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, after_tick) - .unwrap(), - next_tick - ); - } - } - }); - } - - #[test] - fn test_tick_search_many_lows_sparse_reversed() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let count = 1000; - - for i in (0..=count).rev() { - ActiveTickIndexManager::::insert( - netuid, - TickIndex::new_unchecked(i * 10), - ); - } - - for i in 1..count { - let tick = TickIndex::new_unchecked(i * 10); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, tick).unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, tick).unwrap(), - tick - ); - for j in 1..=9 { - let before_tick = TickIndex::new_unchecked(i * 10 - j); - let after_tick = TickIndex::new_unchecked(i * 10 + j); - let prev_tick = TickIndex::new_unchecked((i - 1) * 10); - let next_tick = TickIndex::new_unchecked((i + 1) * 10); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, before_tick) - .unwrap(), - prev_tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, after_tick) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - before_tick - ) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, after_tick) - .unwrap(), - next_tick - ); - } - } - }); - } - - #[test] - fn test_tick_search_repeated_insertions() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let count = 1000; - - for _ in 0..10 { - for i in 0..=count { - let tick = TickIndex::new_unchecked(i * 10); - ActiveTickIndexManager::::insert(netuid, tick); - } - - for i in 1..count { - let tick = TickIndex::new_unchecked(i * 10); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, tick) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, tick) - .unwrap(), - tick - ); - for j in 1..=9 { - let before_tick = TickIndex::new_unchecked(i * 10 - j); - let after_tick = TickIndex::new_unchecked(i * 10 + j); - let prev_tick = TickIndex::new_unchecked((i - 1) * 10); - let next_tick = TickIndex::new_unchecked((i + 1) * 10); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - before_tick - ) - .unwrap(), - prev_tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, after_tick - ) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - before_tick - ) - .unwrap(), - tick - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, after_tick - ) - .unwrap(), - next_tick - ); - } - } - } - }); - } - - #[test] - fn test_tick_search_full_range() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let step = 1019; - // Get the full valid tick range by subtracting MIN from MAX - let count = (TickIndex::MAX.get() - TickIndex::MIN.get()) / step; - - for i in 0..=count { - let index = TickIndex::MIN.saturating_add(i * step); - ActiveTickIndexManager::::insert(netuid, index); - } - for i in 1..count { - let index = TickIndex::MIN.saturating_add(i * step); - - let prev_index = TickIndex::new_unchecked(index.get() - step); - let next_minus_one = TickIndex::new_unchecked(index.get() + step - 1); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, prev_index) - .unwrap(), - prev_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, index).unwrap(), - index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, next_minus_one) - .unwrap(), - index - ); - - let mid_next = TickIndex::new_unchecked(index.get() + step / 2); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, mid_next) - .unwrap(), - index - ); - - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, index).unwrap(), - index - ); - - let next_index = TickIndex::new_unchecked(index.get() + step); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, next_index) - .unwrap(), - next_index - ); - - let mid_next = TickIndex::new_unchecked(index.get() + step / 2); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, mid_next) - .unwrap(), - next_index - ); - - let next_minus_1 = TickIndex::new_unchecked(index.get() + step - 1); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, next_minus_1) - .unwrap(), - next_index - ); - for j in 1..=9 { - let before_index = TickIndex::new_unchecked(index.get() - j); - let after_index = TickIndex::new_unchecked(index.get() + j); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - before_index - ) - .unwrap(), - prev_index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, after_index) - .unwrap(), - index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - before_index - ) - .unwrap(), - index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - after_index - ) - .unwrap(), - next_index - ); - } - } - }); - } - - #[test] - fn test_tick_remove_basic() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - - ActiveTickIndexManager::::insert(netuid, TickIndex::MIN); - ActiveTickIndexManager::::insert(netuid, TickIndex::MAX); - ActiveTickIndexManager::::remove(netuid, TickIndex::MAX); - - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MIN) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, TickIndex::MAX) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.saturating_div(2) - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MAX.prev().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_lower( - netuid, - TickIndex::MIN.next().unwrap() - ) - .unwrap(), - TickIndex::MIN - ); - - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, TickIndex::MIN) - .unwrap(), - TickIndex::MIN - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, TickIndex::MAX), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MAX.saturating_div(2) - ), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MAX.prev().unwrap() - ), - None - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher( - netuid, - TickIndex::MIN.next().unwrap() - ), - None - ); - }); - } - - #[test] - fn test_tick_remove_full_range() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let step = 1019; - // Get the full valid tick range by subtracting MIN from MAX - let count = (TickIndex::MAX.get() - TickIndex::MIN.get()) / step; - let remove_frequency = 5; // Remove every 5th tick - - // Insert ticks - for i in 0..=count { - let index = TickIndex::MIN.saturating_add(i * step); - ActiveTickIndexManager::::insert(netuid, index); - } - - // Remove some ticks - for i in 1..count { - if i % remove_frequency == 0 { - let index = TickIndex::MIN.saturating_add(i * step); - ActiveTickIndexManager::::remove(netuid, index); - } - } - - // Verify - for i in 1..count { - let index = TickIndex::MIN.saturating_add(i * step); - - if i % remove_frequency == 0 { - let lower = - ActiveTickIndexManager::::find_closest_lower(netuid, index); - let higher = - ActiveTickIndexManager::::find_closest_higher(netuid, index); - assert!(lower != Some(index)); - assert!(higher != Some(index)); - } else { - assert_eq!( - ActiveTickIndexManager::::find_closest_lower(netuid, index) - .unwrap(), - index - ); - assert_eq!( - ActiveTickIndexManager::::find_closest_higher(netuid, index) - .unwrap(), - index - ); - } - } - }); - } - } -} diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index fc2a16a409..013903ea8e 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -30,7 +30,7 @@ use subtensor_swap_interface::SwapHandler; use core::marker::PhantomData; use smallvec::smallvec; use sp_std::vec::Vec; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{Balance, Currency, NetUid}; // Tests @@ -145,7 +145,7 @@ where // This is not ideal because it may not pay all fees, but UX is the priority // and this approach still provides spam protection. alpha_vec.iter().any(|(hotkey, netuid)| { - let alpha_balance = U96F32::saturating_from_num( + let alpha_balance = U64F64::saturating_from_num( pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( hotkey, coldkey, *netuid, ), @@ -168,13 +168,13 @@ where alpha_vec.iter().for_each(|(hotkey, netuid)| { // Divide tao_amount evenly among all alpha entries - let alpha_balance = U96F32::saturating_from_num( + let alpha_balance = U64F64::saturating_from_num( pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( hotkey, coldkey, *netuid, ), ); let alpha_price = pallet_subtensor_swap::Pallet::::current_alpha_price(*netuid); - let alpha_fee = U96F32::saturating_from_num(tao_per_entry) + let alpha_fee = U64F64::saturating_from_num(tao_per_entry) .checked_div(alpha_price) .unwrap_or(alpha_balance) .min(alpha_balance) diff --git a/pallets/transaction-fee/src/tests/mod.rs b/pallets/transaction-fee/src/tests/mod.rs index b6697e87f0..4c79c368cb 100644 --- a/pallets/transaction-fee/src/tests/mod.rs +++ b/pallets/transaction-fee/src/tests/mod.rs @@ -2,12 +2,11 @@ use crate::TransactionSource; use frame_support::assert_ok; use frame_support::dispatch::GetDispatchInfo; -use pallet_subtensor_swap::AlphaSqrtPrice; use sp_runtime::{ traits::{DispatchTransaction, TransactionExtension, TxBaseImplication}, transaction_validity::{InvalidTransaction, TransactionValidityError}, }; -use substrate_fixed::types::U64F64; +// use substrate_fixed::types::U64F64; use subtensor_runtime_common::AlphaCurrency; use mock::*; @@ -387,78 +386,80 @@ fn test_remove_stake_not_enough_balance_for_fees() { #[test] #[ignore] fn test_remove_stake_edge_alpha() { - new_test_ext().execute_with(|| { - let stake_amount = TAO; - let sn = setup_subnets(1, 1); - setup_stake( - sn.subnets[0].netuid, - &sn.coldkey, - &sn.hotkeys[0], - stake_amount, - ); - - // Simulate stake removal to get how much TAO should we get for unstaked Alpha - let current_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &sn.hotkeys[0], - &sn.coldkey, - sn.subnets[0].netuid, - ); - - // Forse-set signer balance to ED - let current_balance = Balances::free_balance(sn.coldkey); - let _ = SubtensorModule::remove_balance_from_coldkey_account( - &sn.coldkey, - current_balance - ExistentialDeposit::get(), - ); - - // For-set Alpha balance to low, but enough to pay tx fees at the current Alpha price - let new_current_stake = AlphaCurrency::from(1_000_000); - SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( - &sn.hotkeys[0], - &sn.coldkey, - sn.subnets[0].netuid, - current_stake - new_current_stake, - ); - - // Remove stake - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::remove_stake { - hotkey: sn.hotkeys[0], - netuid: sn.subnets[0].netuid, - amount_unstaked: new_current_stake, - }); - - // Dispatch the extrinsic with ChargeTransactionPayment extension - let info = call.get_dispatch_info(); - let ext = pallet_transaction_payment::ChargeTransactionPayment::::from(0); - let result = ext.validate( - RuntimeOrigin::signed(sn.coldkey).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - - // Ok - Validation passed - assert_ok!(result); - - // Lower Alpha price to 0.0001 so that there is not enough alpha to cover tx fees - AlphaSqrtPrice::::insert(sn.subnets[0].netuid, U64F64::from_num(0.01)); - let result_low_alpha_price = ext.validate( - RuntimeOrigin::signed(sn.coldkey).into(), - &call.clone(), - &info, - 10, - (), - &TxBaseImplication(()), - TransactionSource::External, - ); - assert_eq!( - result_low_alpha_price.unwrap_err(), - TransactionValidityError::Invalid(InvalidTransaction::Payment) - ); - }); + todo!(); + + // new_test_ext().execute_with(|| { + // let stake_amount = TAO; + // let sn = setup_subnets(1, 1); + // setup_stake( + // sn.subnets[0].netuid, + // &sn.coldkey, + // &sn.hotkeys[0], + // stake_amount, + // ); + + // // Simulate stake removal to get how much TAO should we get for unstaked Alpha + // let current_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + // &sn.hotkeys[0], + // &sn.coldkey, + // sn.subnets[0].netuid, + // ); + + // // Forse-set signer balance to ED + // let current_balance = Balances::free_balance(sn.coldkey); + // let _ = SubtensorModule::remove_balance_from_coldkey_account( + // &sn.coldkey, + // current_balance - ExistentialDeposit::get(), + // ); + + // // For-set Alpha balance to low, but enough to pay tx fees at the current Alpha price + // let new_current_stake = AlphaCurrency::from(1_000_000); + // SubtensorModule::decrease_stake_for_hotkey_and_coldkey_on_subnet( + // &sn.hotkeys[0], + // &sn.coldkey, + // sn.subnets[0].netuid, + // current_stake - new_current_stake, + // ); + + // // Remove stake + // let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::remove_stake { + // hotkey: sn.hotkeys[0], + // netuid: sn.subnets[0].netuid, + // amount_unstaked: new_current_stake, + // }); + + // // Dispatch the extrinsic with ChargeTransactionPayment extension + // let info = call.get_dispatch_info(); + // let ext = pallet_transaction_payment::ChargeTransactionPayment::::from(0); + // let result = ext.validate( + // RuntimeOrigin::signed(sn.coldkey).into(), + // &call.clone(), + // &info, + // 10, + // (), + // &TxBaseImplication(()), + // TransactionSource::External, + // ); + + // // Ok - Validation passed + // assert_ok!(result); + + // // Lower Alpha price to 0.0001 so that there is not enough alpha to cover tx fees + // AlphaSqrtPrice::::insert(sn.subnets[0].netuid, U64F64::from_num(0.01)); + // let result_low_alpha_price = ext.validate( + // RuntimeOrigin::signed(sn.coldkey).into(), + // &call.clone(), + // &info, + // 10, + // (), + // &TxBaseImplication(()), + // TransactionSource::External, + // ); + // assert_eq!( + // result_low_alpha_price.unwrap_err(), + // TransactionValidityError::Invalid(InvalidTransaction::Payment) + // ); + // }); } // Validation passes, but transaction fails => TAO fees are paid diff --git a/precompiles/src/alpha.rs b/precompiles/src/alpha.rs index 8dcea0e829..98737f1269 100644 --- a/precompiles/src/alpha.rs +++ b/precompiles/src/alpha.rs @@ -5,7 +5,7 @@ use pallet_evm::{BalanceConverter, PrecompileHandle, SubstrateBalance}; use precompile_utils::EvmResult; use sp_core::U256; use sp_std::vec::Vec; -use substrate_fixed::types::U96F32; +use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::{Currency, NetUid}; use subtensor_swap_interface::{Order, SwapHandler}; @@ -37,7 +37,7 @@ where fn get_alpha_price(_handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { let current_alpha_price = as SwapHandler>::current_alpha_price(netuid.into()); - let price = current_alpha_price.saturating_mul(U96F32::from_num(1_000_000_000)); + let price = current_alpha_price.saturating_mul(U64F64::from_num(1_000_000_000)); let price: SubstrateBalance = price.saturating_to_num::().into(); let price_eth = ::BalanceConverter::into_evm_balance(price) .map(|amount| amount.into_u256()) @@ -194,18 +194,18 @@ where .filter(|(netuid, _)| *netuid != NetUid::ROOT) .collect::>(); - let mut sum_alpha_price: U96F32 = U96F32::from_num(0); + let mut sum_alpha_price: U64F64 = U64F64::from_num(0); for (netuid, _) in netuids { let price = as SwapHandler>::current_alpha_price( netuid.into(), ); - if price < U96F32::from_num(1) { + if price < U64F64::from_num(1) { sum_alpha_price = sum_alpha_price.saturating_add(price); } } - let price = sum_alpha_price.saturating_mul(U96F32::from_num(1_000_000_000)); + let price = sum_alpha_price.saturating_mul(U64F64::from_num(1_000_000_000)); let price: SubstrateBalance = price.saturating_to_num::().into(); let price_eth = ::BalanceConverter::into_evm_balance(price) .map(|amount| amount.into_u256()) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 52746675f9..c74bb0249a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2544,10 +2544,10 @@ impl_runtime_apis! { impl pallet_subtensor_swap_runtime_api::SwapRuntimeApi for Runtime { fn current_alpha_price(netuid: NetUid) -> u64 { - use substrate_fixed::types::U96F32; + use substrate_fixed::types::U64F64; pallet_subtensor_swap::Pallet::::current_price(netuid.into()) - .saturating_mul(U96F32::from_num(1_000_000_000)) + .saturating_mul(U64F64::from_num(1_000_000_000)) .saturating_to_num() }