From de842dd2a63a46a79cd3ddbfe5e718402863df7f Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 24 Feb 2026 15:03:28 -0500 Subject: [PATCH] Revert "mainnet deploy 2/20/2026" --- Cargo.lock | 254 +- Cargo.toml | 1 - chain-extensions/src/lib.rs | 4 +- chain-extensions/src/mock.rs | 22 +- chain-extensions/src/tests.rs | 4 +- common/src/lib.rs | 6 - contract-tests/package-lock.json | 6222 ----------------- .../src/contracts/precompileWrapper.sol | 362 - .../src/contracts/precompileWrapper.ts | 714 -- contract-tests/src/contracts/votingPower.ts | 104 - contract-tests/src/subtensor.ts | 25 +- .../precompileWrapper.direct-call.test.ts | 403 -- .../test/runtime.call.precompile.test.ts | 2 +- .../subnet.precompile.hyperparameter.test.ts | 16 +- .../test/transaction.replace.test.ts | 83 - .../test/votingPower.precompile.test.ts | 226 - contract-tests/yarn.lock | 64 +- node/src/benchmarking.rs | 41 +- node/src/mev_shield/author.rs | 40 +- pallets/admin-utils/src/benchmarking.rs | 10 +- pallets/admin-utils/src/lib.rs | 88 +- pallets/admin-utils/src/tests/mock.rs | 26 +- pallets/admin-utils/src/tests/mod.rs | 116 +- pallets/shield/src/benchmarking.rs | 4 +- pallets/shield/src/lib.rs | 16 +- pallets/subtensor/Cargo.toml | 17 +- pallets/subtensor/src/benchmarks.rs | 218 +- .../subtensor/src/coinbase/block_emission.rs | 18 +- pallets/subtensor/src/coinbase/root.rs | 2 + .../subtensor/src/coinbase/run_coinbase.rs | 12 +- pallets/subtensor/src/epoch/run_epoch.rs | 6 - .../src/extensions/check_coldkey_swap.rs | 384 - pallets/subtensor/src/extensions/mod.rs | 5 - pallets/subtensor/src/lib.rs | 145 +- pallets/subtensor/src/macros/config.rs | 16 +- pallets/subtensor/src/macros/dispatches.rs | 433 +- pallets/subtensor/src/macros/errors.rs | 26 +- pallets/subtensor/src/macros/events.rs | 85 +- pallets/subtensor/src/macros/genesis.rs | 1 + pallets/subtensor/src/macros/hooks.rs | 4 +- .../src/migrations/migrate_cleanup_swap_v3.rs | 70 - .../migrate_coldkey_swap_scheduled.rs | 7 + ...coldkey_swap_scheduled_to_announcements.rs | 91 - pallets/subtensor/src/migrations/mod.rs | 2 - pallets/subtensor/src/rpc_info/subnet_info.rs | 3 +- pallets/subtensor/src/staking/add_stake.rs | 14 +- pallets/subtensor/src/staking/helpers.rs | 20 +- pallets/subtensor/src/staking/move_stake.rs | 15 +- .../subtensor/src/staking/recycle_alpha.rs | 37 - pallets/subtensor/src/staking/remove_stake.rs | 5 +- pallets/subtensor/src/staking/stake_utils.rs | 111 +- pallets/subtensor/src/subnets/mechanism.rs | 42 +- pallets/subtensor/src/subnets/subnet.rs | 2 + pallets/subtensor/src/swap/swap_coldkey.rs | 296 +- pallets/subtensor/src/swap/swap_hotkey.rs | 5 - pallets/subtensor/src/tests/claim_root.rs | 56 +- pallets/subtensor/src/tests/coinbase.rs | 172 +- pallets/subtensor/src/tests/mechanism.rs | 1 - pallets/subtensor/src/tests/migration.rs | 129 +- pallets/subtensor/src/tests/mock.rs | 85 +- pallets/subtensor/src/tests/mod.rs | 3 +- pallets/subtensor/src/tests/move_stake.rs | 7 +- pallets/subtensor/src/tests/networks.rs | 484 +- pallets/subtensor/src/tests/recycle_alpha.rs | 295 +- pallets/subtensor/src/tests/registration.rs | 2 +- pallets/subtensor/src/tests/serving.rs | 2 +- pallets/subtensor/src/tests/staking.rs | 438 +- pallets/subtensor/src/tests/subnet.rs | 57 + pallets/subtensor/src/tests/swap_coldkey.rs | 2844 +++++--- pallets/subtensor/src/tests/voting_power.rs | 760 -- pallets/subtensor/src/tests/weights.rs | 2 +- .../subtensor.rs => transaction_extension.rs} | 162 +- pallets/subtensor/src/utils/misc.rs | 21 +- pallets/subtensor/src/utils/mod.rs | 1 - pallets/subtensor/src/utils/rate_limiting.rs | 4 - pallets/subtensor/src/utils/voting_power.rs | 280 - pallets/swap-interface/src/lib.rs | 15 +- pallets/swap-interface/src/order.rs | 10 +- pallets/swap/Cargo.toml | 5 - pallets/swap/rpc/src/lib.rs | 16 +- pallets/swap/runtime-api/Cargo.toml | 2 - pallets/swap/runtime-api/src/lib.rs | 14 +- pallets/swap/src/benchmarking.rs | 130 +- pallets/swap/src/lib.rs | 6 + pallets/swap/src/mock.rs | 74 +- pallets/swap/src/pallet/balancer.rs | 1095 --- pallets/swap/src/pallet/hooks.rs | 30 - pallets/swap/src/pallet/impls.rs | 1020 ++- .../migrations/migrate_swapv3_to_balancer.rs | 81 - pallets/swap/src/pallet/migrations/mod.rs | 25 - pallets/swap/src/pallet/mod.rs | 565 +- pallets/swap/src/pallet/swap_step.rs | 530 +- pallets/swap/src/pallet/tests.rs | 3315 +++++++-- pallets/swap/src/position.rs | 198 + pallets/swap/src/tick.rs | 2198 ++++++ pallets/swap/src/weights.rs | 56 + pallets/transaction-fee/src/lib.rs | 30 +- pallets/transaction-fee/src/tests/mock.rs | 76 +- pallets/transaction-fee/src/tests/mod.rs | 59 +- precompiles/Cargo.toml | 2 - precompiles/src/alpha.rs | 10 +- precompiles/src/balance_transfer.rs | 14 +- precompiles/src/crowdloan.rs | 12 +- precompiles/src/ed25519.rs | 2 +- precompiles/src/extensions.rs | 10 +- precompiles/src/leasing.rs | 12 +- precompiles/src/lib.rs | 57 +- precompiles/src/neuron.rs | 12 +- precompiles/src/proxy.rs | 12 +- precompiles/src/sr25519.rs | 2 +- precompiles/src/staking.rs | 28 +- precompiles/src/storage_query.rs | 2 +- precompiles/src/subnet.rs | 12 +- precompiles/src/uid_lookup.rs | 2 +- precompiles/src/voting_power.rs | 131 - runtime/Cargo.toml | 3 - runtime/src/base_call_filter.rs | 96 - runtime/src/lib.rs | 136 +- runtime/tests/precompiles.rs | 219 - scripts/update-deps-to-path.sh | 90 - 120 files changed, 10902 insertions(+), 16262 deletions(-) delete mode 100644 contract-tests/package-lock.json delete mode 100644 contract-tests/src/contracts/precompileWrapper.sol delete mode 100644 contract-tests/src/contracts/precompileWrapper.ts delete mode 100644 contract-tests/src/contracts/votingPower.ts delete mode 100644 contract-tests/test/precompileWrapper.direct-call.test.ts delete mode 100644 contract-tests/test/transaction.replace.test.ts delete mode 100644 contract-tests/test/votingPower.precompile.test.ts delete mode 100644 pallets/subtensor/src/extensions/check_coldkey_swap.rs delete mode 100644 pallets/subtensor/src/extensions/mod.rs delete mode 100644 pallets/subtensor/src/migrations/migrate_cleanup_swap_v3.rs delete mode 100644 pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs delete mode 100644 pallets/subtensor/src/tests/voting_power.rs rename pallets/subtensor/src/{extensions/subtensor.rs => transaction_extension.rs} (70%) delete mode 100644 pallets/subtensor/src/utils/voting_power.rs delete mode 100644 pallets/swap/src/pallet/balancer.rs delete mode 100644 pallets/swap/src/pallet/hooks.rs delete mode 100644 pallets/swap/src/pallet/migrations/migrate_swapv3_to_balancer.rs delete mode 100644 pallets/swap/src/pallet/migrations/mod.rs create mode 100644 pallets/swap/src/position.rs create mode 100644 pallets/swap/src/tick.rs delete mode 100644 precompiles/src/voting_power.rs delete mode 100644 runtime/src/base_call_filter.rs delete mode 100644 runtime/tests/precompiles.rs delete mode 100755 scripts/update-deps-to-path.sh diff --git a/Cargo.lock b/Cargo.lock index 71fe31e5f4..f936e40cf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,17 +71,6 @@ 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" @@ -508,7 +497,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" dependencies = [ - "ahash 0.8.12", + "ahash", "ark-ff 0.5.0", "ark-poly 0.5.0", "ark-serialize 0.5.0", @@ -743,7 +732,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" dependencies = [ - "ahash 0.8.12", + "ahash", "ark-ff 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", @@ -1680,29 +1669,6 @@ 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" @@ -1951,17 +1917,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "bstr" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" -dependencies = [ - "memchr", - "regex-automata 0.4.11", - "serde", -] - [[package]] name = "build-helper" version = "0.1.1" @@ -1989,28 +1944,6 @@ 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" @@ -3827,7 +3760,6 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.106", - "unicode-xid", ] [[package]] @@ -5759,9 +5691,6 @@ 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" @@ -5769,7 +5698,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.12", + "ahash", ] [[package]] @@ -5778,7 +5707,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.12", + "ahash", "allocator-api2", "serde", ] @@ -8435,7 +8364,6 @@ dependencies = [ "polkadot-runtime-common", "precompile-utils", "rand_chacha 0.3.1", - "safe-math", "scale-info", "serde_json", "sha2 0.10.9", @@ -8654,7 +8582,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 3.4.0", "proc-macro2", "quote", "syn 2.0.106", @@ -10847,18 +10775,15 @@ dependencies = [ "log", "ndarray", "num-traits", - "pallet-aura", "pallet-balances", "pallet-commitments", "pallet-crowdloan", "pallet-drand", "pallet-preimage", "pallet-scheduler", - "pallet-shield", "pallet-subtensor-proxy", "pallet-subtensor-swap", "pallet-subtensor-utility", - "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", "polkadot-runtime-common", @@ -10870,7 +10795,6 @@ dependencies = [ "serde_json", "sha2 0.10.9", "share-pool", - "sp-consensus-aura", "sp-core", "sp-io", "sp-keyring", @@ -10913,9 +10837,6 @@ dependencies = [ "log", "pallet-subtensor-swap-runtime-api", "parity-scale-codec", - "rand 0.8.5", - "rayon", - "safe-bigmath", "safe-math", "scale-info", "serde", @@ -10952,7 +10873,6 @@ dependencies = [ "frame-support", "parity-scale-codec", "scale-info", - "serde", "sp-api", "sp-std", "subtensor-macros", @@ -13196,23 +13116,18 @@ name = "precompile-utils" version = "0.1.0" source = "git+https://github.com/opentensor/frontier?rev=f12a1274f91442a564bb722a2b9547caba487fa0#f12a1274f91442a564bb722a2b9547caba487fa0" dependencies = [ - "derive_more 1.0.0", "environmental", "evm", "fp-evm", "frame-support", "frame-system", "hex", - "hex-literal", "impl-trait-for-tuples", "log", "num_enum", "pallet-evm", "parity-scale-codec", "precompile-utils-macro", - "scale-info", - "serde", - "similar-asserts", "sp-core", "sp-io", "sp-runtime", @@ -13557,26 +13472,6 @@ 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" @@ -13685,29 +13580,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "quoth" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d9da82a5dc3ff2fb2eee43d2b434fb197a9bf6a2a243850505b61584f888d2" -dependencies = [ - "quoth-macros", - "regex", - "rust_decimal", - "safe-string", -] - -[[package]] -name = "quoth-macros" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58547202bec9896e773db7ef04b4d47c444f9c97bc4386f36e55718c347db440" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "r-efi" version = "5.3.0" @@ -13986,15 +13858,6 @@ 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" @@ -14049,35 +13912,6 @@ 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" @@ -14313,22 +14147,6 @@ 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" @@ -14584,17 +14402,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "safe-bigmath" -version = "0.3.0" -source = "git+https://github.com/sam0x17/safe-bigmath#1da0a09c5bcf143fa7c464b431bfaeff5476b080" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", - "quoth", -] - [[package]] name = "safe-math" version = "0.1.0" @@ -14614,12 +14421,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "safe-string" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fc51f1e562058dee569383bfdb5a58752bfeb7fa7f0823f5c07c4c45381b5a" - [[package]] name = "safe_arch" version = "0.7.4" @@ -15041,7 +14842,7 @@ name = "sc-consensus-grandpa" version = "0.36.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8#a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8" dependencies = [ - "ahash 0.8.12", + "ahash", "array-bytes 6.2.3", "async-trait", "dyn-clone", @@ -15344,7 +15145,7 @@ name = "sc-network-gossip" version = "0.51.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8#a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8" dependencies = [ - "ahash 0.8.12", + "ahash", "futures", "futures-timer", "log", @@ -16057,7 +15858,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ - "ahash 0.8.12", + "ahash", "cfg-if", "hashbrown 0.13.2", ] @@ -16121,12 +15922,6 @@ 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" @@ -16558,32 +16353,6 @@ dependencies = [ "wide", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "similar" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -dependencies = [ - "bstr", - "unicode-segmentation", -] - -[[package]] -name = "similar-asserts" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" -dependencies = [ - "console", - "similar", -] - [[package]] name = "simple-dns" version = "0.9.3" @@ -17664,7 +17433,7 @@ name = "sp-trie" version = "40.0.0" source = "git+https://github.com/opentensor/polkadot-sdk.git?rev=a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8#a584a577eeaf31e3f1a65e91b0e0b41f0356f7c8" dependencies = [ - "ahash 0.8.12", + "ahash", "foldhash 0.1.5", "hash-db", "hashbrown 0.15.5", @@ -18312,7 +18081,7 @@ dependencies = [ name = "subtensor-macros" version = "0.1.0" dependencies = [ - "ahash 0.8.12", + "ahash", "proc-macro2", "quote", "syn 2.0.106", @@ -18336,7 +18105,6 @@ dependencies = [ "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", - "pallet-shield", "pallet-subtensor", "pallet-subtensor-proxy", "pallet-subtensor-swap", @@ -19907,7 +19675,7 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c128c039340ffd50d4195c3f8ce31aac357f06804cfc494c8b9508d4b30dca4" dependencies = [ - "ahash 0.8.12", + "ahash", "hashbrown 0.14.5", "string-interner", ] diff --git a/Cargo.toml b/Cargo.toml index d1784b025b..efc22431fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ 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", default-features = false, git = "https://github.com/sam0x17/safe-bigmath" } 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 2e2eb807fa..eaaee70d27 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::U64F64; +use substrate_fixed::types::U96F32; 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(U64F64::from_num(1_000_000_000)); + let price = current_alpha_price.saturating_mul(U96F32::from_num(1_000_000_000)); let price: u64 = price.saturating_to_num(); let encoded_result = price.encode(); diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index a1e6334b1b..660aa0c77c 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -25,7 +25,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Convert, IdentityLookup}, }; use sp_std::{cell::RefCell, cmp::Ordering, sync::OnceLock}; -use subtensor_runtime_common::{AlphaCurrency, AuthorshipInfo, NetUid, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; type Block = frame_system::mocking::MockBlock; @@ -262,14 +262,6 @@ parameter_types! { pub const AnnouncementDepositFactor: Balance = 1; } -pub struct MockAuthorshipProvider; - -impl AuthorshipInfo for MockAuthorshipProvider { - fn author() -> Option { - Some(U256::from(12345u64)) - } -} - parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -333,8 +325,9 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; - pub const InitialColdkeySwapReannouncementDelay: u64 = 10; + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -404,8 +397,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; @@ -419,13 +412,13 @@ impl pallet_subtensor::Config for Test { type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; type CommitmentsInterface = CommitmentsI; type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = MockAuthorshipProvider; } // Swap-related parameter types parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const SwapMaxFeeRate: u16 = 10000; // 15.26% + pub const SwapMaxPositions: u32 = 100; pub const SwapMinimumLiquidity: u64 = 1_000; pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(100).unwrap(); } @@ -437,6 +430,7 @@ impl pallet_subtensor_swap::Config for Test { type TaoReserve = TaoCurrencyReserve; type AlphaReserve = AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; type MinimumReserve = SwapMinimumReserve; type WeightInfo = (); diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index f3abfa2c91..bd6f46c8ab 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::U64F64; +use substrate_fixed::types::U96F32; 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(U64F64::from_num(1_000_000_000)); + let expected_price_scaled = expected_price.saturating_mul(U96F32::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/common/src/lib.rs b/common/src/lib.rs index f28ec6d878..658f8b2e01 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -260,12 +260,6 @@ pub trait BalanceOps { ) -> Result; } -/// Allows to query the current block author -pub trait AuthorshipInfo { - /// Return the current block author - fn author() -> Option; -} - pub mod time { use super::*; diff --git a/contract-tests/package-lock.json b/contract-tests/package-lock.json deleted file mode 100644 index 06c722a6de..0000000000 --- a/contract-tests/package-lock.json +++ /dev/null @@ -1,6222 +0,0 @@ -{ - "name": "contract-tests", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "license": "ISC", - "dependencies": { - "@polkadot-api/descriptors": "file:.papi/descriptors", - "@polkadot-api/ink-contracts": "^0.4.1", - "@polkadot-api/sdk-ink": "^0.5.1", - "@polkadot-labs/hdkd": "^0.0.25", - "@polkadot-labs/hdkd-helpers": "^0.0.25", - "@polkadot/api": "^16.4.6", - "@polkadot/util-crypto": "^14.0.1", - "@types/mocha": "^10.0.10", - "dotenv": "17.2.1", - "ethers": "^6.13.5", - "mocha": "^11.1.0", - "polkadot-api": "^1.22.0", - "rxjs": "^7.8.2", - "scale-ts": "^1.6.1", - "viem": "2.23.4", - "ws": "^8.18.2" - }, - "devDependencies": { - "@types/chai": "^5.0.1", - "@types/node": "^22.18.0", - "assert": "^2.1.0", - "chai": "^6.0.1", - "prettier": "^3.3.3", - "ts-node": "^10.9.2", - "typescript": "^5.7.2" - } - }, - ".papi/descriptors": { - "name": "@polkadot-api/descriptors", - "version": "0.1.0-autogenerated.5063582544821983772", - "peerDependencies": { - "polkadot-api": ">=1.21.0" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", - "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@commander-js/extra-typings": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-14.0.0.tgz", - "integrity": "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg==", - "license": "MIT", - "peerDependencies": { - "commander": "~14.0.0" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@ethereumjs/rlp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.0.tgz", - "integrity": "sha512-r67BJbwilammAqYI4B5okA66cNdTlFzeWxPNJOolKV52ZS/flo0tUBf4x4gxWXBgh48OgsdFV1Qp5pRoSe8IhQ==", - "license": "MPL-2.0", - "bin": { - "rlp": "bin/rlp.cjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/ciphers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", - "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/curves/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@polkadot-api/cli": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@polkadot-api/cli/-/cli-0.16.3.tgz", - "integrity": "sha512-s+p3dFw1vOeyMMqhUbt1RFyqPZdR7vg6joS0v9wBvK3qX5xU+QfOOaMxXJ8fl0mJEbwoJnJsvVl4MzjsABaKCg==", - "license": "MIT", - "dependencies": { - "@commander-js/extra-typings": "^14.0.0", - "@polkadot-api/codegen": "0.20.0", - "@polkadot-api/ink-contracts": "0.4.3", - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/known-chains": "0.9.15", - "@polkadot-api/legacy-provider": "0.3.6", - "@polkadot-api/metadata-compatibility": "0.4.1", - "@polkadot-api/observable-client": "0.17.0", - "@polkadot-api/polkadot-sdk-compat": "2.3.3", - "@polkadot-api/sm-provider": "0.1.14", - "@polkadot-api/smoldot": "0.3.14", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/substrate-client": "0.4.7", - "@polkadot-api/utils": "0.2.0", - "@polkadot-api/wasm-executor": "^0.2.2", - "@polkadot-api/ws-provider": "0.7.4", - "@types/node": "^24.10.1", - "commander": "^14.0.2", - "execa": "^9.6.0", - "fs.promises.exists": "^1.1.4", - "ora": "^9.0.0", - "read-pkg": "^10.0.0", - "rxjs": "^7.8.2", - "tsc-prog": "^2.3.0", - "tsup": "8.5.0", - "typescript": "^5.9.3", - "write-package": "^7.2.0" - }, - "bin": { - "papi": "dist/main.js", - "polkadot-api": "dist/main.js" - } - }, - "node_modules/@polkadot-api/cli/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@polkadot-api/cli/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/@polkadot-api/codegen": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/codegen/-/codegen-0.20.0.tgz", - "integrity": "sha512-akwPArm35UZcebUFtTKcEkdBLCjYyKweGw3/tT04p/EtM4OsQ1FxhRdXZ51ScBC3JVGCFQTUO2hNsd1E6YXvlw==", - "license": "MIT", - "dependencies": { - "@polkadot-api/ink-contracts": "0.4.3", - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/metadata-compatibility": "0.4.1", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/common-sdk-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/common-sdk-utils/-/common-sdk-utils-0.1.0.tgz", - "integrity": "sha512-cgA9fh8dfBai9b46XaaQmj9vwzyHStQjc/xrAvQksgF6SqvZ0yAfxVqLvGrsz/Xi3dsAdKLg09PybC7MUAMv9w==", - "license": "MIT", - "peerDependencies": { - "polkadot-api": "^1.8.1", - "rxjs": ">=7.8.1" - } - }, - "node_modules/@polkadot-api/descriptors": { - "resolved": ".papi/descriptors", - "link": true - }, - "node_modules/@polkadot-api/ink-contracts": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@polkadot-api/ink-contracts/-/ink-contracts-0.4.3.tgz", - "integrity": "sha512-Wl+4Dxjt0GAl+rADZEgrrqEesqX/xygTpX18TmzmspcKhb9QIZf9FJI8A5Sgtq0TKAOwsd1d/hbHVX3LgbXFXg==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/json-rpc-provider": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.4.tgz", - "integrity": "sha512-9cDijLIxzHOBuq6yHqpqjJ9jBmXrctjc1OFqU+tQrS96adQze3mTIH6DTgfb/0LMrqxzxffz1HQGrIlEH00WrA==", - "license": "MIT" - }, - "node_modules/@polkadot-api/json-rpc-provider-proxy": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.2.7.tgz", - "integrity": "sha512-+HM4JQXzO2GPUD2++4GOLsmFL6LO8RoLvig0HgCLuypDgfdZMlwd8KnyGHjRnVEHA5X+kvXbk84TDcAXVxTazQ==", - "license": "MIT" - }, - "node_modules/@polkadot-api/known-chains": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/@polkadot-api/known-chains/-/known-chains-0.9.15.tgz", - "integrity": "sha512-VQGu2Anvnx0y0Ltd6sQB3aYzQFGsaQwf2znh+w4Oflaxln5lsjO/+trpXz/rdrdgyi0iafkhpeho/p/EGBwJ+A==", - "license": "MIT" - }, - "node_modules/@polkadot-api/legacy-provider": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@polkadot-api/legacy-provider/-/legacy-provider-0.3.6.tgz", - "integrity": "sha512-JZQg0HVtBowFKxNrZdnMBKXmeSBD4yFlz6egEpvE97RXRvjaBzTaVuFFhBchngq9YmgFQewuWSoX5XSUW6hcEg==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/raw-client": "0.1.1", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - }, - "peerDependencies": { - "rxjs": ">=7.8.0" - } - }, - "node_modules/@polkadot-api/logs-provider": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@polkadot-api/logs-provider/-/logs-provider-0.0.6.tgz", - "integrity": "sha512-4WgHlvy+xee1ADaaVf6+MlK/+jGMtsMgAzvbQOJZnP4PfQuagoTqaeayk8HYKxXGphogLlPbD06tANxcb+nvAg==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4" - } - }, - "node_modules/@polkadot-api/merkleize-metadata": { - "version": "1.1.27", - "resolved": "https://registry.npmjs.org/@polkadot-api/merkleize-metadata/-/merkleize-metadata-1.1.27.tgz", - "integrity": "sha512-OdKwOzzrLL0Ju3pQA9LjeQEquMcD+KtLybUAO3fVxwjxD5cyI0RwillGoAIBJvfMaZpNxnxJnD+WzNjRcr7FiQ==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/metadata-builders": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.13.7.tgz", - "integrity": "sha512-xwggY8F/gtX7qGzz+jzP3DZvWgBWIIFQhk+r2MJ431CR+tNKeTtzGdwNocVrb9NYTK2naC9ckJS14nrNM6LWLw==", - "license": "MIT", - "dependencies": { - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/metadata-compatibility": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-compatibility/-/metadata-compatibility-0.4.1.tgz", - "integrity": "sha512-mZt4Af6oPXEHAprrckJiSZkWRVf0mqwF+Bm+703rPsezLptQid9AjSzh1hkgIkOrPbg6IhWbmMhbuJVjx9VeQA==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/substrate-bindings": "0.16.5" - } - }, - "node_modules/@polkadot-api/observable-client": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.17.0.tgz", - "integrity": "sha512-hilb12Fg1JrlM/0nucMT85//EQltB53fmoh7YNBsZMiNpavn/3qGTO4s0JMlC/LBbddYg0nxA+DMkSVlapo7cQ==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/substrate-client": "0.4.7", - "@polkadot-api/utils": "0.2.0" - }, - "peerDependencies": { - "rxjs": ">=7.8.0" - } - }, - "node_modules/@polkadot-api/pjs-signer": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@polkadot-api/pjs-signer/-/pjs-signer-0.6.17.tgz", - "integrity": "sha512-bxFtyiNOchV0osh6m+1CaN4tkWF7Mo4IT9XPLZBwSybpHZgwmu2wbhgqBkVL98QMyGzud7NHfrJsTCgFU6jHGg==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/polkadot-signer": "0.1.6", - "@polkadot-api/signers-common": "0.1.18", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/polkadot-sdk-compat": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@polkadot-api/polkadot-sdk-compat/-/polkadot-sdk-compat-2.3.3.tgz", - "integrity": "sha512-p30po+iv4trniSJ7UZiIt/rFInvtA9Tzg65EzuRkCaQAnh54a3MPp9w/q+x+SNLEcfzVLvf8LyPnMPOIpKuj5w==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4" - } - }, - "node_modules/@polkadot-api/polkadot-signer": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@polkadot-api/polkadot-signer/-/polkadot-signer-0.1.6.tgz", - "integrity": "sha512-X7ghAa4r7doETtjAPTb50IpfGtrBmy3BJM5WCfNKa1saK04VFY9w+vDn+hwEcM4p0PcDHt66Ts74hzvHq54d9A==", - "license": "MIT" - }, - "node_modules/@polkadot-api/raw-client": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@polkadot-api/raw-client/-/raw-client-0.1.1.tgz", - "integrity": "sha512-HxalpNEo8JCYXfxKM5p3TrK8sEasTGMkGjBNLzD4TLye9IK2smdb5oTvp2yfkU1iuVBdmjr69uif4NaukOYo2g==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4" - } - }, - "node_modules/@polkadot-api/sdk-ink": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@polkadot-api/sdk-ink/-/sdk-ink-0.5.1.tgz", - "integrity": "sha512-9pRnghjigivvgq7375hzkoazstvPDbc0YB01Jzw1/MYKcX+YJn1p/H8SAQTWbKlz2ohFgi1nwU52a0bsmKqb/Q==", - "license": "MIT", - "dependencies": { - "@ethereumjs/rlp": "^10.0.0", - "@polkadot-api/common-sdk-utils": "0.1.0", - "@polkadot-api/substrate-bindings": "^0.16.3", - "abitype": "^1.1.1", - "viem": "^2.37.9" - }, - "peerDependencies": { - "@polkadot-api/ink-contracts": ">=0.4.0", - "polkadot-api": ">=1.19.0", - "rxjs": ">=7.8.0" - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/@noble/curves": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/isows": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", - "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/ox": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", - "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.11.0", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "1.9.1", - "@noble/hashes": "^1.8.0", - "@scure/bip32": "^1.7.0", - "@scure/bip39": "^1.6.0", - "abitype": "^1.0.9", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/viem": { - "version": "2.41.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.41.2.tgz", - "integrity": "sha512-LYliajglBe1FU6+EH9mSWozp+gRA/QcHfxeD9Odf83AdH5fwUS7DroH4gHvlv6Sshqi1uXrYFA2B/EOczxd15g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.9.1", - "@noble/hashes": "1.8.0", - "@scure/bip32": "1.7.0", - "@scure/bip39": "1.6.0", - "abitype": "1.1.0", - "isows": "1.0.7", - "ox": "0.9.6", - "ws": "8.18.3" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@polkadot-api/sdk-ink/node_modules/viem/node_modules/abitype": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", - "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@polkadot-api/signer": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@polkadot-api/signer/-/signer-0.2.11.tgz", - "integrity": "sha512-32tqbJo6JDfc/lHg+nTveeunFRULonWoTQX9xbs70arr/tAyyZfljupdECRK8CVRx1777es/CQO3QVj8EpWtYg==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^2.0.1", - "@polkadot-api/merkleize-metadata": "1.1.27", - "@polkadot-api/polkadot-signer": "0.1.6", - "@polkadot-api/signers-common": "0.1.18", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/signers-common": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@polkadot-api/signers-common/-/signers-common-0.1.18.tgz", - "integrity": "sha512-UQXuRZoQ+jMolEpIPF0mVXcoqQ/382fHrSOgfK5sIvjeH0HPf4P+s3IwcnwyAdpHY2gdHXYlHd/SAw7Q1gJ4EA==", - "license": "MIT", - "dependencies": { - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/polkadot-signer": "0.1.6", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/sm-provider": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/@polkadot-api/sm-provider/-/sm-provider-0.1.14.tgz", - "integrity": "sha512-QQvoeBSIwnEm8IUhGA6sBU6LNh2v7SOuVOnF77ZD7P5ELTrdmQH2Tcn0W15qGTmTG45b3Z52XsKpuQbIJ7c7XA==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/json-rpc-provider-proxy": "0.2.7" - }, - "peerDependencies": { - "@polkadot-api/smoldot": ">=0.3" - } - }, - "node_modules/@polkadot-api/smoldot": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@polkadot-api/smoldot/-/smoldot-0.3.14.tgz", - "integrity": "sha512-eWqO0xFQaKzqY5mRYxYuZcj1IiaLcQP+J38UQyuJgEorm+9yHVEQ/XBWoM83P+Y8TwE5IWTICp1LCVeiFQTGPQ==", - "license": "MIT", - "dependencies": { - "@types/node": "^24.5.2", - "smoldot": "2.0.39" - } - }, - "node_modules/@polkadot-api/smoldot/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@polkadot-api/smoldot/node_modules/smoldot": { - "version": "2.0.39", - "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.39.tgz", - "integrity": "sha512-yFMSzI6nkqWFTNao99lBA/TguUFU+bR3A5UGTDd/QqqB12jqzvZnmW/No6l2rKmagt8Qx/KybMNowV/E28znhA==", - "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", - "dependencies": { - "ws": "^8.8.1" - } - }, - "node_modules/@polkadot-api/smoldot/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/@polkadot-api/substrate-bindings": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.16.5.tgz", - "integrity": "sha512-QFgNlBmtLtiUGTCTurxcE6UZrbI2DaQ5/gyIiC2FYfEhStL8tl20b09FRYHcSjY+lxN42Rcf9HVX+MCFWLYlpQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "^2.0.1", - "@polkadot-api/utils": "0.2.0", - "@scure/base": "^2.0.0", - "scale-ts": "^1.6.1" - } - }, - "node_modules/@polkadot-api/substrate-bindings/node_modules/@scure/base": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", - "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-api/substrate-client": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.4.7.tgz", - "integrity": "sha512-Mmx9VKincVqfVQmq89gzDk4DN3uKwf8CxoqYvq+EiPUZ1QmMUc7X4QMwG1MXIlYdnm5LSXzn+2Jn8ik8xMgL+w==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/raw-client": "0.1.1", - "@polkadot-api/utils": "0.2.0" - } - }, - "node_modules/@polkadot-api/utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.2.0.tgz", - "integrity": "sha512-nY3i5fQJoAxU4n3bD7Fs208/KR2J95SGfVc58kDjbRYN5a84kWaGEqzjBNtP9oqht49POM8Bm9mbIrkvC1Bzuw==", - "license": "MIT" - }, - "node_modules/@polkadot-api/wasm-executor": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@polkadot-api/wasm-executor/-/wasm-executor-0.2.3.tgz", - "integrity": "sha512-B2h1o+Qlo9idpASaHvMSoViB2I5ko5OAfwfhYF8LQDkTADK0B+SeStzNj1Qn+FG34wqTuv7HzBCdjaUgzYINJQ==", - "license": "MIT" - }, - "node_modules/@polkadot-api/ws-provider": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@polkadot-api/ws-provider/-/ws-provider-0.7.4.tgz", - "integrity": "sha512-mkk2p8wPht+ljU1xULCPMsLpNF7NHuGaufuDCIZZgopALaZpfVFJxc3qa9s6Xv8X3hM+TRoC5WknuD1ykRY99A==", - "license": "MIT", - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/json-rpc-provider-proxy": "0.2.7", - "@types/ws": "^8.18.1", - "ws": "^8.18.3" - } - }, - "node_modules/@polkadot-labs/hdkd": { - "version": "0.0.25", - "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd/-/hdkd-0.0.25.tgz", - "integrity": "sha512-+yZJC1TE4ZKdfoILw8nGxu3H/klrYXm9GdVB0kcyQDecq320ThUmM1M4l8d1F/3QD0Nez9NwHi9t5B++OgJU5A==", - "license": "MIT", - "dependencies": { - "@polkadot-labs/hdkd-helpers": "~0.0.26" - } - }, - "node_modules/@polkadot-labs/hdkd-helpers": { - "version": "0.0.25", - "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.25.tgz", - "integrity": "sha512-GwHayBuyHKfzvGD0vG47NbjFeiK6rRQHQAn1syut9nt0mhXMg4yb3tJ//IyM317qWuDU3HbD2OIp5jKDEQz2/A==", - "license": "MIT", - "dependencies": { - "@noble/curves": "^2.0.0", - "@noble/hashes": "^2.0.0", - "@scure/base": "^2.0.0", - "@scure/sr25519": "^0.3.0", - "scale-ts": "^1.6.1" - } - }, - "node_modules/@polkadot-labs/hdkd-helpers/node_modules/@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "2.0.1" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-labs/hdkd-helpers/node_modules/@scure/base": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", - "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-labs/hdkd/node_modules/@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "2.0.1" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot-labs/hdkd/node_modules/@polkadot-labs/hdkd-helpers": { - "version": "0.0.26", - "resolved": "https://registry.npmjs.org/@polkadot-labs/hdkd-helpers/-/hdkd-helpers-0.0.26.tgz", - "integrity": "sha512-mp3GCSiOQeh4aPt+DYBQq6UnX/tKgYUH5F75knjW3ATSA90ifEEWWjRan0Bddt4QKYKamaDGadK9GbVREgzQFw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "@scure/base": "^2.0.0", - "@scure/sr25519": "^0.3.0", - "scale-ts": "^1.6.1" - } - }, - "node_modules/@polkadot-labs/hdkd/node_modules/@scure/base": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.0.0.tgz", - "integrity": "sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/api": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-16.5.3.tgz", - "integrity": "sha512-Ptwo0f5Qonmus7KIklsbFcGTdHtNjbTAwl5GGI8Mp0dmBc7Y/ISJpIJX49UrG6FhW6COMa0ItsU87XIWMRwI/Q==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/api-augment": "16.5.3", - "@polkadot/api-base": "16.5.3", - "@polkadot/api-derive": "16.5.3", - "@polkadot/keyring": "^13.5.9", - "@polkadot/rpc-augment": "16.5.3", - "@polkadot/rpc-core": "16.5.3", - "@polkadot/rpc-provider": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/types-augment": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/types-create": "16.5.3", - "@polkadot/types-known": "16.5.3", - "@polkadot/util": "^13.5.9", - "@polkadot/util-crypto": "^13.5.9", - "eventemitter3": "^5.0.1", - "rxjs": "^7.8.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-augment": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-16.5.3.tgz", - "integrity": "sha512-9+8YKSS66x9qpWS+ZQ/FSm9P4mgE+icD53oAmeIykriPW2gcSTAiNufLwAjmAJAkOLcqbTD7LPjFW6xFlmtYsA==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/api-base": "16.5.3", - "@polkadot/rpc-augment": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/types-augment": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-base": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-16.5.3.tgz", - "integrity": "sha512-M1+pY6OFQ1uOB73VQMt2JAGq/UVISVQJISqyfjiUllUc0qIzaDMkcZxRqE34Lwaib3fD3RuIpG6dXqCL9rdzJQ==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/rpc-core": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/util": "^13.5.9", - "rxjs": "^7.8.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-derive": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-16.5.3.tgz", - "integrity": "sha512-nMsnSC/N1SK1kNhgh2FhrrR1S8bTVH+3WsuBHFRzl+txKHq232IeIn9LpebSvgZdd77PaKaYBxbhYcNaA8Ypew==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/api": "16.5.3", - "@polkadot/api-augment": "16.5.3", - "@polkadot/api-base": "16.5.3", - "@polkadot/rpc-core": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/util": "^13.5.9", - "@polkadot/util-crypto": "^13.5.9", - "rxjs": "^7.8.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/api-derive/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/api-derive/node_modules/@polkadot/util-crypto": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz", - "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "13.5.9", - "@polkadot/util": "13.5.9", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-randomvalues": "13.5.9", - "@scure/base": "^1.1.7", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9" - } - }, - "node_modules/@polkadot/api/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/api/node_modules/@polkadot/util-crypto": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz", - "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "13.5.9", - "@polkadot/util": "13.5.9", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-randomvalues": "13.5.9", - "@scure/base": "^1.1.7", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9" - } - }, - "node_modules/@polkadot/keyring": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-13.5.9.tgz", - "integrity": "sha512-bMCpHDN7U8ytxawjBZ89/he5s3AmEZuOdkM/ABcorh/flXNPfyghjFK27Gy4OKoFxX52yJ2sTHR4NxM87GuFXQ==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "13.5.9", - "@polkadot/util-crypto": "13.5.9", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9", - "@polkadot/util-crypto": "13.5.9" - } - }, - "node_modules/@polkadot/keyring/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/keyring/node_modules/@polkadot/util-crypto": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz", - "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "13.5.9", - "@polkadot/util": "13.5.9", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-randomvalues": "13.5.9", - "@scure/base": "^1.1.7", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9" - } - }, - "node_modules/@polkadot/networks": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-13.5.9.tgz", - "integrity": "sha512-nmKUKJjiLgcih0MkdlJNMnhEYdwEml2rv/h59ll2+rAvpsVWMTLCb6Cq6q7UC44+8kiWK2UUJMkFU+3PFFxndA==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "13.5.9", - "@substrate/ss58-registry": "^1.51.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-augment": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-16.5.3.tgz", - "integrity": "sha512-q3Y+b0FSwbYe8Qopd4In+9KCL3eH5QmGVvimX7Z8+cvQ9+h+JUA6TP1bfpWBmYJRKlolaljsBQPBWoubchmxSw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/rpc-core": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-core": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-16.5.3.tgz", - "integrity": "sha512-UYEIRhO/1uTz/rpWLwUN9Re3c4fuTs0I9RR8dHKpKsH3jZTs1M3CtqME3NNzpGqApY1xb9tZemU/0GfHjCpeBQ==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/rpc-augment": "16.5.3", - "@polkadot/rpc-provider": "16.5.3", - "@polkadot/types": "16.5.3", - "@polkadot/util": "^13.5.9", - "rxjs": "^7.8.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/rpc-provider": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-16.5.3.tgz", - "integrity": "sha512-O7hD82HwjT4XJ4i/G58B52RSDM7arHXSpzahZKz4/wtb4x6d6b4JVdfZoskInadARFi5RwIWCrftwPtpRH81Fw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/keyring": "^13.5.9", - "@polkadot/types": "16.5.3", - "@polkadot/types-support": "16.5.3", - "@polkadot/util": "^13.5.9", - "@polkadot/util-crypto": "^13.5.9", - "@polkadot/x-fetch": "^13.5.9", - "@polkadot/x-global": "^13.5.9", - "@polkadot/x-ws": "^13.5.9", - "eventemitter3": "^5.0.1", - "mock-socket": "^9.3.1", - "nock": "^13.5.5", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@substrate/connect": "0.8.11" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/rpc-provider/node_modules/@polkadot/util-crypto": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz", - "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "13.5.9", - "@polkadot/util": "13.5.9", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-randomvalues": "13.5.9", - "@scure/base": "^1.1.7", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9" - } - }, - "node_modules/@polkadot/types": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-16.5.3.tgz", - "integrity": "sha512-xy9uv/X4iT7uJ7TNCoqbcMkR8ePHwNW6DgpOU+1y1zc/KSu9ZC5i+haFOL68BpmR/QXk99YfuHoKwXvteDmykw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/keyring": "^13.5.9", - "@polkadot/types-augment": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/types-create": "16.5.3", - "@polkadot/util": "^13.5.9", - "@polkadot/util-crypto": "^13.5.9", - "rxjs": "^7.8.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-augment": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-16.5.3.tgz", - "integrity": "sha512-SfS4arJUxW6BeCEhLMVPrZwWOLte69k5+/lvEKOKHQA8Mz0MEkD4uqGZGibDjgBgdnu8N+3b+rs+Fn3YfZu4yA==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/types": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-codec": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-16.5.3.tgz", - "integrity": "sha512-b+oKMrIZrsFH4pPwvGQ6lMS8oFrYAGMy9QSbytA+KDmXAgTCtShz5XGvdQabvsGCjJ45EKgkKpKynVcYh3gk8g==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "^13.5.9", - "@polkadot/x-bigint": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-create": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-16.5.3.tgz", - "integrity": "sha512-XGnBLNamPh7eQGcHNGFghA/prH7z2BsQ+9EVSbHCvw9ENr/Ow24mmmkZyMG5WM/5I6/4HRdfwFJucYt1GL/p9g==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/types-codec": "16.5.3", - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-known": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-16.5.3.tgz", - "integrity": "sha512-ZLAZI24bQD0C9CJWYHxrLG8QSmzRzfWa51rlSNwZ9Atsc3R+GeX1YZGc9IljpQxYJCHrCqd6X8TXpAmEJdnbKw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/networks": "^13.5.9", - "@polkadot/types": "16.5.3", - "@polkadot/types-codec": "16.5.3", - "@polkadot/types-create": "16.5.3", - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types-support": { - "version": "16.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-16.5.3.tgz", - "integrity": "sha512-ggyIRV+4Kn+aG1PiVT0PE00pAqMveyS3CuFsW9gJnKxeev4VrGfr08R4vw/61D7uIfpilkQdkXNgXAbeN09Mxg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "^13.5.9", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/types/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/types/node_modules/@polkadot/util-crypto": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-13.5.9.tgz", - "integrity": "sha512-foUesMhxkTk8CZ0/XEcfvHk6I0O+aICqqVJllhOpyp/ZVnrTBKBf59T6RpsXx2pCtBlMsLRvg/6Mw7RND1HqDg==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "13.5.9", - "@polkadot/util": "13.5.9", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-randomvalues": "13.5.9", - "@scure/base": "^1.1.7", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9" - } - }, - "node_modules/@polkadot/util": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-13.5.9.tgz", - "integrity": "sha512-pIK3XYXo7DKeFRkEBNYhf3GbCHg6dKQisSvdzZwuyzA6m7YxQq4DFw4IE464ve4Z7WsJFt3a6C9uII36hl9EWw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-bigint": "13.5.9", - "@polkadot/x-global": "13.5.9", - "@polkadot/x-textdecoder": "13.5.9", - "@polkadot/x-textencoder": "13.5.9", - "@types/bn.js": "^5.1.6", - "bn.js": "^5.2.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-14.0.1.tgz", - "integrity": "sha512-Cu7AKUzBTsUkbOtyuNzXcTpDjR9QW0fVR56o3gBmzfUCmvO1vlsuGzmmPzqpHymQQ3rrfqV78CPs62EGhw0R+A==", - "license": "Apache-2.0", - "dependencies": { - "@noble/curves": "^1.3.0", - "@noble/hashes": "^1.3.3", - "@polkadot/networks": "14.0.1", - "@polkadot/util": "14.0.1", - "@polkadot/wasm-crypto": "^7.5.3", - "@polkadot/wasm-util": "^7.5.3", - "@polkadot/x-bigint": "14.0.1", - "@polkadot/x-randomvalues": "14.0.1", - "@scure/base": "^1.1.7", - "@scure/sr25519": "^0.2.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "14.0.1" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/networks": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-14.0.1.tgz", - "integrity": "sha512-wGlBtXDkusRAj4P7uxfPz80gLO1+j99MLBaQi3bEym2xrFrFhgIWVHOZlBit/1PfaBjhX2Z8XjRxaM2w1p7w2w==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "14.0.1", - "@substrate/ss58-registry": "^1.51.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/util": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-14.0.1.tgz", - "integrity": "sha512-764HhxkPV3x5rM0/p6QdynC2dw26n+SaE+jisjx556ViCd4E28Ke4xSPef6C0Spy4aoXf2gt0PuLEcBvd6fVZg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-bigint": "14.0.1", - "@polkadot/x-global": "14.0.1", - "@polkadot/x-textdecoder": "14.0.1", - "@polkadot/x-textencoder": "14.0.1", - "@types/bn.js": "^5.1.6", - "bn.js": "^5.2.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-bigint": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-14.0.1.tgz", - "integrity": "sha512-gfozjGnebr2rqURs31KtaWumbW4rRZpbiluhlmai6luCNrf5u8pB+oLA35kPEntrsLk9PnIG9OsC/n4hEtx4OQ==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "14.0.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-global": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-14.0.1.tgz", - "integrity": "sha512-aCI44DJU4fU0XXqrrSGIpi7JrZXK2kpe0jaQ2p6oDVXOOYEnZYXnMhTTmBE1lF/xtxzX50MnZrrU87jziU0qbA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-randomvalues": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-14.0.1.tgz", - "integrity": "sha512-/XkQcvshzJLHITuPrN3zmQKuFIPdKWoaiHhhVLD6rQWV60lTXA3ajw3ocju8ZN7xRxnweMS9Ce0kMPYa0NhRMg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "14.0.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "14.0.1", - "@polkadot/wasm-util": "*" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-textdecoder": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-14.0.1.tgz", - "integrity": "sha512-CcWiPCuPVJsNk4Vq43lgFHqLRBQHb4r9RD7ZIYgmwoebES8TNm4g2ew9ToCzakFKSpzKu6I07Ne9wv/dt5zLuw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "14.0.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-textencoder": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-14.0.1.tgz", - "integrity": "sha512-VY51SpQmF1ccmAGLfxhYnAe95Spfz049WZ/+kK4NfsGF9WejxVdU53Im5C80l45r8qHuYQsCWU3+t0FNunh2Kg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "14.0.1", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/util-crypto/node_modules/@scure/sr25519": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@scure/sr25519/-/sr25519-0.2.0.tgz", - "integrity": "sha512-uUuLP7Z126XdSizKtrCGqYyR3b3hYtJ6Fg/XFUXmc2//k2aXHDLqZwFeXxL97gg4XydPROPVnuaHGF2+xriSKg==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.2", - "@noble/hashes": "~1.8.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/wasm-bridge": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.5.3.tgz", - "integrity": "sha512-mUvwwNH+uP1wqpMuHjmEwHxRIaVc5csmb+ukycWQGhzwhpXe/0fvBEU2TQ8kwgqO2MU0FS3hN/QcIWKfPRJgxQ==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/wasm-util": "7.5.3", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.5.3.tgz", - "integrity": "sha512-dmKUM9vw1wrnCHGuIeOtQo1pwuSF7fkyF4TYimTn3tAa0+3cDctYBErtGxgUeqP0Bo4Q0Of4/vnHlSk5Rbt9Uw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/wasm-bridge": "7.5.3", - "@polkadot/wasm-crypto-asmjs": "7.5.3", - "@polkadot/wasm-crypto-init": "7.5.3", - "@polkadot/wasm-crypto-wasm": "7.5.3", - "@polkadot/wasm-util": "7.5.3", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-asmjs": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.5.3.tgz", - "integrity": "sha512-fSbbjI+4p0U3PQ8nOz/3p7euHriSdh+2CSywNuXHa8fMaYlMqCKt9K7+HI8CQ4RZNvZWDq+Py1nEDEkM4rZrvw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-init": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.5.3.tgz", - "integrity": "sha512-KvUpxqvW70XhuDiw/N6rM8fQ7zRjIFblw+vdJ0/wwyagwg9jrYNA9TMei5ksQd9sxGCGXN/xJmwHJXuUjkocmg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/wasm-bridge": "7.5.3", - "@polkadot/wasm-crypto-asmjs": "7.5.3", - "@polkadot/wasm-crypto-wasm": "7.5.3", - "@polkadot/wasm-util": "7.5.3", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-wasm": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.5.3.tgz", - "integrity": "sha512-fc88+HyVxebB/40GVgGUOLBqyO3C571DXWPTFmtt5EX9H8gw7Jg0Bkitz7hgSVP2x4FjXpqS9UNTJ8trVH0x1A==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/wasm-util": "7.5.3", - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-util": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.5.3.tgz", - "integrity": "sha512-hBr9bbjS+Yr7DrDUSkIIuvlTSoAlI8WXuo9YEB4C76j130u/cl+zyq6Iy/WnaTE6QH+8i9DhM8QTety6TqYnUQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/x-bigint": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-13.5.9.tgz", - "integrity": "sha512-JVW6vw3e8fkcRyN9eoc6JIl63MRxNQCP/tuLdHWZts1tcAYao0hpWUzteqJY93AgvmQ91KPsC1Kf3iuuZCi74g==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-fetch": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-13.5.9.tgz", - "integrity": "sha512-urwXQZtT4yYROiRdJS6zHu18J/jCoAGpbgPIAjwdqjT11t9XIq4SjuPMxD19xBRhbYe9ocWV8i1KHuoMbZgKbA==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "node-fetch": "^3.3.2", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-global": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-13.5.9.tgz", - "integrity": "sha512-zSRWvELHd3Q+bFkkI1h2cWIqLo1ETm+MxkNXLec3lB56iyq/MjWBxfXnAFFYFayvlEVneo7CLHcp+YTFd9aVSA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-randomvalues": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-13.5.9.tgz", - "integrity": "sha512-Uuuz3oubf1JCCK97fsnVUnHvk4BGp/W91mQWJlgl5TIOUSSTIRr+lb5GurCfl4kgnQq53Zi5fJV+qR9YumbnZw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@polkadot/util": "13.5.9", - "@polkadot/wasm-util": "*" - } - }, - "node_modules/@polkadot/x-textdecoder": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-13.5.9.tgz", - "integrity": "sha512-W2HhVNUbC/tuFdzNMbnXAWsIHSg9SC9QWDNmFD3nXdSzlXNgL8NmuiwN2fkYvCQBtp/XSoy0gDLx0C+Fo19cfw==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-textencoder": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-13.5.9.tgz", - "integrity": "sha512-SG0MHnLUgn1ZxFdm0KzMdTHJ47SfqFhdIPMcGA0Mg/jt2rwrfrP3jtEIJMsHfQpHvfsNPfv55XOMmoPWuQnP/Q==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@polkadot/x-ws": { - "version": "13.5.9", - "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-13.5.9.tgz", - "integrity": "sha512-NKVgvACTIvKT8CjaQu9d0dERkZsWIZngX/4NVSjc01WHmln4F4y/zyBdYn/Z2V0Zw28cISx+lB4qxRmqTe7gbg==", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "13.5.9", - "tslib": "^2.8.0", - "ws": "^8.18.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rx-state/core": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@rx-state/core/-/core-0.1.4.tgz", - "integrity": "sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==", - "license": "MIT", - "peerDependencies": { - "rxjs": ">=7" - } - }, - "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", - "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.9.0", - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", - "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.8.0", - "@scure/base": "~1.2.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/sr25519": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@scure/sr25519/-/sr25519-0.3.0.tgz", - "integrity": "sha512-SKsinX2sImunfcsH3seGrwH/OayBwwaJqVN8J1cJBNRCfbBq5q0jyTKGa9PcW1HWv9vXT6Yuq41JsxFLvF59ew==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~2.0.0", - "@noble/hashes": "~2.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/sr25519/node_modules/@noble/curves": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.0.1.tgz", - "integrity": "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "2.0.1" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@substrate/connect": { - "version": "0.8.11", - "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.8.11.tgz", - "integrity": "sha512-ofLs1PAO9AtDdPbdyTYj217Pe+lBfTLltdHDs3ds8no0BseoLeAGxpz1mHfi7zB4IxI3YyAiLjH6U8cw4pj4Nw==", - "license": "GPL-3.0-only", - "optional": true, - "dependencies": { - "@substrate/connect-extension-protocol": "^2.0.0", - "@substrate/connect-known-chains": "^1.1.5", - "@substrate/light-client-extension-helpers": "^1.0.0", - "smoldot": "2.0.26" - } - }, - "node_modules/@substrate/connect-extension-protocol": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.2.2.tgz", - "integrity": "sha512-t66jwrXA0s5Goq82ZtjagLNd7DPGCNjHeehRlE/gcJmJ+G56C0W+2plqOMRicJ8XGR1/YFnUSEqUFiSNbjGrAA==", - "license": "GPL-3.0-only", - "optional": true - }, - "node_modules/@substrate/connect-known-chains": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.10.3.tgz", - "integrity": "sha512-OJEZO1Pagtb6bNE3wCikc2wrmvEU5x7GxFFLqqbz1AJYYxSlrPCGu4N2og5YTExo4IcloNMQYFRkBGue0BKZ4w==", - "license": "GPL-3.0-only", - "optional": true - }, - "node_modules/@substrate/light-client-extension-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-1.0.0.tgz", - "integrity": "sha512-TdKlni1mBBZptOaeVrKnusMg/UBpWUORNDv5fdCaJklP4RJiFOzBCrzC+CyVI5kQzsXBisZ+2pXm+rIjS38kHg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@polkadot-api/json-rpc-provider": "^0.0.1", - "@polkadot-api/json-rpc-provider-proxy": "^0.1.0", - "@polkadot-api/observable-client": "^0.3.0", - "@polkadot-api/substrate-client": "^0.1.2", - "@substrate/connect-extension-protocol": "^2.0.0", - "@substrate/connect-known-chains": "^1.1.5", - "rxjs": "^7.8.1" - }, - "peerDependencies": { - "smoldot": "2.x" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "optional": true, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/json-rpc-provider": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz", - "integrity": "sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA==", - "license": "MIT", - "optional": true - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/json-rpc-provider-proxy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.1.0.tgz", - "integrity": "sha512-8GSFE5+EF73MCuLQm8tjrbCqlgclcHBSRaswvXziJ0ZW7iw3UEMsKkkKvELayWyBuOPa2T5i1nj6gFOeIsqvrg==", - "license": "MIT", - "optional": true - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/metadata-builders": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.3.2.tgz", - "integrity": "sha512-TKpfoT6vTb+513KDzMBTfCb/ORdgRnsS3TDFpOhAhZ08ikvK+hjHMt5plPiAX/OWkm1Wc9I3+K6W0hX5Ab7MVg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@polkadot-api/substrate-bindings": "0.6.0", - "@polkadot-api/utils": "0.1.0" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/observable-client": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@polkadot-api/observable-client/-/observable-client-0.3.2.tgz", - "integrity": "sha512-HGgqWgEutVyOBXoGOPp4+IAq6CNdK/3MfQJmhCJb8YaJiaK4W6aRGrdQuQSTPHfERHCARt9BrOmEvTXAT257Ug==", - "license": "MIT", - "optional": true, - "dependencies": { - "@polkadot-api/metadata-builders": "0.3.2", - "@polkadot-api/substrate-bindings": "0.6.0", - "@polkadot-api/utils": "0.1.0" - }, - "peerDependencies": { - "@polkadot-api/substrate-client": "0.1.4", - "rxjs": ">=7.8.0" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/substrate-bindings": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.6.0.tgz", - "integrity": "sha512-lGuhE74NA1/PqdN7fKFdE5C1gNYX357j1tWzdlPXI0kQ7h3kN0zfxNOpPUN7dIrPcOFZ6C0tRRVrBylXkI6xPw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@noble/hashes": "^1.3.1", - "@polkadot-api/utils": "0.1.0", - "@scure/base": "^1.1.1", - "scale-ts": "^1.6.0" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/substrate-client": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz", - "integrity": "sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A==", - "license": "MIT", - "optional": true, - "dependencies": { - "@polkadot-api/json-rpc-provider": "0.0.1", - "@polkadot-api/utils": "0.1.0" - } - }, - "node_modules/@substrate/light-client-extension-helpers/node_modules/@polkadot-api/utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.1.0.tgz", - "integrity": "sha512-MXzWZeuGxKizPx2Xf/47wx9sr/uxKw39bVJUptTJdsaQn/TGq+z310mHzf1RCGvC1diHM8f593KrnDgc9oNbJA==", - "license": "MIT", - "optional": true - }, - "node_modules/@substrate/ss58-registry": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.51.0.tgz", - "integrity": "sha512-TWDurLiPxndFgKjVavCniytBIw+t4ViOi7TYp9h/D0NMmkEc9klFTo+827eyEJ0lELpqO207Ey7uGxUa+BS1jQ==", - "license": "Apache-2.0" - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/bn.js/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/bn.js/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" - }, - "node_modules/@types/mocha": { - "version": "10.0.10", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", - "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/ws/node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/abitype": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.0.tgz", - "integrity": "sha512-fD3ROjckUrWsybaSor2AdWxzA0e/DSyV2dA4aYd7bd8orHsoJjl09fOgKfUkTDfk0BsDGBf4NBgu/c7JoS2Npw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3.22.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/bn.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", - "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "license": "ISC" - }, - "node_modules/bundle-require": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", - "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.18" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.1.tgz", - "integrity": "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz", - "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==", - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/detect-indent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.2.tgz", - "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/dotenv": { - "version": "17.2.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", - "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ethers": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", - "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/ethers/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/ethers/node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/ethers/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/ethers/node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.6", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.1", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.2.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fix-dts-default-cjs-exports": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", - "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", - "license": "MIT", - "dependencies": { - "magic-string": "^0.30.17", - "mlly": "^1.7.4", - "rollup": "^4.34.8" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/fs.promises.exists": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/fs.promises.exists/-/fs.promises.exists-1.1.4.tgz", - "integrity": "sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/fs.promises.exists?sponsor=1" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/index-to-position": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", - "license": "MIT", - "dependencies": { - "browser-stdout": "^1.3.1", - "chokidar": "^4.0.1", - "debug": "^4.3.5", - "diff": "^7.0.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^10.4.5", - "he": "^1.2.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^9.0.5", - "ms": "^2.1.3", - "picocolors": "^1.1.1", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^9.2.0", - "yargs": "^17.7.2", - "yargs-parser": "^21.1.1", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mock-socket": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", - "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nock": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", - "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/normalize-package-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", - "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^9.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", - "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", - "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.2.2", - "string-width": "^8.1.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ox/node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/polkadot-api": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/polkadot-api/-/polkadot-api-1.22.0.tgz", - "integrity": "sha512-uREBLroPbnJxBBQ+qSkKLF493qukX4PAg32iThlELrZdxfNNgro6nvWRdVmBv73tFHvf+nyWWHKTx1c57nbixg==", - "license": "MIT", - "dependencies": { - "@polkadot-api/cli": "0.16.3", - "@polkadot-api/ink-contracts": "0.4.3", - "@polkadot-api/json-rpc-provider": "0.0.4", - "@polkadot-api/known-chains": "0.9.15", - "@polkadot-api/logs-provider": "0.0.6", - "@polkadot-api/metadata-builders": "0.13.7", - "@polkadot-api/metadata-compatibility": "0.4.1", - "@polkadot-api/observable-client": "0.17.0", - "@polkadot-api/pjs-signer": "0.6.17", - "@polkadot-api/polkadot-sdk-compat": "2.3.3", - "@polkadot-api/polkadot-signer": "0.1.6", - "@polkadot-api/signer": "0.2.11", - "@polkadot-api/sm-provider": "0.1.14", - "@polkadot-api/smoldot": "0.3.14", - "@polkadot-api/substrate-bindings": "0.16.5", - "@polkadot-api/substrate-client": "0.4.7", - "@polkadot-api/utils": "0.2.0", - "@polkadot-api/ws-provider": "0.7.4", - "@rx-state/core": "^0.1.4" - }, - "bin": { - "papi": "bin/cli.mjs", - "polkadot-api": "bin/cli.mjs" - }, - "peerDependencies": { - "rxjs": ">=7.8.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/read-pkg": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.0.0.tgz", - "integrity": "sha512-A70UlgfNdKI5NSvTTfHzLQj7NJRpJ4mT5tGafkllJ4wh71oYuGm/pzphHcmW4s35iox56KSK721AihodoXSc/A==", - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.4", - "normalize-package-data": "^8.0.0", - "parse-json": "^8.3.0", - "type-fest": "^5.2.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.3.0.tgz", - "integrity": "sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==", - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", - "fsevents": "~2.3.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scale-ts": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/scale-ts/-/scale-ts-1.6.1.tgz", - "integrity": "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/smoldot": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.26.tgz", - "integrity": "sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig==", - "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", - "optional": true, - "dependencies": { - "ws": "^8.8.1" - } - }, - "node_modules/sort-keys": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.1.0.tgz", - "integrity": "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==", - "license": "MIT", - "dependencies": { - "is-plain-obj": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", - "license": "CC0-1.0" - }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/tagged-tag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tsc-prog": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tsc-prog/-/tsc-prog-2.3.0.tgz", - "integrity": "sha512-ycET2d75EgcX7y8EmG4KiZkLAwUzbY4xRhA6NU0uVbHkY4ZjrAAuzTMxXI85kOwATqPnBI5C/7y7rlpY0xdqHA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "typescript": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsup": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", - "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", - "license": "MIT", - "dependencies": { - "bundle-require": "^5.1.0", - "cac": "^6.7.14", - "chokidar": "^4.0.3", - "consola": "^3.4.0", - "debug": "^4.4.0", - "esbuild": "^0.25.0", - "fix-dts-default-cjs-exports": "^1.0.0", - "joycon": "^3.1.1", - "picocolors": "^1.1.1", - "postcss-load-config": "^6.0.1", - "resolve-from": "^5.0.0", - "rollup": "^4.34.8", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.35.0", - "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.11", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@microsoft/api-extractor": "^7.36.0", - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.5.0" - }, - "peerDependenciesMeta": { - "@microsoft/api-extractor": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/viem": { - "version": "2.23.4", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.4.tgz", - "integrity": "sha512-UQquuolKlS1w5H5e0Fd1KKoUlIPJryIEBzY5AUhGyV1ka+9O6+3uYVhUzj6RbvGK0PtsMKn2ddwPZFwjNDVU/A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/viem/node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32/node_modules/@noble/curves": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", - "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.2" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip32/node_modules/@noble/hashes": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", - "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/viem/node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/viem/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/workerpool": { - "version": "9.3.4", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", - "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", - "license": "Apache-2.0" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-json-file": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-6.0.0.tgz", - "integrity": "sha512-MNHcU3f9WxnNyR6MxsYSj64Jz0+dwIpisWKWq9gqLj/GwmA9INg3BZ3vt70/HB3GEwrnDQWr4RPrywnhNzmUFA==", - "license": "MIT", - "dependencies": { - "detect-indent": "^7.0.1", - "is-plain-obj": "^4.1.0", - "sort-keys": "^5.0.0", - "write-file-atomic": "^5.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/write-package": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/write-package/-/write-package-7.2.0.tgz", - "integrity": "sha512-uMQTubF/vcu+Wd0b5BGtDmiXePd/+44hUWQz2nZPbs92/BnxRo74tqs+hqDo12RLiEd+CXFKUwxvvIZvtt34Jw==", - "license": "MIT", - "dependencies": { - "deepmerge-ts": "^7.1.0", - "read-pkg": "^9.0.1", - "sort-keys": "^5.0.0", - "type-fest": "^4.23.0", - "write-json-file": "^6.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/write-package/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/write-package/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/write-package/node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/write-package/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/contract-tests/src/contracts/precompileWrapper.sol b/contract-tests/src/contracts/precompileWrapper.sol deleted file mode 100644 index 9f5fe242c1..0000000000 --- a/contract-tests/src/contracts/precompileWrapper.sol +++ /dev/null @@ -1,362 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Precompile addresses -address constant ISUBTENSOR_BALANCE_TRANSFER_ADDRESS = 0x0000000000000000000000000000000000000800; -address constant IMETAGRAPH_ADDRESS = 0x0000000000000000000000000000000000000802; -address constant ISUBNET_ADDRESS = 0x0000000000000000000000000000000000000803; -address constant INEURON_ADDRESS = 0x0000000000000000000000000000000000000804; -address constant ISTAKING_V2_ADDRESS = 0x0000000000000000000000000000000000000805; -address constant IUID_LOOKUP_ADDRESS = 0x0000000000000000000000000000000000000806; -address constant IALPHA_ADDRESS = 0x0000000000000000000000000000000000000808; -address constant ICROWDLOAN_ADDRESS = 0x0000000000000000000000000000000000000809; -address constant ILEASING_ADDRESS = 0x000000000000000000000000000000000000080a; -address constant IPROXY_ADDRESS = 0x000000000000000000000000000000000000080b; -address constant IADDRESS_MAPPING_ADDRESS = 0x000000000000000000000000000000000000080C; - -// Interface definitions -interface ISubtensorBalanceTransfer { - function transfer(bytes32 data) external payable; -} - -interface IMetagraph { - function getUidCount(uint16 netuid) external view returns (uint16); -} - -interface ISubnet { - function registerNetwork( - bytes32 hotkey, - string memory subnetName, - string memory githubRepo, - string memory subnetContact, - string memory subnetUrl, - string memory discord, - string memory description, - string memory additional - ) external payable; - function getServingRateLimit(uint16 netuid) external view returns (uint64); -} - -interface INeuron { - function burnedRegister(uint16 netuid, bytes32 hotkey) external payable; -} - -interface IStaking { - function addStake( - bytes32 hotkey, - uint256 amount, - uint256 netuid - ) external payable; - function removeStake( - bytes32 hotkey, - uint256 amount, - uint256 netuid - ) external payable; - function getTotalColdkeyStake( - bytes32 coldkey - ) external view returns (uint256); - function getTotalHotkeyStake( - bytes32 hotkey - ) external view returns (uint256); -} - -struct LookupItem { - uint16 uid; - uint64 block_associated; -} - -interface IUidLookup { - function uidLookup( - uint16 netuid, - address evm_address, - uint16 limit - ) external view returns (LookupItem[] memory); -} - -interface IAlpha { - function getAlphaPrice(uint16 netuid) external view returns (uint256); -} - -struct CrowdloanInfo { - bytes32 creator; - uint64 deposit; - uint64 min_contribution; - uint32 end; - uint64 cap; - bytes32 funds_account; - uint64 raised; - bool has_target_address; - bytes32 target_address; - bool finalized; - uint32 contributors_count; -} - -interface ICrowdloan { - function getCrowdloan( - uint32 crowdloanId - ) external view returns (CrowdloanInfo memory); - function getContribution( - uint32 crowdloanId, - bytes32 coldkey - ) external view returns (uint64); - function create( - uint64 deposit, - uint64 minContribution, - uint64 cap, - uint32 end, - address targetAddress - ) external payable; -} - -struct LeaseInfo { - bytes32 beneficiary; - bytes32 coldkey; - bytes32 hotkey; - uint8 emissions_share; - bool has_end_block; - uint32 end_block; - uint16 netuid; - uint64 cost; -} - -interface ILeasing { - function getContributorShare( - uint32 leaseId, - bytes32 contributor - ) external view returns (uint128, uint128); - function createLeaseCrowdloan( - uint64 crowdloanDeposit, - uint64 crowdloanMinContribution, - uint64 crowdloanCap, - uint32 crowdloanEnd, - uint8 leasingEmissionsShare, - bool hasLeasingEndBlock, - uint32 leasingEndBlock - ) external payable; -} - -interface IProxy { - struct ProxyInfo { - bytes32 delegate; - uint256 proxy_type; - uint256 delay; - } - - function addProxy( - bytes32 delegate, - uint8 proxy_type, - uint32 delay - ) external; - function proxyCall( - bytes32 real, - uint8[] memory force_proxy_type, - uint8[] memory call - ) external; - function getProxies( - bytes32 account - ) external view returns (ProxyInfo[] memory); -} - -interface IAddressMapping { - function addressMapping( - address target_address - ) external view returns (bytes32); -} - -/** - * @title PrecompileWrapper - * @dev A wrapper contract that calls all precompile functions directly - * instead of using low-level calls like address.call() - */ -contract PrecompileWrapper { - ISubtensorBalanceTransfer public constant balanceTransfer = - ISubtensorBalanceTransfer(ISUBTENSOR_BALANCE_TRANSFER_ADDRESS); - IMetagraph public constant metagraph = IMetagraph(IMETAGRAPH_ADDRESS); - ISubnet public constant subnet = ISubnet(ISUBNET_ADDRESS); - INeuron public constant neuron = INeuron(INEURON_ADDRESS); - IStaking public constant staking = IStaking(ISTAKING_V2_ADDRESS); - IUidLookup public constant uidLookupPrecompile = - IUidLookup(IUID_LOOKUP_ADDRESS); - IAlpha public constant alpha = IAlpha(IALPHA_ADDRESS); - ICrowdloan public constant crowdloan = ICrowdloan(ICROWDLOAN_ADDRESS); - ILeasing public constant leasing = ILeasing(ILEASING_ADDRESS); - IProxy public constant proxy = IProxy(IPROXY_ADDRESS); - IAddressMapping public constant addressMappingPrecompile = - IAddressMapping(IADDRESS_MAPPING_ADDRESS); - - // ============ SubtensorBalanceTransfer Functions ============ - function transfer(bytes32 data) external payable { - balanceTransfer.transfer{value: msg.value}(data); - } - - // ============ Metagraph Functions ============ - - function getUidCount(uint16 netuid) external view returns (uint16) { - return metagraph.getUidCount(netuid); - } - - // ============ Subnet Functions ============ - - function registerNetworkWithDetails( - bytes32 hotkey, - string memory subnetName, - string memory githubRepo, - string memory subnetContact, - string memory subnetUrl, - string memory discord, - string memory description, - string memory additional - ) external payable { - subnet.registerNetwork( - hotkey, - subnetName, - githubRepo, - subnetContact, - subnetUrl, - discord, - description, - additional - ); - } - - function getServingRateLimit(uint16 netuid) external view returns (uint64) { - return subnet.getServingRateLimit(netuid); - } - - // ============ Neuron Functions ============ - - function burnedRegister(uint16 netuid, bytes32 hotkey) external payable { - neuron.burnedRegister{value: msg.value}(netuid, hotkey); - } - - // ============ Staking Functions ============ - function addStake( - bytes32 hotkey, - uint256 amount, - uint256 netuid - ) external payable { - staking.addStake(hotkey, amount, netuid); - } - - function removeStake( - bytes32 hotkey, - uint256 amount, - uint256 netuid - ) external payable { - staking.removeStake(hotkey, amount, netuid); - } - - function getTotalColdkeyStake( - bytes32 coldkey - ) external view returns (uint256) { - return staking.getTotalColdkeyStake(coldkey); - } - - function getTotalHotkeyStake( - bytes32 hotkey - ) external view returns (uint256) { - return staking.getTotalHotkeyStake(hotkey); - } - - // ============ Alpha Functions ============ - - function getAlphaPrice(uint16 netuid) external view returns (uint256) { - return alpha.getAlphaPrice(netuid); - } - - // ============ Address Mapping Functions ============ - - function addressMapping( - address target_address - ) external view returns (bytes32) { - return addressMappingPrecompile.addressMapping(target_address); - } - - // ============ Proxy Functions ============ - - function proxyCall( - bytes32 real, - uint8[] memory force_proxy_type, - uint8[] memory call - ) external { - proxy.proxyCall(real, force_proxy_type, call); - } - - function addProxy( - bytes32 delegate, - uint8 proxy_type, - uint32 delay - ) external { - proxy.addProxy(delegate, proxy_type, delay); - } - - function getProxies( - bytes32 account - ) external view returns (IProxy.ProxyInfo[] memory) { - return proxy.getProxies(account); - } - - // ============ UID Lookup Functions ============ - - function uidLookup( - uint16 netuid, - address evm_address, - uint16 limit - ) external view returns (LookupItem[] memory) { - return uidLookupPrecompile.uidLookup(netuid, evm_address, limit); - } - - // ============ Crowdloan Functions ============ - - function getCrowdloan( - uint32 crowdloanId - ) external view returns (CrowdloanInfo memory) { - return crowdloan.getCrowdloan(crowdloanId); - } - - function getContribution( - uint32 crowdloanId, - bytes32 coldkey - ) external view returns (uint64) { - return crowdloan.getContribution(crowdloanId, coldkey); - } - - function createCrowdloan( - uint64 deposit, - uint64 minContribution, - uint64 cap, - uint32 end, - address targetAddress - ) external payable { - crowdloan.create(deposit, minContribution, cap, end, targetAddress); - } - - // ============ Leasing Functions ============ - - function getContributorShare( - uint32 leaseId, - bytes32 contributor - ) external view returns (uint128, uint128) { - return leasing.getContributorShare(leaseId, contributor); - } - - function createLeaseCrowdloan( - uint64 crowdloanDeposit, - uint64 crowdloanMinContribution, - uint64 crowdloanCap, - uint32 crowdloanEnd, - uint8 leasingEmissionsShare, - bool hasLeasingEndBlock, - uint32 leasingEndBlock - ) external payable { - leasing.createLeaseCrowdloan( - crowdloanDeposit, - crowdloanMinContribution, - crowdloanCap, - crowdloanEnd, - leasingEmissionsShare, - hasLeasingEndBlock, - leasingEndBlock - ); - } -} diff --git a/contract-tests/src/contracts/precompileWrapper.ts b/contract-tests/src/contracts/precompileWrapper.ts deleted file mode 100644 index 9916b735e9..0000000000 --- a/contract-tests/src/contracts/precompileWrapper.ts +++ /dev/null @@ -1,714 +0,0 @@ -export const PRECOMPILE_WRAPPER_ABI = [ - { - "inputs": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "proxy_type", - "type": "uint8" - }, - { - "internalType": "uint32", - "name": "delay", - "type": "uint32" - } - ], - "name": "addProxy", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "addStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "target_address", - "type": "address" - } - ], - "name": "addressMapping", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "addressMappingPrecompile", - "outputs": [ - { - "internalType": "contract IAddressMapping", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "alpha", - "outputs": [ - { - "internalType": "contract IAlpha", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "balanceTransfer", - "outputs": [ - { - "internalType": "contract ISubtensorBalanceTransfer", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - }, - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - } - ], - "name": "burnedRegister", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "deposit", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "minContribution", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "cap", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "end", - "type": "uint32" - }, - { - "internalType": "address", - "name": "targetAddress", - "type": "address" - } - ], - "name": "createCrowdloan", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint64", - "name": "crowdloanDeposit", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanMinContribution", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "crowdloanCap", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "crowdloanEnd", - "type": "uint32" - }, - { - "internalType": "uint8", - "name": "leasingEmissionsShare", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "hasLeasingEndBlock", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "leasingEndBlock", - "type": "uint32" - } - ], - "name": "createLeaseCrowdloan", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "crowdloan", - "outputs": [ - { - "internalType": "contract ICrowdloan", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getAlphaPrice", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint32", - "name": "crowdloanId", - "type": "uint32" - }, - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - } - ], - "name": "getContribution", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint32", - "name": "leaseId", - "type": "uint32" - }, - { - "internalType": "bytes32", - "name": "contributor", - "type": "bytes32" - } - ], - "name": "getContributorShare", - "outputs": [ - { - "internalType": "uint128", - "name": "", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint32", - "name": "crowdloanId", - "type": "uint32" - } - ], - "name": "getCrowdloan", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "creator", - "type": "bytes32" - }, - { - "internalType": "uint64", - "name": "deposit", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "min_contribution", - "type": "uint64" - }, - { - "internalType": "uint32", - "name": "end", - "type": "uint32" - }, - { - "internalType": "uint64", - "name": "cap", - "type": "uint64" - }, - { - "internalType": "bytes32", - "name": "funds_account", - "type": "bytes32" - }, - { - "internalType": "uint64", - "name": "raised", - "type": "uint64" - }, - { - "internalType": "bool", - "name": "has_target_address", - "type": "bool" - }, - { - "internalType": "bytes32", - "name": "target_address", - "type": "bytes32" - }, - { - "internalType": "bool", - "name": "finalized", - "type": "bool" - }, - { - "internalType": "uint32", - "name": "contributors_count", - "type": "uint32" - } - ], - "internalType": "struct CrowdloanInfo", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "account", - "type": "bytes32" - } - ], - "name": "getProxies", - "outputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "proxy_type", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "delay", - "type": "uint256" - } - ], - "internalType": "struct IProxy.ProxyInfo[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getServingRateLimit", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - } - ], - "name": "getTotalColdkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - } - ], - "name": "getTotalHotkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getUidCount", - "outputs": [ - { - "internalType": "uint16", - "name": "", - "type": "uint16" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "leasing", - "outputs": [ - { - "internalType": "contract ILeasing", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "metagraph", - "outputs": [ - { - "internalType": "contract IMetagraph", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "neuron", - "outputs": [ - { - "internalType": "contract INeuron", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "proxy", - "outputs": [ - { - "internalType": "contract IProxy", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "real", - "type": "bytes32" - }, - { - "internalType": "uint8[]", - "name": "force_proxy_type", - "type": "uint8[]" - }, - { - "internalType": "uint8[]", - "name": "call", - "type": "uint8[]" - } - ], - "name": "proxyCall", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "subnetName", - "type": "string" - }, - { - "internalType": "string", - "name": "githubRepo", - "type": "string" - }, - { - "internalType": "string", - "name": "subnetContact", - "type": "string" - }, - { - "internalType": "string", - "name": "subnetUrl", - "type": "string" - }, - { - "internalType": "string", - "name": "discord", - "type": "string" - }, - { - "internalType": "string", - "name": "description", - "type": "string" - }, - { - "internalType": "string", - "name": "additional", - "type": "string" - } - ], - "name": "registerNetworkWithDetails", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "removeStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "staking", - "outputs": [ - { - "internalType": "contract IStaking", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "subnet", - "outputs": [ - { - "internalType": "contract ISubnet", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "data", - "type": "bytes32" - } - ], - "name": "transfer", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - }, - { - "internalType": "address", - "name": "evm_address", - "type": "address" - }, - { - "internalType": "uint16", - "name": "limit", - "type": "uint16" - } - ], - "name": "uidLookup", - "outputs": [ - { - "components": [ - { - "internalType": "uint16", - "name": "uid", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "block_associated", - "type": "uint64" - } - ], - "internalType": "struct LookupItem[]", - "name": "", - "type": "tuple[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "uidLookupPrecompile", - "outputs": [ - { - "internalType": "contract IUidLookup", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - } -]; - -export const PRECOMPILE_WRAPPER_BYTECODE = "6080604052348015600e575f5ffd5b50612bb98061001c5f395ff3fe6080604052600436106101d6575f3560e01c80637d691e3011610101578063b1f789ef11610094578063d75e3e0d11610063578063d75e3e0d146106a9578063db1d0fd5146106d3578063ec556889146106fd578063fc6679fb14610727576101d6565b8063b1f789ef146105fd578063bfe252a214610639578063caf2ebf214610663578063cd6f4eb11461068d576101d6565b80639f246f6f116100d05780639f246f6f14610551578063a21762761461058d578063ac3166bf146105b7578063afed65f9146105e1576101d6565b80637d691e30146104815780638bba466c1461049d57806394e3ac6f146104d9578063998538c414610515576101d6565b80634c378a96116101795780635e25f3f8116101485780635e25f3f8146103d157806369e38bc3146103ed57806371214e27146104295780637444dadc14610445576101d6565b80634c378a96146103175780634cf088d9146103415780635b53ddde1461036b5780635b7210c514610395576101d6565b80631f193572116101b55780631f193572146102665780631fc9b141146102a25780633175bd98146102be5780634054ecca146102fb576101d6565b80620ae759146101da5780630494cd9a146102025780630cadeda51461023e575b5f5ffd5b3480156101e5575f5ffd5b5061020060048036038101906101fb91906113ab565b610751565b005b34801561020d575f5ffd5b506102286004803603810190610223919061148d565b6107c1565b60405161023591906114c7565b60405180910390f35b348015610249575f5ffd5b50610264600480360381019061025f9190611519565b610843565b005b348015610271575f5ffd5b5061028c600480360381019061028791906115a0565b6108b4565b60405161029991906115da565b60405180910390f35b6102bc60048036038101906102b79190611626565b610936565b005b3480156102c9575f5ffd5b506102e460048036038101906102df9190611676565b6109a7565b6040516102f29291906116de565b60405180910390f35b61031560048036038101906103109190611705565b610a2f565b005b348015610322575f5ffd5b5061032b610a9d565b604051610338919061179e565b60405180910390f35b34801561034c575f5ffd5b50610355610aa3565b60405161036291906117d7565b60405180910390f35b348015610376575f5ffd5b5061037f610aa9565b60405161038c9190611810565b60405180910390f35b3480156103a0575f5ffd5b506103bb60048036038101906103b69190611676565b610aaf565b6040516103c8919061184b565b60405180910390f35b6103eb60048036038101906103e69190611914565b610b34565b005b3480156103f8575f5ffd5b50610413600480360381019061040e91906115a0565b610bb4565b6040516104209190611a98565b60405180910390f35b610443600480360381019061043e9190611adb565b610c36565b005b348015610450575f5ffd5b5061046b600480360381019061046691906115a0565b610cad565b604051610478919061184b565b60405180910390f35b61049b60048036038101906104969190611626565b610d2f565b005b3480156104a8575f5ffd5b506104c360048036038101906104be9190611b52565b610da0565b6040516104d09190611ca3565b60405180910390f35b3480156104e4575f5ffd5b506104ff60048036038101906104fa9190611cbd565b610e2a565b60405161050c9190611ddf565b60405180910390f35b348015610520575f5ffd5b5061053b60048036038101906105369190611cbd565b610eb0565b6040516105489190611a98565b60405180910390f35b34801561055c575f5ffd5b5061057760048036038101906105729190611cbd565b610f32565b6040516105849190611a98565b60405180910390f35b348015610598575f5ffd5b506105a1610fb4565b6040516105ae9190611e1f565b60405180910390f35b3480156105c2575f5ffd5b506105cb610fba565b6040516105d89190611e58565b60405180910390f35b6105fb60048036038101906105f69190611e9b565b610fc0565b005b348015610608575f5ffd5b50610623600480360381019061061e9190611f38565b61103d565b604051610630919061206c565b60405180910390f35b348015610644575f5ffd5b5061064d6110c9565b60405161065a91906120ac565b60405180910390f35b34801561066e575f5ffd5b506106776110cf565b60405161068491906120e5565b60405180910390f35b6106a760048036038101906106a29190611cbd565b6110d5565b005b3480156106b4575f5ffd5b506106bd611142565b6040516106ca919061211e565b60405180910390f35b3480156106de575f5ffd5b506106e7611148565b6040516106f49190612157565b60405180910390f35b348015610708575f5ffd5b5061071161114e565b60405161071e9190612190565b60405180910390f35b348015610732575f5ffd5b5061073b611154565b60405161074891906121c9565b60405180910390f35b61080b73ffffffffffffffffffffffffffffffffffffffff16620ae7598484846040518463ffffffff1660e01b815260040161078f93929190612299565b5f604051808303815f87803b1580156107a6575f5ffd5b505af11580156107b8573d5f5f3e3d5ffd5b50505050505050565b5f61080c73ffffffffffffffffffffffffffffffffffffffff16630494cd9a836040518263ffffffff1660e01b81526004016107fd91906122eb565b602060405180830381865afa158015610818573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061083c9190612318565b9050919050565b61080b73ffffffffffffffffffffffffffffffffffffffff16630cadeda58484846040518463ffffffff1660e01b815260040161088293929190612361565b5f604051808303815f87803b158015610899575f5ffd5b505af11580156108ab573d5f5f3e3d5ffd5b50505050505050565b5f61080273ffffffffffffffffffffffffffffffffffffffff16631f193572836040518263ffffffff1660e01b81526004016108f091906115da565b602060405180830381865afa15801561090b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061092f91906123aa565b9050919050565b61080573ffffffffffffffffffffffffffffffffffffffff16631fc9b1418484846040518463ffffffff1660e01b8152600401610975939291906123d5565b5f604051808303815f87803b15801561098c575f5ffd5b505af115801561099e573d5f5f3e3d5ffd5b50505050505050565b5f5f61080a73ffffffffffffffffffffffffffffffffffffffff16633175bd9885856040518363ffffffff1660e01b81526004016109e692919061240a565b6040805180830381865afa158015610a00573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a24919061245b565b915091509250929050565b61080473ffffffffffffffffffffffffffffffffffffffff16634054ecca83836040518363ffffffff1660e01b8152600401610a6c929190612499565b5f604051808303815f87803b158015610a83575f5ffd5b505af1158015610a95573d5f5f3e3d5ffd5b505050505050565b61080481565b61080581565b61080a81565b5f61080973ffffffffffffffffffffffffffffffffffffffff16635b7210c584846040518363ffffffff1660e01b8152600401610aed92919061240a565b602060405180830381865afa158015610b08573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b2c91906124d4565b905092915050565b61080373ffffffffffffffffffffffffffffffffffffffff16631cf98c6b89898989898989896040518963ffffffff1660e01b8152600401610b7d98979695949392919061255f565b5f604051808303815f87803b158015610b94575f5ffd5b505af1158015610ba6573d5f5f3e3d5ffd5b505050505050505050505050565b5f61080873ffffffffffffffffffffffffffffffffffffffff166369e38bc3836040518263ffffffff1660e01b8152600401610bf091906115da565b602060405180830381865afa158015610c0b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c2f9190612620565b9050919050565b61080973ffffffffffffffffffffffffffffffffffffffff1663127e1adb86868686866040518663ffffffff1660e01b8152600401610c7995949392919061264b565b5f604051808303815f87803b158015610c90575f5ffd5b505af1158015610ca2573d5f5f3e3d5ffd5b505050505050505050565b5f61080373ffffffffffffffffffffffffffffffffffffffff16637444dadc836040518263ffffffff1660e01b8152600401610ce991906115da565b602060405180830381865afa158015610d04573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d2891906124d4565b9050919050565b61080573ffffffffffffffffffffffffffffffffffffffff16637d691e308484846040518463ffffffff1660e01b8152600401610d6e939291906123d5565b5f604051808303815f87803b158015610d85575f5ffd5b505af1158015610d97573d5f5f3e3d5ffd5b50505050505050565b610da861115a565b61080973ffffffffffffffffffffffffffffffffffffffff16638bba466c836040518263ffffffff1660e01b8152600401610de3919061269c565b61016060405180830381865afa158015610dff573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e2391906127ea565b9050919050565b606061080b73ffffffffffffffffffffffffffffffffffffffff166394e3ac6f836040518263ffffffff1660e01b8152600401610e6791906114c7565b5f60405180830381865afa158015610e81573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610ea99190612937565b9050919050565b5f61080573ffffffffffffffffffffffffffffffffffffffff1663998538c4836040518263ffffffff1660e01b8152600401610eec91906114c7565b602060405180830381865afa158015610f07573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f2b9190612620565b9050919050565b5f61080573ffffffffffffffffffffffffffffffffffffffff16639f246f6f836040518263ffffffff1660e01b8152600401610f6e91906114c7565b602060405180830381865afa158015610f89573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610fad9190612620565b9050919050565b61080681565b61080c81565b61080a73ffffffffffffffffffffffffffffffffffffffff1663afed65f9888888888888886040518863ffffffff1660e01b8152600401611007979695949392919061298d565b5f604051808303815f87803b15801561101e575f5ffd5b505af1158015611030573d5f5f3e3d5ffd5b5050505050505050505050565b606061080673ffffffffffffffffffffffffffffffffffffffff1663b1f789ef8585856040518463ffffffff1660e01b815260040161107e939291906129fa565b5f60405180830381865afa158015611098573d5f5f3e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906110c09190612b3c565b90509392505050565b61080981565b61080381565b61080073ffffffffffffffffffffffffffffffffffffffff1663cd6f4eb134836040518363ffffffff1660e01b815260040161111191906114c7565b5f604051808303818588803b158015611128575f5ffd5b505af115801561113a573d5f5f3e3d5ffd5b505050505050565b61080081565b61080881565b61080b81565b61080281565b6040518061016001604052805f81526020015f67ffffffffffffffff1681526020015f67ffffffffffffffff1681526020015f63ffffffff1681526020015f67ffffffffffffffff1681526020015f81526020015f67ffffffffffffffff1681526020015f151581526020015f81526020015f151581526020015f63ffffffff1681525090565b5f604051905090565b5f5ffd5b5f5ffd5b5f819050919050565b611204816111f2565b811461120e575f5ffd5b50565b5f8135905061121f816111fb565b92915050565b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61126f82611229565b810181811067ffffffffffffffff8211171561128e5761128d611239565b5b80604052505050565b5f6112a06111e1565b90506112ac8282611266565b919050565b5f67ffffffffffffffff8211156112cb576112ca611239565b5b602082029050602081019050919050565b5f5ffd5b5f60ff82169050919050565b6112f5816112e0565b81146112ff575f5ffd5b50565b5f81359050611310816112ec565b92915050565b5f611328611323846112b1565b611297565b9050808382526020820190506020840283018581111561134b5761134a6112dc565b5b835b8181101561137457806113608882611302565b84526020840193505060208101905061134d565b5050509392505050565b5f82601f83011261139257611391611225565b5b81356113a2848260208601611316565b91505092915050565b5f5f5f606084860312156113c2576113c16111ea565b5b5f6113cf86828701611211565b935050602084013567ffffffffffffffff8111156113f0576113ef6111ee565b5b6113fc8682870161137e565b925050604084013567ffffffffffffffff81111561141d5761141c6111ee565b5b6114298682870161137e565b9150509250925092565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61145c82611433565b9050919050565b61146c81611452565b8114611476575f5ffd5b50565b5f8135905061148781611463565b92915050565b5f602082840312156114a2576114a16111ea565b5b5f6114af84828501611479565b91505092915050565b6114c1816111f2565b82525050565b5f6020820190506114da5f8301846114b8565b92915050565b5f63ffffffff82169050919050565b6114f8816114e0565b8114611502575f5ffd5b50565b5f81359050611513816114ef565b92915050565b5f5f5f606084860312156115305761152f6111ea565b5b5f61153d86828701611211565b935050602061154e86828701611302565b925050604061155f86828701611505565b9150509250925092565b5f61ffff82169050919050565b61157f81611569565b8114611589575f5ffd5b50565b5f8135905061159a81611576565b92915050565b5f602082840312156115b5576115b46111ea565b5b5f6115c28482850161158c565b91505092915050565b6115d481611569565b82525050565b5f6020820190506115ed5f8301846115cb565b92915050565b5f819050919050565b611605816115f3565b811461160f575f5ffd5b50565b5f81359050611620816115fc565b92915050565b5f5f5f6060848603121561163d5761163c6111ea565b5b5f61164a86828701611211565b935050602061165b86828701611612565b925050604061166c86828701611612565b9150509250925092565b5f5f6040838503121561168c5761168b6111ea565b5b5f61169985828601611505565b92505060206116aa85828601611211565b9150509250929050565b5f6fffffffffffffffffffffffffffffffff82169050919050565b6116d8816116b4565b82525050565b5f6040820190506116f15f8301856116cf565b6116fe60208301846116cf565b9392505050565b5f5f6040838503121561171b5761171a6111ea565b5b5f6117288582860161158c565b925050602061173985828601611211565b9150509250929050565b5f819050919050565b5f61176661176161175c84611433565b611743565b611433565b9050919050565b5f6117778261174c565b9050919050565b5f6117888261176d565b9050919050565b6117988161177e565b82525050565b5f6020820190506117b15f83018461178f565b92915050565b5f6117c18261176d565b9050919050565b6117d1816117b7565b82525050565b5f6020820190506117ea5f8301846117c8565b92915050565b5f6117fa8261176d565b9050919050565b61180a816117f0565b82525050565b5f6020820190506118235f830184611801565b92915050565b5f67ffffffffffffffff82169050919050565b61184581611829565b82525050565b5f60208201905061185e5f83018461183c565b92915050565b5f5ffd5b5f67ffffffffffffffff82111561188257611881611239565b5b61188b82611229565b9050602081019050919050565b828183375f83830152505050565b5f6118b86118b384611868565b611297565b9050828152602081018484840111156118d4576118d3611864565b5b6118df848285611898565b509392505050565b5f82601f8301126118fb576118fa611225565b5b813561190b8482602086016118a6565b91505092915050565b5f5f5f5f5f5f5f5f610100898b031215611931576119306111ea565b5b5f61193e8b828c01611211565b985050602089013567ffffffffffffffff81111561195f5761195e6111ee565b5b61196b8b828c016118e7565b975050604089013567ffffffffffffffff81111561198c5761198b6111ee565b5b6119988b828c016118e7565b965050606089013567ffffffffffffffff8111156119b9576119b86111ee565b5b6119c58b828c016118e7565b955050608089013567ffffffffffffffff8111156119e6576119e56111ee565b5b6119f28b828c016118e7565b94505060a089013567ffffffffffffffff811115611a1357611a126111ee565b5b611a1f8b828c016118e7565b93505060c089013567ffffffffffffffff811115611a4057611a3f6111ee565b5b611a4c8b828c016118e7565b92505060e089013567ffffffffffffffff811115611a6d57611a6c6111ee565b5b611a798b828c016118e7565b9150509295985092959890939650565b611a92816115f3565b82525050565b5f602082019050611aab5f830184611a89565b92915050565b611aba81611829565b8114611ac4575f5ffd5b50565b5f81359050611ad581611ab1565b92915050565b5f5f5f5f5f60a08688031215611af457611af36111ea565b5b5f611b0188828901611ac7565b9550506020611b1288828901611ac7565b9450506040611b2388828901611ac7565b9350506060611b3488828901611505565b9250506080611b4588828901611479565b9150509295509295909350565b5f60208284031215611b6757611b666111ea565b5b5f611b7484828501611505565b91505092915050565b611b86816111f2565b82525050565b611b9581611829565b82525050565b611ba4816114e0565b82525050565b5f8115159050919050565b611bbe81611baa565b82525050565b61016082015f820151611bd95f850182611b7d565b506020820151611bec6020850182611b8c565b506040820151611bff6040850182611b8c565b506060820151611c126060850182611b9b565b506080820151611c256080850182611b8c565b5060a0820151611c3860a0850182611b7d565b5060c0820151611c4b60c0850182611b8c565b5060e0820151611c5e60e0850182611bb5565b50610100820151611c73610100850182611b7d565b50610120820151611c88610120850182611bb5565b50610140820151611c9d610140850182611b9b565b50505050565b5f61016082019050611cb75f830184611bc4565b92915050565b5f60208284031215611cd257611cd16111ea565b5b5f611cdf84828501611211565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b611d1a816115f3565b82525050565b606082015f820151611d345f850182611b7d565b506020820151611d476020850182611d11565b506040820151611d5a6040850182611d11565b50505050565b5f611d6b8383611d20565b60608301905092915050565b5f602082019050919050565b5f611d8d82611ce8565b611d978185611cf2565b9350611da283611d02565b805f5b83811015611dd2578151611db98882611d60565b9750611dc483611d77565b925050600181019050611da5565b5085935050505092915050565b5f6020820190508181035f830152611df78184611d83565b905092915050565b5f611e098261176d565b9050919050565b611e1981611dff565b82525050565b5f602082019050611e325f830184611e10565b92915050565b5f611e428261176d565b9050919050565b611e5281611e38565b82525050565b5f602082019050611e6b5f830184611e49565b92915050565b611e7a81611baa565b8114611e84575f5ffd5b50565b5f81359050611e9581611e71565b92915050565b5f5f5f5f5f5f5f60e0888a031215611eb657611eb56111ea565b5b5f611ec38a828b01611ac7565b9750506020611ed48a828b01611ac7565b9650506040611ee58a828b01611ac7565b9550506060611ef68a828b01611505565b9450506080611f078a828b01611302565b93505060a0611f188a828b01611e87565b92505060c0611f298a828b01611505565b91505092959891949750929550565b5f5f5f60608486031215611f4f57611f4e6111ea565b5b5f611f5c8682870161158c565b9350506020611f6d86828701611479565b9250506040611f7e8682870161158c565b9150509250925092565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b611fba81611569565b82525050565b604082015f820151611fd45f850182611fb1565b506020820151611fe76020850182611b8c565b50505050565b5f611ff88383611fc0565b60408301905092915050565b5f602082019050919050565b5f61201a82611f88565b6120248185611f92565b935061202f83611fa2565b805f5b8381101561205f5781516120468882611fed565b975061205183612004565b925050600181019050612032565b5085935050505092915050565b5f6020820190508181035f8301526120848184612010565b905092915050565b5f6120968261176d565b9050919050565b6120a68161208c565b82525050565b5f6020820190506120bf5f83018461209d565b92915050565b5f6120cf8261176d565b9050919050565b6120df816120c5565b82525050565b5f6020820190506120f85f8301846120d6565b92915050565b5f6121088261176d565b9050919050565b612118816120fe565b82525050565b5f6020820190506121315f83018461210f565b92915050565b5f6121418261176d565b9050919050565b61215181612137565b82525050565b5f60208201905061216a5f830184612148565b92915050565b5f61217a8261176d565b9050919050565b61218a81612170565b82525050565b5f6020820190506121a35f830184612181565b92915050565b5f6121b38261176d565b9050919050565b6121c3816121a9565b82525050565b5f6020820190506121dc5f8301846121ba565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b612214816112e0565b82525050565b5f612225838361220b565b60208301905092915050565b5f602082019050919050565b5f612247826121e2565b61225181856121ec565b935061225c836121fc565b805f5b8381101561228c578151612273888261221a565b975061227e83612231565b92505060018101905061225f565b5085935050505092915050565b5f6060820190506122ac5f8301866114b8565b81810360208301526122be818561223d565b905081810360408301526122d2818461223d565b9050949350505050565b6122e581611452565b82525050565b5f6020820190506122fe5f8301846122dc565b92915050565b5f81519050612312816111fb565b92915050565b5f6020828403121561232d5761232c6111ea565b5b5f61233a84828501612304565b91505092915050565b61234c816112e0565b82525050565b61235b816114e0565b82525050565b5f6060820190506123745f8301866114b8565b6123816020830185612343565b61238e6040830184612352565b949350505050565b5f815190506123a481611576565b92915050565b5f602082840312156123bf576123be6111ea565b5b5f6123cc84828501612396565b91505092915050565b5f6060820190506123e85f8301866114b8565b6123f56020830185611a89565b6124026040830184611a89565b949350505050565b5f60408201905061241d5f830185612352565b61242a60208301846114b8565b9392505050565b61243a816116b4565b8114612444575f5ffd5b50565b5f8151905061245581612431565b92915050565b5f5f60408385031215612471576124706111ea565b5b5f61247e85828601612447565b925050602061248f85828601612447565b9150509250929050565b5f6040820190506124ac5f8301856115cb565b6124b960208301846114b8565b9392505050565b5f815190506124ce81611ab1565b92915050565b5f602082840312156124e9576124e86111ea565b5b5f6124f6848285016124c0565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f612531826124ff565b61253b8185612509565b935061254b818560208601612519565b61255481611229565b840191505092915050565b5f610100820190506125735f83018b6114b8565b8181036020830152612585818a612527565b905081810360408301526125998189612527565b905081810360608301526125ad8188612527565b905081810360808301526125c18187612527565b905081810360a08301526125d58186612527565b905081810360c08301526125e98185612527565b905081810360e08301526125fd8184612527565b90509998505050505050505050565b5f8151905061261a816115fc565b92915050565b5f60208284031215612635576126346111ea565b5b5f6126428482850161260c565b91505092915050565b5f60a08201905061265e5f83018861183c565b61266b602083018761183c565b612678604083018661183c565b6126856060830185612352565b61269260808301846122dc565b9695505050505050565b5f6020820190506126af5f830184612352565b92915050565b5f5ffd5b5f815190506126c7816114ef565b92915050565b5f815190506126db81611e71565b92915050565b5f61016082840312156126f7576126f66126b5565b5b612702610160611297565b90505f61271184828501612304565b5f830152506020612724848285016124c0565b6020830152506040612738848285016124c0565b604083015250606061274c848285016126b9565b6060830152506080612760848285016124c0565b60808301525060a061277484828501612304565b60a08301525060c0612788848285016124c0565b60c08301525060e061279c848285016126cd565b60e0830152506101006127b184828501612304565b610100830152506101206127c7848285016126cd565b610120830152506101406127dd848285016126b9565b6101408301525092915050565b5f6101608284031215612800576127ff6111ea565b5b5f61280d848285016126e1565b91505092915050565b5f67ffffffffffffffff8211156128305761282f611239565b5b602082029050602081019050919050565b5f60608284031215612856576128556126b5565b5b6128606060611297565b90505f61286f84828501612304565b5f8301525060206128828482850161260c565b60208301525060406128968482850161260c565b60408301525092915050565b5f6128b46128af84612816565b611297565b905080838252602082019050606084028301858111156128d7576128d66112dc565b5b835b8181101561290057806128ec8882612841565b8452602084019350506060810190506128d9565b5050509392505050565b5f82601f83011261291e5761291d611225565b5b815161292e8482602086016128a2565b91505092915050565b5f6020828403121561294c5761294b6111ea565b5b5f82015167ffffffffffffffff811115612969576129686111ee565b5b6129758482850161290a565b91505092915050565b61298781611baa565b82525050565b5f60e0820190506129a05f83018a61183c565b6129ad602083018961183c565b6129ba604083018861183c565b6129c76060830187612352565b6129d46080830186612343565b6129e160a083018561297e565b6129ee60c0830184612352565b98975050505050505050565b5f606082019050612a0d5f8301866115cb565b612a1a60208301856122dc565b612a2760408301846115cb565b949350505050565b5f67ffffffffffffffff821115612a4957612a48611239565b5b602082029050602081019050919050565b5f60408284031215612a6f57612a6e6126b5565b5b612a796040611297565b90505f612a8884828501612396565b5f830152506020612a9b848285016124c0565b60208301525092915050565b5f612ab9612ab484612a2f565b611297565b90508083825260208201905060408402830185811115612adc57612adb6112dc565b5b835b81811015612b055780612af18882612a5a565b845260208401935050604081019050612ade565b5050509392505050565b5f82601f830112612b2357612b22611225565b5b8151612b33848260208601612aa7565b91505092915050565b5f60208284031215612b5157612b506111ea565b5b5f82015167ffffffffffffffff811115612b6e57612b6d6111ee565b5b612b7a84828501612b0f565b9150509291505056fea2646970667358221220768c64014d2253c661e44d07f480f7a203eb9e422f680d00272498325a4f6ad964736f6c634300081e0033"; diff --git a/contract-tests/src/contracts/votingPower.ts b/contract-tests/src/contracts/votingPower.ts deleted file mode 100644 index bbcc3ca6e6..0000000000 --- a/contract-tests/src/contracts/votingPower.ts +++ /dev/null @@ -1,104 +0,0 @@ -export const IVOTING_POWER_ADDRESS = "0x000000000000000000000000000000000000080d"; - -export const IVotingPowerABI = [ - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - }, - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - } - ], - "name": "getVotingPower", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "isVotingPowerTrackingEnabled", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getVotingPowerDisableAtBlock", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getVotingPowerEmaAlpha", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint16", - "name": "netuid", - "type": "uint16" - } - ], - "name": "getTotalVotingPower", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/contract-tests/src/subtensor.ts b/contract-tests/src/subtensor.ts index f5829c76aa..fab9e8cc10 100644 --- a/contract-tests/src/subtensor.ts +++ b/contract-tests/src/subtensor.ts @@ -1,20 +1,17 @@ import * as assert from "assert"; import { devnet, MultiAddress } from '@polkadot-api/descriptors'; -import { TypedApi, TxCallData, Binary, Enum, getTypedCodecs } from 'polkadot-api'; +import { TypedApi, TxCallData, Binary, Enum } from 'polkadot-api'; import { KeyPair } from "@polkadot-labs/hdkd-helpers" import { getAliceSigner, waitForTransactionCompletion, getSignerFromKeypair, waitForTransactionWithRetry } from './substrate' import { convertH160ToSS58, convertPublicKeyToSs58, ethAddressToH160 } from './address-utils' import { tao } from './balance-math' import internal from "stream"; -import { createCodec } from "scale-ts"; // create a new subnet and return netuid export async function addNewSubnetwork(api: TypedApi, hotkey: KeyPair, coldkey: KeyPair) { const alice = getAliceSigner() const totalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() - const defaultNetworkLastLockCost = await api.query.SubtensorModule.NetworkLastLockCost.getValue() - const rateLimit = await api.query.SubtensorModule.NetworkRateLimit.getValue() if (rateLimit !== BigInt(0)) { const internalCall = api.tx.AdminUtils.sudo_set_network_rate_limit({ rate_limit: BigInt(0) }) @@ -29,9 +26,6 @@ export async function addNewSubnetwork(api: TypedApi, hotkey: Key const newTotalNetworks = await api.query.SubtensorModule.TotalNetworks.getValue() // could create multiple subnetworks during retry, just return the first created one assert.ok(newTotalNetworks > totalNetworks) - - // reset network last lock cost to 0, to avoid the lock cost calculation error - await setNetworkLastLockCost(api, defaultNetworkLastLockCost) return totalNetworks } @@ -404,19 +398,4 @@ export async function sendWasmContractExtrinsic(api: TypedApi, co storage_deposit_limit: BigInt(1000000000) }) await waitForTransactionWithRetry(api, tx, signer) -} - -export async function setNetworkLastLockCost(api: TypedApi, defaultNetworkLastLockCost: bigint) { - const alice = getAliceSigner() - const key = await api.query.SubtensorModule.NetworkLastLockCost.getKey() - const codec = await getTypedCodecs(devnet); - const value = codec.query.SubtensorModule.NetworkLastLockCost.value.enc(defaultNetworkLastLockCost) - const internalCall = api.tx.System.set_storage({ - items: [[Binary.fromHex(key), Binary.fromBytes(value)]] - }) - const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) - await waitForTransactionWithRetry(api, tx, alice) - - const valueOnChain = await api.query.SubtensorModule.NetworkLastLockCost.getValue() - assert.equal(defaultNetworkLastLockCost, valueOnChain) -} \ No newline at end of file +} \ No newline at end of file diff --git a/contract-tests/test/precompileWrapper.direct-call.test.ts b/contract-tests/test/precompileWrapper.direct-call.test.ts deleted file mode 100644 index 5d63dfbb44..0000000000 --- a/contract-tests/test/precompileWrapper.direct-call.test.ts +++ /dev/null @@ -1,403 +0,0 @@ -import * as assert from "assert"; -import { getDevnetApi, getRandomSubstrateKeypair, getBalance, getSignerFromKeypair } from "../src/substrate"; -import { devnet } from "@polkadot-api/descriptors"; -import { TypedApi, Binary } from "polkadot-api"; -import { convertH160ToSS58, convertPublicKeyToSs58, convertH160ToPublicKey } from "../src/address-utils"; -import { tao, raoToEth } from "../src/balance-math"; -import { - forceSetBalanceToSs58Address, - addNewSubnetwork, - startCall, - disableWhiteListCheck, - forceSetBalanceToEthAddress, - -} from "../src/subtensor"; -import { ethers } from "ethers"; -import { generateRandomEthersWallet, getPublicClient } from "../src/utils"; -import { PRECOMPILE_WRAPPER_ABI, PRECOMPILE_WRAPPER_BYTECODE } from "../src/contracts/precompileWrapper"; -import { ETH_LOCAL_URL } from "../src/config"; -import { PublicClient } from "viem"; -import { IProxyABI, IPROXY_ADDRESS } from "../src/contracts/proxy" - -describe("PrecompileWrapper - Direct Call Tests", () => { - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - const wallet1 = generateRandomEthersWallet(); - const wallet2 = generateRandomEthersWallet(); - - let api: TypedApi; - let publicClient: PublicClient; - let wrapperContract: ethers.Contract; - let wrapperAddress: string; - let netuid: number; - - before(async () => { - api = await getDevnetApi(); - publicClient = await getPublicClient(ETH_LOCAL_URL); - await disableWhiteListCheck(api, true); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)); - await forceSetBalanceToEthAddress(api, wallet1.address); - await forceSetBalanceToEthAddress(api, wallet2.address); - await addNewSubnetwork(api, hotkey, coldkey); - netuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1; - await startCall(api, netuid, coldkey); - - const factory = new ethers.ContractFactory( - PRECOMPILE_WRAPPER_ABI, - PRECOMPILE_WRAPPER_BYTECODE, - wallet1 - ); - const deployContract = await factory.deploy(); - await deployContract.waitForDeployment(); - wrapperAddress = await deployContract.getAddress(); - await forceSetBalanceToEthAddress(api, wrapperAddress); - - console.log("Wrapper contract deployed at:", wrapperAddress); - console.log("Testing in subnet:", netuid); - - wrapperContract = new ethers.Contract(wrapperAddress, PRECOMPILE_WRAPPER_ABI, wallet1); - }); - - describe("Balance Transfer Precompile Direct Calls", () => { - it("Should transfer balance via wrapper", async () => { - const keypair = getRandomSubstrateKeypair(); - const transferAmount = raoToEth(tao(1)); - - // Transfer via wrapper - const transferTx = await wrapperContract.transfer(keypair.publicKey, { value: transferAmount.toString() }); - await transferTx.wait(); - - const balance = await getBalance(api, convertPublicKeyToSs58(keypair.publicKey)); - assert.ok(balance >= tao(1), "Balance should be transferred"); - }); - }); - - describe("Metagraph Precompile Direct Calls", () => { - it("Should get UID count via wrapper", async () => { - const uidCountViaWrapper = await wrapperContract.getUidCount(netuid); - assert.ok(uidCountViaWrapper !== undefined, "UID count should be not undefined"); - }); - }); - - describe("Subnet Precompile Direct Calls", () => { - it("Should get serving rate limit via wrapper", async () => { - const rateLimitViaWrapper = await wrapperContract.getServingRateLimit(netuid); - - assert.ok(rateLimitViaWrapper !== undefined, "Rate limit should be not undefined"); - }); - - it("Should register network with details via wrapper", async () => { - const newHotkey = getRandomSubstrateKeypair(); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(newHotkey.publicKey)); - - const totalNetworksBefore = await api.query.SubtensorModule.TotalNetworks.getValue(); - - const registerTx = await wrapperContract.registerNetworkWithDetails( - newHotkey.publicKey, - "Test Subnet", - "https://github.com/test/repo", - "test@example.com", - "https://test.example.com", - "test#1234", - "Test description", - "Additional info", - { value: raoToEth(tao(100)).toString() } - ); - await registerTx.wait(); - - const totalNetworksAfter = await api.query.SubtensorModule.TotalNetworks.getValue(); - const beforeValue = typeof totalNetworksBefore === 'bigint' ? totalNetworksBefore : BigInt(totalNetworksBefore); - assert.equal(totalNetworksAfter, beforeValue + BigInt(1), "Network should be registered"); - }); - }); - - describe("Neuron Precompile Direct Calls", () => { - it("Should register neuron via wrapper", async () => { - const newHotkey = getRandomSubstrateKeypair(); - const newColdkey = getRandomSubstrateKeypair(); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(newHotkey.publicKey)); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(newColdkey.publicKey)); - - // Use a reasonable burn amount (100 TAO) - const burnAmount = tao(100); - - const registerTx = await wrapperContract.burnedRegister( - netuid, - newHotkey.publicKey, - { value: raoToEth(burnAmount).toString() } - ); - await registerTx.wait(); - - const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertPublicKeyToSs58(newHotkey.publicKey)); - assert.ok(uid !== undefined, "Neuron should be registered"); - }); - }); - - describe("Staking Precompile Direct Calls", () => { - it("Should get total coldkey stake via wrapper", async () => { - const stakeViaWrapper = await wrapperContract.getTotalColdkeyStake(coldkey.publicKey); - assert.ok(stakeViaWrapper !== undefined, "Total coldkey stake should be not undefined"); - }); - - it("Should get total hotkey stake via wrapper", async () => { - const stakeViaWrapper = await wrapperContract.getTotalHotkeyStake(hotkey.publicKey); - assert.ok(stakeViaWrapper !== undefined, "Total hotkey stake should be not undefined"); - }); - - it("Should add stake via wrapper", async () => { - const stakeAmount = tao(2); - const stakeBefore = await api.query.SubtensorModule.Alpha.getValue( - convertPublicKeyToSs58(hotkey.publicKey), - convertH160ToSS58(wrapperAddress), - netuid - ); - - const addStakeTx = await wrapperContract.addStake( - hotkey.publicKey, - stakeAmount.toString(), - netuid, - { value: raoToEth(stakeAmount).toString() } - ); - await addStakeTx.wait(); - - const stakeAfter = await api.query.SubtensorModule.Alpha.getValue( - convertPublicKeyToSs58(hotkey.publicKey), - convertH160ToSS58(wrapperAddress), - netuid - ); - assert.ok(stakeAfter > stakeBefore, "Stake should be increased"); - }); - - it("Should remove stake via wrapper", async () => { - const removeAmount = tao(1); - const stakeBefore = await api.query.SubtensorModule.Alpha.getValue( - convertPublicKeyToSs58(hotkey.publicKey), - convertH160ToSS58(wrapperAddress), - netuid - ); - - const removeStakeTx = await wrapperContract.removeStake( - hotkey.publicKey, - removeAmount.toString(), - netuid - ); - await removeStakeTx.wait(); - - const stakeAfter = await api.query.SubtensorModule.Alpha.getValue( - convertPublicKeyToSs58(hotkey.publicKey), - convertH160ToSS58(wrapperAddress), - netuid - ); - assert.ok(stakeAfter < stakeBefore, "Stake should be decreased"); - }); - }); - - describe("UID Lookup Precompile Direct Calls", () => { - it("Should lookup UID via wrapper", async () => { - const evmAddress = wallet1.address; - const limit = 10; - const lookupViaWrapper = await wrapperContract.uidLookup(netuid, evmAddress, limit); - - assert.ok(Array.isArray(lookupViaWrapper), "Lookup should return an array"); - }); - }); - - describe("Alpha Precompile Direct Calls", () => { - it("Should get alpha price via wrapper", async () => { - const priceViaWrapper = await wrapperContract.getAlphaPrice(netuid); - assert.ok(priceViaWrapper !== undefined, "Alpha price should be not undefined"); - }); - }); - - describe("Crowdloan Precompile Direct Calls", () => { - it("Should get crowdloan via wrapper", async () => { - // First create a crowdloan via substrate - const nextId = await api.query.Crowdloan.NextCrowdloanId.getValue(); - const end = await api.query.System.Number.getValue() + 100; - const deposit = BigInt(15_000_000_000); // 15 TAO - const minContribution = BigInt(1_000_000_000); // 1 TAO - const cap = BigInt(100_000_000_000); // 100 TAO - - const signer = getSignerFromKeypair(coldkey); - await api.tx.Crowdloan.create({ - deposit, - min_contribution: minContribution, - cap, - end, - target_address: undefined, - call: api.tx.System.remark({ remark: Binary.fromText("test") }).decodedCall - }).signAndSubmit(signer); - - // Wait a bit for the transaction to be included - await new Promise(resolve => setTimeout(resolve, 2000)); - - const crowdloanViaWrapper = await wrapperContract.getCrowdloan(nextId); - - assert.ok(crowdloanViaWrapper !== undefined, "Crowdloan should be not undefined"); - }); - - it("Should get contribution via wrapper", async () => { - const nextId = await api.query.Crowdloan.NextCrowdloanId.getValue(); - const contributionViaWrapper = await wrapperContract.getContribution(nextId - 1, coldkey.publicKey); - - assert.ok(contributionViaWrapper !== undefined, "Contribution should be not undefined"); - }); - - it("Should create crowdloan via wrapper", async () => { - const deposit = BigInt(20_000_000_000); // 20 TAO - const minContribution = BigInt(2_000_000_000); // 2 TAO - const cap = BigInt(200_000_000_000); // 200 TAO - const end = Number(await api.query.System.Number.getValue()) + 100; - const targetAddress = wallet2.address; - - const nextIdBefore = await api.query.Crowdloan.NextCrowdloanId.getValue(); - - const createTx = await wrapperContract.createCrowdloan( - deposit.toString(), - minContribution.toString(), - cap.toString(), - end, - targetAddress, - { value: raoToEth(deposit).toString() } - ); - await createTx.wait(); - - const nextIdAfter = await api.query.Crowdloan.NextCrowdloanId.getValue(); - const beforeId = typeof nextIdBefore === 'bigint' ? nextIdBefore : BigInt(nextIdBefore); - assert.equal(nextIdAfter, beforeId + BigInt(1), "Crowdloan should be created"); - }); - }); - - - describe("Leasing Precompile Direct Calls", () => { - it("Should get contributor share via wrapper", async () => { - // First create a lease crowdloan - const nextCrowdloanId = await api.query.Crowdloan.NextCrowdloanId.getValue(); - const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO - const networkLastLockCost = await api.query.SubtensorModule.NetworkLastLockCost.getValue(); - const lockCostValue = typeof networkLastLockCost === 'bigint' ? networkLastLockCost : BigInt(networkLastLockCost); - const crowdloanCap = lockCostValue * BigInt(2); - const currentBlock = await api.query.System.Number.getValue(); - const crowdloanEnd = currentBlock + 100; - const leasingEmissionsShare = 15; - const leasingEndBlock = currentBlock + 300; - - const signer = getSignerFromKeypair(coldkey); - await api.tx.Crowdloan.create({ - deposit: crowdloanDeposit, - min_contribution: BigInt(1_000_000_000), - cap: crowdloanCap, - end: crowdloanEnd, - target_address: undefined, - call: api.tx.SubtensorModule.register_leased_network({ - emissions_share: leasingEmissionsShare, - end_block: leasingEndBlock, - }).decodedCall - }).signAndSubmit(signer); - - await new Promise(resolve => setTimeout(resolve, 2000)); - - const nextLeaseId = await api.query.SubtensorModule.NextSubnetLeaseId.getValue(); - - // Get contributor share - const shareViaWrapper = await wrapperContract.getContributorShare(nextLeaseId, coldkey.publicKey); - - assert.ok(shareViaWrapper !== undefined, "Share should be not undefined"); - - }); - - it("Should create lease crowdloan via wrapper", async () => { - const crowdloanDeposit = BigInt(100_000_000_000); // 100 TAO - const crowdloanMinContribution = BigInt(1_000_000_000); // 1 TAO - const networkLastLockCost = await api.query.SubtensorModule.NetworkLastLockCost.getValue(); - const lockCostValue = typeof networkLastLockCost === 'bigint' ? networkLastLockCost : BigInt(networkLastLockCost); - const crowdloanCap = lockCostValue * BigInt(2); - const currentBlock = await api.query.System.Number.getValue(); - const currentBlockValue = typeof currentBlock === 'bigint' ? Number(currentBlock) : currentBlock; - const crowdloanEnd = currentBlockValue + 100; - const leasingEmissionsShare = 15; - const hasLeasingEndBlock = true; - const leasingEndBlock = currentBlockValue + 300; - - const nextCrowdloanIdBefore = await api.query.Crowdloan.NextCrowdloanId.getValue(); - - const createTx = await wrapperContract.createLeaseCrowdloan( - crowdloanDeposit.toString(), - crowdloanMinContribution.toString(), - crowdloanCap.toString(), - crowdloanEnd, - leasingEmissionsShare, - hasLeasingEndBlock, - leasingEndBlock, - { value: raoToEth(crowdloanDeposit).toString() } - ); - await createTx.wait(); - - const nextCrowdloanIdAfter = await api.query.Crowdloan.NextCrowdloanId.getValue(); - const beforeId = typeof nextCrowdloanIdBefore === 'bigint' ? nextCrowdloanIdBefore : BigInt(nextCrowdloanIdBefore); - assert.equal(nextCrowdloanIdAfter, beforeId + BigInt(1), "Lease crowdloan should be created"); - }); - }); - - - describe("Proxy Precompile Direct Calls", () => { - it("Should get proxies via wrapper", async () => { - const accountKey = convertH160ToPublicKey(wallet1.address); - const proxiesViaWrapper = await wrapperContract.getProxies(accountKey); - - assert.ok(proxiesViaWrapper !== undefined, "Proxies should be not undefined"); - assert.ok(Array.isArray(proxiesViaWrapper), "Proxies should be an array"); - }); - it("Should add proxy via wrapper", async () => { - const delegate = getRandomSubstrateKeypair(); - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(delegate.publicKey)); - const delegateKey = delegate.publicKey; - const proxyType = 0; - const delay = 0; - - const proxiesBefore = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(wrapperAddress)); - - const addProxyTx = await wrapperContract.addProxy(delegateKey, proxyType, delay); - await addProxyTx.wait(); - - const proxiesAfter = await api.query.Proxy.Proxies.getValue(convertH160ToSS58(wrapperAddress)); - assert.ok(proxiesAfter[0].length > proxiesBefore[0].length, "Proxy should be added"); - }); - - it("Should proxy call via wrapper", async () => { - const proxyType = 0; - const delay = 0; - - const proxyContract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, wallet1); - const addProxyTx = await proxyContract.addProxy(convertH160ToPublicKey(wrapperAddress), proxyType, delay); - await addProxyTx.wait(); - - // Create a simple call (remark) - const remarkCall = api.tx.System.remark({ remark: Binary.fromText("") }); - - const callData = await remarkCall.getEncodedData(); - const data = callData.asBytes(); - - const proxyCallTx = await wrapperContract.proxyCall( - convertH160ToPublicKey(wallet1.address), - [proxyType], - [...data] - ); - await proxyCallTx.wait(); - - // Verify the call was executed (no error means success) - assert.ok(proxyCallTx, "Proxy call should succeed"); - }); - }); - - describe("Address Mapping Precompile Direct Calls", () => { - it("Should map address via wrapper", async () => { - const testAddress = wallet1.address; - const mappedViaWrapper = await wrapperContract.addressMapping(testAddress); - - assert.ok(mappedViaWrapper !== undefined, "Mapped address should be not undefined"); - assert.ok(mappedViaWrapper !== "0x0000000000000000000000000000000000000000000000000000000000000000", "Mapped address should not be zero"); - }); - }); -}); diff --git a/contract-tests/test/runtime.call.precompile.test.ts b/contract-tests/test/runtime.call.precompile.test.ts index 7bacc947fd..c409b25c8b 100644 --- a/contract-tests/test/runtime.call.precompile.test.ts +++ b/contract-tests/test/runtime.call.precompile.test.ts @@ -66,7 +66,7 @@ describe("Test the dispatch precompile", () => { it("Storage query only allow some pallets prefixed storage", async () => { const authorizedKeys = [ await api.query.SubtensorModule.TotalNetworks.getKey(), - await api.query.Swap.FeeRate.getKey(), + await api.query.Swap.AlphaSqrtPrice.getKey(), await api.query.Balances.TotalIssuance.getKey(), await api.query.Proxy.Announcements.getKey(), await api.query.Scheduler.Agenda.getKey(), diff --git a/contract-tests/test/subnet.precompile.hyperparameter.test.ts b/contract-tests/test/subnet.precompile.hyperparameter.test.ts index 75d361a77f..8598b45a81 100644 --- a/contract-tests/test/subnet.precompile.hyperparameter.test.ts +++ b/contract-tests/test/subnet.precompile.hyperparameter.test.ts @@ -2,13 +2,12 @@ import * as assert from "assert"; import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate" import { devnet } from "@polkadot-api/descriptors" -import { Binary, FixedSizeBinary, TypedApi, getTypedCodecs } from "polkadot-api"; +import { Binary, TypedApi, getTypedCodecs } from "polkadot-api"; import { convertH160ToSS58, convertPublicKeyToSs58 } from "../src/address-utils" import { generateRandomEthersWallet } from "../src/utils"; import { ISubnetABI, ISUBNET_ADDRESS } from "../src/contracts/subnet" import { ethers } from "ethers" import { disableAdminFreezeWindowAndOwnerHyperparamRateLimit, forceSetBalanceToEthAddress, forceSetBalanceToSs58Address } from "../src/subtensor" -import { blake2AsU8a } from "@polkadot/util-crypto" describe("Test the Subnet precompile contract", () => { // init eth part @@ -553,16 +552,16 @@ describe("Test the Subnet precompile contract", () => { const netuid = totalNetwork - 1; const coldkeySs58 = convertH160ToSS58(wallet.address) - const newColdkeyHash = FixedSizeBinary.fromBytes(blake2AsU8a(hotkey1.publicKey)) + const newColdkeySs58 = convertPublicKeyToSs58(hotkey1.publicKey) const currentBlock = await api.query.System.Number.getValue() const executionBlock = currentBlock + 10 const codec = await getTypedCodecs(devnet); - const valueBytes = codec.query.SubtensorModule.ColdkeySwapAnnouncements.value.enc([ + const valueBytes = codec.query.SubtensorModule.ColdkeySwapScheduled.value.enc([ executionBlock, - newColdkeyHash + newColdkeySs58, ]) - const key = await api.query.SubtensorModule.ColdkeySwapAnnouncements.getKey(coldkeySs58); + const key = await api.query.SubtensorModule.ColdkeySwapScheduled.getKey(coldkeySs58); // Use sudo + set_storage since the swap-scheduled check only exists in the tx extension. const setStorageCall = api.tx.System.set_storage({ @@ -571,9 +570,8 @@ describe("Test the Subnet precompile contract", () => { const sudoTx = api.tx.Sudo.sudo({ call: setStorageCall.decodedCall }) await waitForTransactionWithRetry(api, sudoTx, getAliceSigner()) - const storedValue = await api.query.SubtensorModule.ColdkeySwapAnnouncements.getValue(coldkeySs58) - assert.equal(storedValue?.[0], executionBlock) - assert.equal(storedValue?.[1].asHex(), newColdkeyHash.asHex()) + const storedValue = await api.query.SubtensorModule.ColdkeySwapScheduled.getValue(coldkeySs58) + assert.deepStrictEqual(storedValue, [executionBlock, newColdkeySs58]) await assert.rejects(async () => { const tx = await contract.setServingRateLimit(netuid, 100); diff --git a/contract-tests/test/transaction.replace.test.ts b/contract-tests/test/transaction.replace.test.ts deleted file mode 100644 index afb95ed9d5..0000000000 --- a/contract-tests/test/transaction.replace.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as assert from "assert"; - -import { getDevnetApi, getRandomSubstrateSigner, } from "../src/substrate" -import { getPublicClient } from "../src/utils"; -import { ETH_LOCAL_URL, IBALANCETRANSFER_ADDRESS, IBalanceTransferABI } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { PublicClient } from "viem"; -import { TypedApi } from "polkadot-api"; -import { generateRandomEthersWallet } from "../src/utils"; -import { tao, raoToEth } from "../src/balance-math"; -import { toViemAddress, } from "../src/address-utils" -import { getContract } from "../src/eth" -import { forceSetBalanceToEthAddress, } from "../src/subtensor"; - -describe("Transaction replace tests", () => { - // init eth part - const wallet = generateRandomEthersWallet(); - const wallet2 = generateRandomEthersWallet(); - const signer = getRandomSubstrateSigner(); - let publicClient: PublicClient; - let api: TypedApi - - before(async () => { - - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - await forceSetBalanceToEthAddress(api, wallet.address) - }); - - it("Can replace simple transfer transaction", async () => { - const transferBalance = raoToEth(tao(1)) - - const gasPrice = BigInt(10e9) - const gasLimit = BigInt(1000000) - const nonce = await publicClient.getTransactionCount({ address: toViemAddress(wallet.address) }) - - for (let i = 1; i < 10; i++) { - const transfer = { - to: wallet2.address, - value: transferBalance.toString(), - nonce: nonce, - gasPrice: gasPrice * BigInt(i), - gasLimit: gasLimit * BigInt(i) - } - - try { - await wallet.sendTransaction(transfer) - } catch (error) { - // ignore error, previous transaction could be mined. the nonce is wrong. - } - await new Promise(resolve => setTimeout(resolve, 10)) - } - - // check the node not crashed - await forceSetBalanceToEthAddress(api, wallet.address) - }) - - it("Can replace precompile call transaction", async () => { - const contract = getContract(IBALANCETRANSFER_ADDRESS, IBalanceTransferABI, wallet) - const transferBalance = raoToEth(tao(1)) - - const gasPrice = BigInt(10e9) - const gasLimit = BigInt(1000000) - const nonce = await publicClient.getTransactionCount({ address: toViemAddress(wallet.address) }) - - for (let i = 1; i < 10; i++) { - try { - await contract.transfer(signer.publicKey, { - value: transferBalance.toString(), - nonce: nonce, - gasPrice: gasPrice * BigInt(i), - gasLimit: gasLimit * BigInt(i) - }) - } catch (error) { - // ignore error, previous transaction could be mined. the nonce is wrong. - } - - await new Promise(resolve => setTimeout(resolve, 10)) - } - // check the node not crashed - await forceSetBalanceToEthAddress(api, wallet.address) - }) -}) \ No newline at end of file diff --git a/contract-tests/test/votingPower.precompile.test.ts b/contract-tests/test/votingPower.precompile.test.ts deleted file mode 100644 index f98edd5fc1..0000000000 --- a/contract-tests/test/votingPower.precompile.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import * as assert from "assert"; - -import { getDevnetApi, getRandomSubstrateKeypair, getAliceSigner, getSignerFromKeypair, waitForTransactionWithRetry } from "../src/substrate" -import { getPublicClient } from "../src/utils"; -import { ETH_LOCAL_URL } from "../src/config"; -import { devnet } from "@polkadot-api/descriptors" -import { PublicClient } from "viem"; -import { PolkadotSigner, TypedApi } from "polkadot-api"; -import { toViemAddress, convertPublicKeyToSs58 } from "../src/address-utils" -import { IVotingPowerABI, IVOTING_POWER_ADDRESS } from "../src/contracts/votingPower" -import { forceSetBalanceToSs58Address, addNewSubnetwork, startCall } from "../src/subtensor"; - -describe("Test VotingPower Precompile", () => { - // init substrate part - const hotkey = getRandomSubstrateKeypair(); - const coldkey = getRandomSubstrateKeypair(); - let publicClient: PublicClient; - - let api: TypedApi; - - // sudo account alice as signer - let alice: PolkadotSigner; - - // init other variable - let subnetId = 0; - - before(async () => { - // init variables got from await and async - publicClient = await getPublicClient(ETH_LOCAL_URL) - api = await getDevnetApi() - alice = await getAliceSigner(); - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) - - let netuid = await addNewSubnetwork(api, hotkey, coldkey) - await startCall(api, netuid, coldkey) - subnetId = netuid - }) - - describe("VotingPower Tracking Status Functions", () => { - it("isVotingPowerTrackingEnabled returns false by default", async () => { - const isEnabled = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "isVotingPowerTrackingEnabled", - args: [subnetId] - }) - - assert.ok(isEnabled !== undefined, "isVotingPowerTrackingEnabled should return a value"); - assert.strictEqual(typeof isEnabled, 'boolean', "isVotingPowerTrackingEnabled should return a boolean"); - // By default, voting power tracking is disabled - assert.strictEqual(isEnabled, false, "Voting power tracking should be disabled by default"); - }); - - it("getVotingPowerDisableAtBlock returns 0 when not scheduled", async () => { - const disableAtBlock = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPowerDisableAtBlock", - args: [subnetId] - }) - - assert.ok(disableAtBlock !== undefined, "getVotingPowerDisableAtBlock should return a value"); - assert.strictEqual(typeof disableAtBlock, 'bigint', "getVotingPowerDisableAtBlock should return a bigint"); - assert.strictEqual(disableAtBlock, BigInt(0), "Disable at block should be 0 when not scheduled"); - }); - - it("getVotingPowerEmaAlpha returns default alpha value", async () => { - const alpha = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPowerEmaAlpha", - args: [subnetId] - }) - - assert.ok(alpha !== undefined, "getVotingPowerEmaAlpha should return a value"); - assert.strictEqual(typeof alpha, 'bigint', "getVotingPowerEmaAlpha should return a bigint"); - // Default alpha is 0_003_570_000_000_000_000 // 0.00357 * 10^18 = 2 weeks e-folding (time-constant) @ 361 - assert.strictEqual(alpha, BigInt("3570000000000000"), "Default alpha should be 0.00357 * 10^18 (3570000000000000)"); - }); - }); - - describe("VotingPower Query Functions", () => { - it("getVotingPower returns 0 for hotkey without voting power", async () => { - // Convert hotkey public key to bytes32 format (0x prefixed hex string) - const hotkeyBytes32 = '0x' + Buffer.from(hotkey.publicKey).toString('hex'); - - const votingPower = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPower", - args: [subnetId, hotkeyBytes32 as `0x${string}`] - }) - - assert.ok(votingPower !== undefined, "getVotingPower should return a value"); - assert.strictEqual(typeof votingPower, 'bigint', "getVotingPower should return a bigint"); - // Without voting power tracking enabled, voting power should be 0 - assert.strictEqual(votingPower, BigInt(0), "Voting power should be 0 when tracking is disabled"); - }); - - it("getVotingPower returns 0 for unknown hotkey", async () => { - // Generate a random hotkey that doesn't exist - const randomHotkey = getRandomSubstrateKeypair(); - const randomHotkeyBytes32 = '0x' + Buffer.from(randomHotkey.publicKey).toString('hex'); - - const votingPower = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPower", - args: [subnetId, randomHotkeyBytes32 as `0x${string}`] - }) - - assert.ok(votingPower !== undefined, "getVotingPower should return a value"); - assert.strictEqual(votingPower, BigInt(0), "Voting power should be 0 for unknown hotkey"); - }); - - it("getTotalVotingPower returns 0 when no voting power exists", async () => { - const totalVotingPower = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getTotalVotingPower", - args: [subnetId] - }) - - assert.ok(totalVotingPower !== undefined, "getTotalVotingPower should return a value"); - assert.strictEqual(typeof totalVotingPower, 'bigint', "getTotalVotingPower should return a bigint"); - assert.strictEqual(totalVotingPower, BigInt(0), "Total voting power should be 0 when tracking is disabled"); - }); - }); - - describe("VotingPower with Tracking Enabled", () => { - let enabledSubnetId: number; - - before(async () => { - // Create a new subnet for this test - const hotkey2 = getRandomSubstrateKeypair(); - const coldkey2 = getRandomSubstrateKeypair(); - - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) - await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey2.publicKey)) - - enabledSubnetId = await addNewSubnetwork(api, hotkey2, coldkey2) - await startCall(api, enabledSubnetId, coldkey2) - - // Enable voting power tracking via sudo - const internalCall = api.tx.SubtensorModule.enable_voting_power_tracking({ netuid: enabledSubnetId }) - const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) - await waitForTransactionWithRetry(api, tx, alice) - }); - - it("isVotingPowerTrackingEnabled returns true after enabling", async () => { - const isEnabled = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "isVotingPowerTrackingEnabled", - args: [enabledSubnetId] - }) - - assert.strictEqual(isEnabled, true, "Voting power tracking should be enabled"); - }); - - it("getVotingPowerDisableAtBlock still returns 0 when enabled but not scheduled for disable", async () => { - const disableAtBlock = await publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPowerDisableAtBlock", - args: [enabledSubnetId] - }) - - assert.strictEqual(disableAtBlock, BigInt(0), "Disable at block should still be 0"); - }); - }); - - describe("All precompile functions are accessible", () => { - it("All VotingPower precompile functions can be called", async () => { - const hotkeyBytes32 = '0x' + Buffer.from(hotkey.publicKey).toString('hex'); - - // Test all five functions - const results = await Promise.all([ - publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPower", - args: [subnetId, hotkeyBytes32 as `0x${string}`] - }), - publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "isVotingPowerTrackingEnabled", - args: [subnetId] - }), - publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPowerDisableAtBlock", - args: [subnetId] - }), - publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getVotingPowerEmaAlpha", - args: [subnetId] - }), - publicClient.readContract({ - abi: IVotingPowerABI, - address: toViemAddress(IVOTING_POWER_ADDRESS), - functionName: "getTotalVotingPower", - args: [subnetId] - }) - ]); - - // All functions should return defined values - results.forEach((result: unknown, index: number) => { - assert.ok(result !== undefined, `Function ${index} should return a value`); - }); - - // Verify types - assert.strictEqual(typeof results[0], 'bigint', "getVotingPower should return bigint"); - assert.strictEqual(typeof results[1], 'boolean', "isVotingPowerTrackingEnabled should return boolean"); - assert.strictEqual(typeof results[2], 'bigint', "getVotingPowerDisableAtBlock should return bigint"); - assert.strictEqual(typeof results[3], 'bigint', "getVotingPowerEmaAlpha should return bigint"); - assert.strictEqual(typeof results[4], 'bigint', "getTotalVotingPower should return bigint"); - }); - }); -}); diff --git a/contract-tests/yarn.lock b/contract-tests/yarn.lock index 080ecb1325..25300ca989 100644 --- a/contract-tests/yarn.lock +++ b/contract-tests/yarn.lock @@ -155,50 +155,35 @@ dependencies: "@noble/hashes" "1.8.0" -"@noble/hashes@^1.3.1": +"@noble/hashes@^1.3.1", "@noble/hashes@^1.3.3", "@noble/hashes@^1.5.0", "@noble/hashes@^1.8.0", "@noble/hashes@~1.8.0", "@noble/hashes@1.8.0": version "1.8.0" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== -"@noble/hashes@^1.3.3", "@noble/hashes@~1.8.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== - -"@noble/hashes@^1.5.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== - -"@noble/hashes@^1.8.0", "@noble/hashes@1.8.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== - -"@noble/hashes@^2.0.0", "@noble/hashes@^2.0.1", "@noble/hashes@~2.0.0", "@noble/hashes@2.0.1": +"@noble/hashes@^2.0.0", "@noble/hashes@~2.0.0", "@noble/hashes@2.0.1": version "2.0.1" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz" integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== -"@noble/hashes@~1.7.1", "@noble/hashes@1.7.1": - version "1.7.1" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz" - integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== +"@noble/hashes@^2.0.1", "@noble/hashes@2.0.1": + version "2.0.1" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz" + integrity sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw== -"@noble/hashes@~1.8.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz" - integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== +"@noble/hashes@~1.7.1", "@noble/hashes@1.7.2": + version "1.7.2" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz" + integrity sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ== "@noble/hashes@1.3.2": version "1.3.2" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== -"@noble/hashes@1.7.2": - version "1.7.2" - resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz" - integrity sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ== +"@noble/hashes@1.7.1": + version "1.7.1" + resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz" + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -255,7 +240,7 @@ integrity sha512-cgA9fh8dfBai9b46XaaQmj9vwzyHStQjc/xrAvQksgF6SqvZ0yAfxVqLvGrsz/Xi3dsAdKLg09PybC7MUAMv9w== "@polkadot-api/descriptors@file:.papi/descriptors": - version "0.1.0-autogenerated.5063582544821983772" + version "0.1.0-autogenerated.14746733976505338329" resolved "file:.papi/descriptors" "@polkadot-api/ink-contracts@^0.4.1", "@polkadot-api/ink-contracts@>=0.4.0", "@polkadot-api/ink-contracts@0.4.3": @@ -277,7 +262,7 @@ resolved "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.2.7.tgz" integrity sha512-+HM4JQXzO2GPUD2++4GOLsmFL6LO8RoLvig0HgCLuypDgfdZMlwd8KnyGHjRnVEHA5X+kvXbk84TDcAXVxTazQ== -"@polkadot-api/json-rpc-provider@^0.0.1", "@polkadot-api/json-rpc-provider@0.0.1": +"@polkadot-api/json-rpc-provider@^0.0.1": version "0.0.1" resolved "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1.tgz" integrity sha512-/SMC/l7foRjpykLTUTacIH05H3mr9ip8b5xxfwXlVezXrNVLp3Cv0GX6uItkKd+ZjzVPf3PFrDF2B2/HLSNESA== @@ -460,15 +445,7 @@ "@scure/base" "^1.1.1" scale-ts "^1.6.0" -"@polkadot-api/substrate-client@^0.1.2", "@polkadot-api/substrate-client@0.1.4": - version "0.1.4" - resolved "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.1.4.tgz" - integrity sha512-MljrPobN0ZWTpn++da9vOvt+Ex+NlqTlr/XT7zi9sqPtDJiQcYl+d29hFAgpaeTqbeQKZwz3WDE9xcEfLE8c5A== - dependencies: - "@polkadot-api/json-rpc-provider" "0.0.1" - "@polkadot-api/utils" "0.1.0" - -"@polkadot-api/substrate-client@0.4.7": +"@polkadot-api/substrate-client@^0.1.2", "@polkadot-api/substrate-client@0.1.4", "@polkadot-api/substrate-client@0.4.7": version "0.4.7" resolved "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.4.7.tgz" integrity sha512-Mmx9VKincVqfVQmq89gzDk4DN3uKwf8CxoqYvq+EiPUZ1QmMUc7X4QMwG1MXIlYdnm5LSXzn+2Jn8ik8xMgL+w== @@ -2499,13 +2476,6 @@ signal-exit@^4.0.1, signal-exit@^4.1.0: resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -smoldot@2.0.26, smoldot@2.x: - version "2.0.26" - resolved "https://registry.npmjs.org/smoldot/-/smoldot-2.0.26.tgz" - integrity sha512-F+qYmH4z2s2FK+CxGj8moYcd1ekSIKH8ywkdqlOz88Dat35iB1DIYL11aILN46YSGMzQW/lbJNS307zBSDN5Ig== - dependencies: - ws "^8.8.1" - smoldot@2.0.39: version "2.0.39" resolved "https://registry.npmjs.org/smoldot/-/smoldot-2.0.39.tgz" diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index 02e31e750e..5430a75d9e 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -124,25 +124,28 @@ pub fn create_benchmark_extrinsic( .checked_next_power_of_two() .map(|c| c / 2) .unwrap_or(2) as u64; - let extra: runtime::TransactionExtensions = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( - period, - best_block.saturated_into(), - )), - check_nonce::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - transaction_payment_wrapper::ChargeTransactionPaymentWrapper::new( - pallet_transaction_payment::ChargeTransactionPayment::::from(0), - ), - sudo_wrapper::SudoTransactionExtension::::new(), - pallet_subtensor::SubtensorTransactionExtension::::new(), - pallet_drand::drand_priority::DrandPriority::::new(), - frame_metadata_hash_extension::CheckMetadataHash::::new(true), - ); + let extra: runtime::TransactionExtensions = + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + check_nonce::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + transaction_payment_wrapper::ChargeTransactionPaymentWrapper::new( + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ), + sudo_wrapper::SudoTransactionExtension::::new(), + pallet_subtensor::transaction_extension::SubtensorTransactionExtension::< + runtime::Runtime, + >::new(), + pallet_drand::drand_priority::DrandPriority::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), + ); let raw_payload = runtime::SignedPayload::from_raw( call.clone(), diff --git a/node/src/mev_shield/author.rs b/node/src/mev_shield/author.rs index 35f362df3d..8ac57b1d52 100644 --- a/node/src/mev_shield/author.rs +++ b/node/src/mev_shield/author.rs @@ -375,24 +375,28 @@ where let current_block: u64 = info.best_number.saturated_into(); let era = Era::mortal(ERA_PERIOD, current_block); - let extra: Extra = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(era), - node_subtensor_runtime::check_nonce::CheckNonce::::from(nonce).into(), - frame_system::CheckWeight::::new(), - node_subtensor_runtime::transaction_payment_wrapper::ChargeTransactionPaymentWrapper::< - runtime::Runtime, - >::new(pallet_transaction_payment::ChargeTransactionPayment::< - runtime::Runtime, - >::from(0u64)), - node_subtensor_runtime::sudo_wrapper::SudoTransactionExtension::::new(), - pallet_subtensor::SubtensorTransactionExtension::::new(), - pallet_drand::drand_priority::DrandPriority::::new(), - frame_metadata_hash_extension::CheckMetadataHash::::new(false), - ); + let extra: Extra = + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(era), + node_subtensor_runtime::check_nonce::CheckNonce::::from(nonce).into(), + frame_system::CheckWeight::::new(), + node_subtensor_runtime::transaction_payment_wrapper::ChargeTransactionPaymentWrapper::< + runtime::Runtime, + >::new(pallet_transaction_payment::ChargeTransactionPayment::< + runtime::Runtime, + >::from(0u64)), + node_subtensor_runtime::sudo_wrapper::SudoTransactionExtension::::new( + ), + pallet_subtensor::transaction_extension::SubtensorTransactionExtension::< + runtime::Runtime, + >::new(), + pallet_drand::drand_priority::DrandPriority::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), + ); // 3) Manually construct the `Implicit` tuple that the runtime will also derive. type Implicit = >::Implicit; diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 9cab4894e9..7b8124144d 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -263,7 +263,7 @@ mod benchmarks { ); #[extrinsic_call] - _(RawOrigin::Root, 1u16.into()/*netuid*/, 256u16/*max_allowed_uids*/)/*sudo_set_max_allowed_uids*/; + _(RawOrigin::Root, 1u16.into()/*netuid*/, 2048u16/*max_allowed_uids*/)/*sudo_set_max_allowed_uids*/; } #[benchmark] @@ -480,13 +480,7 @@ mod benchmarks { } #[benchmark] - fn sudo_set_coldkey_swap_announcement_delay() { - #[extrinsic_call] - _(RawOrigin::Root, 100u32.into()); - } - - #[benchmark] - fn sudo_set_coldkey_swap_reannouncement_delay() { + fn sudo_set_coldkey_swap_schedule_duration() { #[extrinsic_call] _(RawOrigin::Root, 100u32.into()); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 654f7207b8..4143e3fb3a 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -145,8 +145,6 @@ pub mod pallet { Leasing, /// Address mapping precompile AddressMapping, - /// Voting power precompile - VotingPower, } #[pallet::type_value] @@ -507,7 +505,7 @@ pub mod pallet { /// The extrinsic will call the Subtensor pallet to set the maximum allowed UIDs for a subnet. #[pallet::call_index(15)] #[pallet::weight(Weight::from_parts(32_140_000, 0) - .saturating_add(::DbWeight::get().reads(6_u64)) + .saturating_add(::DbWeight::get().reads(5_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_max_allowed_uids( origin: OriginFor, @@ -536,12 +534,6 @@ pub mod pallet { max_allowed_uids <= DefaultMaxAllowedUids::::get(), Error::::MaxAllowedUidsGreaterThanDefaultMaxAllowedUids ); - // Prevent chain bloat: Require max UIDs to be limited - let mechanism_count = pallet_subtensor::MechanismCountCurrent::::get(netuid); - pallet_subtensor::Pallet::::ensure_max_uids_over_all_mechanisms( - max_allowed_uids, - mechanism_count.into(), - )?; pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, max_allowed_uids); pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, @@ -1299,6 +1291,40 @@ pub mod pallet { res } + /// Sets the duration of the coldkey swap schedule. + /// + /// This extrinsic allows the root account to set the duration for the coldkey swap schedule. + /// The coldkey swap schedule determines how long it takes for a coldkey swap operation to complete. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `duration` - The new duration for the coldkey swap schedule, in number of blocks. + /// + /// # Errors + /// * `BadOrigin` - If the caller is not the root account. + /// + /// # Weight + /// Weight is handled by the `#[pallet::weight]` attribute. + #[pallet::call_index(54)] + #[pallet::weight(Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)))] + pub fn sudo_set_coldkey_swap_schedule_duration( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the new duration of schedule coldkey swap + pallet_subtensor::Pallet::::set_coldkey_swap_schedule_duration(duration); + + // Log the change + log::trace!("ColdkeySwapScheduleDurationSet( duration: {duration:?} )"); + + Ok(()) + } + /// Sets the duration of the dissolve network schedule. /// /// This extrinsic allows the root account to set the duration for the dissolve network schedule. @@ -2065,20 +2091,6 @@ pub mod pallet { Ok(()) } - /// Sets the global maximum number of mechanisms in a subnet - #[pallet::call_index(88)] - #[pallet::weight(Weight::from_parts(15_000_000, 0) - .saturating_add(::DbWeight::get().reads(1_u64)) - .saturating_add(::DbWeight::get().writes(1_u64)))] - pub fn sudo_set_max_mechanism_count( - origin: OriginFor, - max_mechanism_count: MechId, - ) -> DispatchResult { - ensure_root(origin)?; - pallet_subtensor::Pallet::::do_set_max_mechanism_count(max_mechanism_count)?; - Ok(()) - } - /// Sets the minimum number of non-immortal & non-immune UIDs that must remain in a subnet #[pallet::call_index(84)] #[pallet::weight(Weight::from_parts(7_114_000, 0) @@ -2104,36 +2116,6 @@ pub mod pallet { log::debug!("StartCallDelay( delay: {delay:?} ) "); Ok(()) } - - /// Sets the announcement delay for coldkey swap. - #[pallet::call_index(86)] - #[pallet::weight(Weight::from_parts(5_000_000, 0) - .saturating_add(::DbWeight::get().reads(0_u64)) - .saturating_add(::DbWeight::get().writes(1_u64)))] - pub fn sudo_set_coldkey_swap_announcement_delay( - origin: OriginFor, - duration: BlockNumberFor, - ) -> DispatchResult { - ensure_root(origin)?; - pallet_subtensor::Pallet::::set_coldkey_swap_announcement_delay(duration); - log::trace!("ColdkeySwapAnnouncementDelaySet( duration: {duration:?} )"); - Ok(()) - } - - /// Sets the coldkey swap reannouncement delay. - #[pallet::call_index(87)] - #[pallet::weight(Weight::from_parts(5_000_000, 0) - .saturating_add(::DbWeight::get().reads(0_u64)) - .saturating_add(::DbWeight::get().writes(1_u64)))] - pub fn sudo_set_coldkey_swap_reannouncement_delay( - origin: OriginFor, - duration: BlockNumberFor, - ) -> DispatchResult { - ensure_root(origin)?; - pallet_subtensor::Pallet::::set_coldkey_swap_reannouncement_delay(duration); - log::trace!("ColdkeySwapReannouncementDelaySet( duration: {duration:?} )"); - Ok(()) - } } } diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 19d2e891cd..0117dff889 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -19,7 +19,7 @@ use sp_runtime::{ }; use sp_std::cmp::Ordering; use sp_weights::Weight; -use subtensor_runtime_common::{AuthorshipInfo, NetUid, TaoCurrency}; +use subtensor_runtime_common::{NetUid, TaoCurrency}; type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -74,14 +74,6 @@ pub type BlockNumber = u64; pub type TestAuthId = test_crypto::TestAuthId; pub type UncheckedExtrinsic = TestXt; -pub struct MockAuthorshipProvider; - -impl AuthorshipInfo for MockAuthorshipProvider { - fn author() -> Option { - Some(U256::from(12345u64)) - } -} - parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -99,7 +91,7 @@ parameter_types! { pub const SelfOwnership: u64 = 2; pub const InitialImmunityPeriod: u16 = 2; pub const InitialMinAllowedUids: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 256; + pub const InitialMaxAllowedUids: u16 = 16; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty: u16 = u16::MAX; pub const InitialBondsResetOn: bool = false; @@ -139,14 +131,17 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. + // pub const InitialSubnetLimit: u16 = 10; // (DEPRECATED) pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; - pub const InitialColdkeySwapReannouncementDelay: u64 = 10; + // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -215,8 +210,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; @@ -230,7 +225,6 @@ impl pallet_subtensor::Config for Test { type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; type CommitmentsInterface = CommitmentsI; type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = MockAuthorshipProvider; } parameter_types! { @@ -331,6 +325,7 @@ impl pallet_balances::Config for Test { parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const SwapMaxFeeRate: u16 = 10000; // 15.26% + pub const SwapMaxPositions: u32 = 100; pub const SwapMinimumLiquidity: u64 = 1_000; pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(1_000_000).unwrap(); } @@ -342,6 +337,7 @@ impl pallet_subtensor_swap::Config for Test { type TaoReserve = pallet_subtensor::TaoCurrencyReserve; type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; type MinimumReserve = SwapMinimumReserve; type WeightInfo = (); diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index f87ba31bba..93d56ec343 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -10,10 +10,7 @@ use pallet_subtensor::{ TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, *, }; // use pallet_subtensor::{migrations, Event}; -use pallet_subtensor::{ - Event, subnets::mechanism::MAX_MECHANISM_COUNT_PER_SUBNET, - utils::rate_limiting::TransactionType, -}; +use pallet_subtensor::{Event, utils::rate_limiting::TransactionType}; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{Get, Pair, U256, ed25519}; use substrate_fixed::types::I96F32; @@ -543,20 +540,6 @@ fn test_sudo_set_max_allowed_uids() { Error::::MaxAllowedUidsGreaterThanDefaultMaxAllowedUids ); - // Trying to set max allowed uids that would cause max_allowed_uids * mechanism_count > 256 - MaxAllowedUids::::insert(netuid, 8); - MechanismCountCurrent::::insert(netuid, MechId::from(32)); - let large_max_uids = 16; - assert_noop!( - AdminUtils::sudo_set_max_allowed_uids( - <::RuntimeOrigin>::root(), - netuid, - large_max_uids - ), - SubtensorError::::TooManyUIDsPerMechanism - ); - MechanismCountCurrent::::insert(netuid, MechId::from(1)); - // Normal case assert_ok!(AdminUtils::sudo_set_max_allowed_uids( <::RuntimeOrigin>::root(), @@ -1399,74 +1382,39 @@ fn test_sudo_get_set_alpha() { } #[test] -fn test_sudo_set_coldkey_swap_announcement_delay() { +fn test_sudo_set_coldkey_swap_schedule_duration() { new_test_ext().execute_with(|| { // Arrange let root = RuntimeOrigin::root(); let non_root = RuntimeOrigin::signed(U256::from(1)); - let new_delay = 100u32.into(); + let new_duration = 100u32.into(); // Act & Assert: Non-root account should fail assert_noop!( - AdminUtils::sudo_set_coldkey_swap_announcement_delay(non_root, new_delay), + AdminUtils::sudo_set_coldkey_swap_schedule_duration(non_root, new_duration), DispatchError::BadOrigin ); // Act: Root account should succeed - assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( root.clone(), - new_delay - )); - - // Assert: Check if the delay was actually set - assert_eq!( - pallet_subtensor::ColdkeySwapAnnouncementDelay::::get(), - new_delay - ); - - // Act & Assert: Setting the same value again should succeed (idempotent operation) - assert_ok!(AdminUtils::sudo_set_coldkey_swap_announcement_delay( - root, new_delay - )); - - // You might want to check for events here if your pallet emits them - System::assert_last_event(Event::ColdkeySwapAnnouncementDelaySet(new_delay).into()); - }); -} - -#[test] -fn test_sudo_set_coldkey_swap_reannouncement_delay() { - new_test_ext().execute_with(|| { - // Arrange - let root = RuntimeOrigin::root(); - let non_root = RuntimeOrigin::signed(U256::from(1)); - let new_delay = 100u32.into(); - - // Act & Assert: Non-root account should fail - assert_noop!( - AdminUtils::sudo_set_coldkey_swap_reannouncement_delay(non_root, new_delay), - DispatchError::BadOrigin - ); - - // Act: Root account should succeed - assert_ok!(AdminUtils::sudo_set_coldkey_swap_reannouncement_delay( - root.clone(), - new_delay + new_duration )); - // Assert: Check if the delay was actually set + // Assert: Check if the duration was actually set assert_eq!( - pallet_subtensor::ColdkeySwapReannouncementDelay::::get(), - new_delay + pallet_subtensor::ColdkeySwapScheduleDuration::::get(), + new_duration ); // Act & Assert: Setting the same value again should succeed (idempotent operation) - assert_ok!(AdminUtils::sudo_set_coldkey_swap_reannouncement_delay( - root, new_delay + assert_ok!(AdminUtils::sudo_set_coldkey_swap_schedule_duration( + root, + new_duration )); // You might want to check for events here if your pallet emits them - System::assert_last_event(Event::ColdkeySwapReannouncementDelaySet(new_delay).into()); + System::assert_last_event(Event::ColdkeySwapScheduleDurationSet(new_duration).into()); }); } @@ -2393,7 +2341,6 @@ fn test_sudo_set_mechanism_count() { add_network(netuid, 10); // Set the Subnet Owner SubnetOwner::::insert(netuid, sn_owner); - MaxAllowedUids::::insert(netuid, 256_u16); assert_eq!( AdminUtils::sudo_set_mechanism_count( @@ -2407,13 +2354,7 @@ fn test_sudo_set_mechanism_count() { AdminUtils::sudo_set_mechanism_count(RuntimeOrigin::root(), netuid, ss_count_bad), pallet_subtensor::Error::::InvalidValue ); - assert_noop!( - AdminUtils::sudo_set_mechanism_count(RuntimeOrigin::root(), netuid, ss_count_ok), - pallet_subtensor::Error::::TooManyUIDsPerMechanism - ); - // Reduce max UIDs to 128 - MaxAllowedUids::::insert(netuid, 128_u16); assert_ok!(AdminUtils::sudo_set_mechanism_count( <::RuntimeOrigin>::root(), netuid, @@ -2439,8 +2380,6 @@ fn test_sudo_set_mechanism_count_and_emissions() { add_network(netuid, 10); // Set the Subnet Owner SubnetOwner::::insert(netuid, sn_owner); - MaxMechanismCount::::set(MechId::from(2)); - MaxAllowedUids::::set(netuid, 128_u16); assert_ok!(AdminUtils::sudo_set_mechanism_count( <::RuntimeOrigin>::signed(sn_owner), @@ -2929,35 +2868,6 @@ fn test_sudo_set_min_allowed_uids() { }); } -#[test] -fn test_sudo_set_max_mechanism_count() { - new_test_ext().execute_with(|| { - // Normal case - assert_ok!(AdminUtils::sudo_set_max_mechanism_count( - <::RuntimeOrigin>::root(), - MechId::from(10) - )); - - // Zero fails - assert_noop!( - AdminUtils::sudo_set_max_mechanism_count( - <::RuntimeOrigin>::root(), - MechId::from(0) - ), - pallet_subtensor::Error::::InvalidValue - ); - - // Over max bound fails - assert_noop!( - AdminUtils::sudo_set_max_mechanism_count( - <::RuntimeOrigin>::root(), - MechId::from(MAX_MECHANISM_COUNT_PER_SUBNET + 1) - ), - pallet_subtensor::Error::::InvalidValue - ); - }); -} - #[test] fn test_sudo_set_min_non_immune_uids() { new_test_ext().execute_with(|| { diff --git a/pallets/shield/src/benchmarking.rs b/pallets/shield/src/benchmarking.rs index e3890bae76..1414779314 100644 --- a/pallets/shield/src/benchmarking.rs +++ b/pallets/shield/src/benchmarking.rs @@ -4,7 +4,7 @@ use frame_benchmarking::v2::*; use frame_support::{BoundedVec, pallet_prelude::ConstU32}; use frame_system::{RawOrigin, pallet_prelude::BlockNumberFor}; use sp_core::sr25519; -use sp_runtime::{AccountId32, traits::Hash as HashT}; +use sp_runtime::traits::Hash as HashT; use sp_std::vec; // /// Helper to build bounded bytes (public key) of a given length. @@ -40,8 +40,6 @@ fn bounded_ct(len: usize) -> BoundedVec> { ::RuntimeCall: From>, // Needed so we can seed Authorities from a dev sr25519 pubkey. ::AuthorityId: From, - ::AccountId: From + Into, - ::RuntimeOrigin: From> )] mod benches { use super::*; diff --git a/pallets/shield/src/lib.rs b/pallets/shield/src/lib.rs index e0fc250058..eed0161f20 100644 --- a/pallets/shield/src/lib.rs +++ b/pallets/shield/src/lib.rs @@ -86,7 +86,9 @@ pub mod pallet { #[pallet::config] pub trait Config: - frame_system::Config>> + pallet_aura::Config + frame_system::Config>> + + pallet_timestamp::Config + + pallet_aura::Config { type RuntimeCall: Parameter + sp_runtime::traits::Dispatchable< @@ -94,7 +96,7 @@ pub mod pallet { PostInfo = PostDispatchInfo, > + GetDispatchInfo; - type AuthorityOrigin: AuthorityOriginExt; + type AuthorityOrigin: AuthorityOriginExt; } #[pallet::pallet] @@ -244,9 +246,13 @@ pub mod pallet { /// Announce the ML‑KEM public key that will become `CurrentKey` in /// the following block. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(20_999_999_999, 0) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)))] + #[pallet::weight(( + Weight::from_parts(20_999_999_999, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] #[allow(clippy::useless_conversion)] pub fn announce_next_key( origin: OriginFor, diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index a6fadec1f4..2e35a89d19 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -55,15 +55,11 @@ sha2.workspace = true rand_chacha.workspace = true pallet-crowdloan.workspace = true pallet-subtensor-proxy.workspace = true -pallet-shield.workspace = true [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } pallet-scheduler.workspace = true pallet-subtensor-proxy.workspace = true -pallet-aura.workspace = true -pallet-timestamp.workspace = true -sp-consensus-aura.workspace = true subtensor-runtime-common.workspace = true pallet-subtensor-swap.workspace = true sp-version.workspace = true @@ -91,10 +87,7 @@ try-runtime = [ "pallet-crowdloan/try-runtime", "pallet-drand/try-runtime", "pallet-subtensor-proxy/try-runtime", - "pallet-subtensor-utility/try-runtime", - "pallet-shield/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-aura/try-runtime", + "pallet-subtensor-utility/try-runtime" ] default = ["std"] std = [ @@ -122,10 +115,6 @@ std = [ "pallet-drand/std", "pallet-subtensor-proxy/std", "pallet-subtensor-swap/std", - "pallet-shield/std", - "pallet-timestamp/std", - "pallet-aura/std", - "sp-consensus-aura/std", "subtensor-swap-interface/std", "pallet-subtensor-utility/std", "safe-math/std", @@ -158,9 +147,7 @@ runtime-benchmarks = [ "pallet-drand/runtime-benchmarks", "pallet-subtensor-proxy/runtime-benchmarks", "pallet-subtensor-swap/runtime-benchmarks", - "pallet-subtensor-utility/runtime-benchmarks", - "pallet-shield/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks" + "pallet-subtensor-utility/runtime-benchmarks" ] pow-faucet = [] fast-runtime = ["subtensor-runtime-common/fast-runtime"] diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index a43a468bef..f61c35aede 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -16,15 +16,9 @@ use sp_runtime::{ }; use sp_std::collections::btree_set::BTreeSet; use sp_std::vec; -use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; -#[benchmarks( - where - T: pallet_balances::Config, - ::ExistentialDeposit: Get, -)] +#[frame_benchmarking::v2::benchmarks] mod pallet_benchmarks { use super::*; @@ -68,8 +62,6 @@ mod pallet_benchmarks { Subtensor::::set_max_registrations_per_block(netuid, 4096); Subtensor::::set_target_registrations_per_interval(netuid, 4096); Subtensor::::set_commit_reveal_weights_enabled(netuid, false); - SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000_u64)); - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1_000_000_000_000_000_u64)); let mut seed: u32 = 1; let mut dests = Vec::new(); @@ -377,6 +369,17 @@ mod pallet_benchmarks { ); } + #[benchmark] + fn schedule_swap_coldkey() { + let old_coldkey: T::AccountId = account("old_cold", 0, 1); + let new_coldkey: T::AccountId = account("new_cold", 1, 2); + let amount: u64 = 100_000_000_000_000; + Subtensor::::add_balance_to_coldkey_account(&old_coldkey, amount); + + #[extrinsic_call] + _(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()); + } + #[benchmark] fn sudo_set_tx_childkey_take_rate_limit() { let new_rate_limit: u64 = 100; @@ -415,65 +418,15 @@ mod pallet_benchmarks { ); } - #[benchmark] - fn announce_coldkey_swap() { - let coldkey: T::AccountId = account("old_coldkey", 0, 0); - let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); - let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); - - let ed = ::ExistentialDeposit::get(); - let swap_cost = Subtensor::::get_key_swap_cost(); - Subtensor::::add_balance_to_coldkey_account(&coldkey, swap_cost.to_u64() + ed); - - #[extrinsic_call] - _(RawOrigin::Signed(coldkey), new_coldkey_hash); - } - - #[benchmark] - fn swap_coldkey_announced() { - let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); - let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); - let new_coldkey_hash: T::Hash = ::Hashing::hash_of(&new_coldkey); - let hotkey1: T::AccountId = account("hotkey1", 0, 0); - - let now = frame_system::Pallet::::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - ColdkeySwapAnnouncements::::insert(&old_coldkey, (now, new_coldkey_hash)); - frame_system::Pallet::::set_block_number(now + delay + 1u32.into()); - - let netuid = NetUid::from(1); - Subtensor::::init_new_network(netuid, 1); - Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::set_network_pow_registration_allowed(netuid, true); - - let block_number = Subtensor::::get_current_block_as_u64(); - let (nonce, work) = - Subtensor::::create_work_for_block_number(netuid, block_number, 3, &hotkey1); - let _ = Subtensor::::register( - RawOrigin::Signed(old_coldkey.clone()).into(), - netuid, - block_number, - nonce, - work.clone(), - hotkey1.clone(), - old_coldkey.clone(), - ); - - #[extrinsic_call] - _(RawOrigin::Signed(old_coldkey), new_coldkey); - } - #[benchmark] fn swap_coldkey() { let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); let new_coldkey: T::AccountId = account("new_coldkey", 0, 0); let hotkey1: T::AccountId = account("hotkey1", 0, 0); - - let ed = ::ExistentialDeposit::get(); + let netuid = NetUid::from(1); let swap_cost = Subtensor::::get_key_swap_cost(); - Subtensor::::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + ed); + let free_balance_old = swap_cost + 12345.into(); - let netuid = NetUid::from(1); Subtensor::::init_new_network(netuid, 1); Subtensor::::set_network_registration_allowed(netuid, true); Subtensor::::set_network_pow_registration_allowed(netuid, true); @@ -491,6 +444,19 @@ mod pallet_benchmarks { old_coldkey.clone(), ); + Subtensor::::add_balance_to_coldkey_account(&old_coldkey, free_balance_old.into()); + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity = ChainIdentityV2 { + name, + url: vec![], + github_repo: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + IdentitiesV2::::insert(&old_coldkey, identity); + #[extrinsic_call] _( RawOrigin::Root, @@ -500,31 +466,6 @@ mod pallet_benchmarks { ); } - #[benchmark] - fn dispute_coldkey_swap() { - let coldkey: T::AccountId = account("old_coldkey", 0, 0); - let coldkey_hash: T::Hash = ::Hashing::hash_of(&coldkey); - let now = frame_system::Pallet::::block_number(); - - ColdkeySwapAnnouncements::::insert(&coldkey, (now, coldkey_hash)); - - #[extrinsic_call] - _(RawOrigin::Signed(coldkey)); - } - - #[benchmark] - fn reset_coldkey_swap() { - let coldkey: T::AccountId = account("old_coldkey", 0, 0); - let coldkey_hash: T::Hash = ::Hashing::hash_of(&coldkey); - let now = frame_system::Pallet::::block_number(); - - ColdkeySwapAnnouncements::::insert(&coldkey, (now, coldkey_hash)); - ColdkeySwapDisputes::::insert(&coldkey, now); - - #[extrinsic_call] - _(RawOrigin::Root, coldkey); - } - #[benchmark] fn batch_reveal_weights() { let tempo: u16 = 0; @@ -733,12 +674,13 @@ mod pallet_benchmarks { let coldkey: T::AccountId = account("Test", 0, seed); let hotkey: T::AccountId = account("Alice", 0, seed); - let initial_balance = 900_000_000_000; - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), initial_balance); + let amount = 900_000_000_000; + let limit = TaoCurrency::from(6_000_000_000); + let amount_to_be_staked = TaoCurrency::from(44_000_000_000); + Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount); - // Price = 0.01 - let tao_reserve = TaoCurrency::from(1_000_000_000_000); - let alpha_in = AlphaCurrency::from(100_000_000_000_000); + let tao_reserve = TaoCurrency::from(150_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); @@ -748,23 +690,14 @@ mod pallet_benchmarks { hotkey.clone() )); - // Read current price and set limit price 0.1% higher, which is certainly getting hit - // by swapping 100 TAO - let current_price = T::SwapInterface::current_alpha_price(netuid); - let limit = current_price - .saturating_mul(U64F64::saturating_from_num(1_001_000_000)) - .saturating_to_num::(); - let amount_to_be_staked = TaoCurrency::from(100_000_000_000); - - // Allow partial (worst case) #[extrinsic_call] _( RawOrigin::Signed(coldkey.clone()), hotkey, netuid, amount_to_be_staked, - limit.into(), - true, + limit, + false, ); } @@ -841,9 +774,9 @@ mod pallet_benchmarks { let hotkey: T::AccountId = account("Alice", 0, seed); Subtensor::::set_burn(netuid, 1.into()); - // Price = 0.01 - let tao_reserve = TaoCurrency::from(1_000_000_000_000); - let alpha_in = AlphaCurrency::from(100_000_000_000_000); + let limit = TaoCurrency::from(1_000_000_000); + let tao_reserve = TaoCurrency::from(150_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); @@ -866,13 +799,7 @@ mod pallet_benchmarks { u64_staked_amt.into() )); - // Read current price and set limit price 0.01% lower, which is certainly getting hit - // by swapping 100 Alpha - let current_price = T::SwapInterface::current_alpha_price(netuid); - let limit = current_price - .saturating_mul(U64F64::saturating_from_num(999_900_000)) - .saturating_to_num::(); - let amount_unstaked = AlphaCurrency::from(100_000_000_000); + let amount_unstaked = AlphaCurrency::from(30_000_000_000); // Remove stake limit for benchmark StakingOperationRateLimiter::::remove((hotkey.clone(), coldkey.clone(), netuid)); @@ -883,8 +810,8 @@ mod pallet_benchmarks { hotkey.clone(), netuid, amount_unstaked, - limit.into(), - true, + limit, + false, ); } @@ -1338,9 +1265,8 @@ mod pallet_benchmarks { let hotkey: T::AccountId = account("Alice", 0, seed); Subtensor::::set_burn(netuid, 1.into()); - // Price = 0.01 - SubnetTAO::::insert(netuid, TaoCurrency::from(1_000_000_000_000)); - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(100_000_000_000_000)); + SubnetTAO::::insert(netuid, TaoCurrency::from(150_000_000_000)); + SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(100_000_000_000)); Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), 1000000u32.into()); @@ -1387,8 +1313,9 @@ mod pallet_benchmarks { let hotkey: T::AccountId = account("Alice", 0, seed); Subtensor::::set_burn(netuid, 1.into()); - let tao_reserve = TaoCurrency::from(1_000_000_000_000); - let alpha_in = AlphaCurrency::from(100_000_000_000_000); + let limit = TaoCurrency::from(1_000_000_000); + let tao_reserve = TaoCurrency::from(150_000_000_000); + let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); @@ -1401,13 +1328,7 @@ mod pallet_benchmarks { hotkey.clone() )); - // Read current price and set limit price 50% lower, which is not getting hit - // by swapping 1 TAO - let current_price = T::SwapInterface::current_alpha_price(netuid); - let limit = current_price - .saturating_mul(U64F64::saturating_from_num(500_000_000)) - .saturating_to_num::(); - let u64_staked_amt = 1_000_000_000; + let u64_staked_amt = 100_000_000_000; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), u64_staked_amt); assert_ok!(Subtensor::::add_stake( @@ -1424,7 +1345,7 @@ mod pallet_benchmarks { RawOrigin::Signed(coldkey.clone()), hotkey.clone(), netuid, - Some(limit.into()), + Some(limit), ); } @@ -1745,47 +1666,4 @@ mod pallet_benchmarks { #[extrinsic_call] _(RawOrigin::Root, netuid, 100); } - - #[benchmark] - fn add_stake_burn() { - let netuid = NetUid::from(1); - let tempo: u16 = 1; - let seed: u32 = 1; - - Subtensor::::init_new_network(netuid, tempo); - SubtokenEnabled::::insert(netuid, true); - Subtensor::::set_burn(netuid, 1000.into()); - Subtensor::::set_network_registration_allowed(netuid, true); - Subtensor::::set_max_allowed_uids(netuid, 4096); - - let coldkey: T::AccountId = account("Test", 0, seed); - let hotkey: T::AccountId = account("Alice", 0, seed); - - SubnetOwner::::set(netuid, coldkey.clone()); - - let balance_update = 900_000_000_000; - let limit = TaoCurrency::from(6_000_000_000); - let amount = TaoCurrency::from(44_000_000_000); - Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), balance_update); - - let tao_reserve = TaoCurrency::from(150_000_000_000); - let alpha_in = AlphaCurrency::from(100_000_000_000); - SubnetTAO::::insert(netuid, tao_reserve); - SubnetAlphaIn::::insert(netuid, alpha_in); - - assert_ok!(Subtensor::::do_burned_registration( - RawOrigin::Signed(coldkey.clone()).into(), - netuid, - hotkey.clone() - )); - - #[extrinsic_call] - _( - RawOrigin::Signed(coldkey.clone()), - hotkey, - netuid, - amount, - Some(limit), - ); - } } diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index c499fe3117..064bab4d2a 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, U64F64}, + types::{I96F32, U96F32}, }; 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: U64F64 = U64F64::saturating_from_num(tao_emission); - let float_alpha_block_emission: U64F64 = U64F64::saturating_from_num(alpha_block_emission); + 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); // 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: U64F64 = U64F64::saturating_from_num(tao_emission) + let mut alpha_in_emission: U96F32 = U96F32::saturating_from_num(tao_emission) .checked_div(alpha_price) .unwrap_or(float_alpha_block_emission); @@ -62,11 +62,11 @@ impl Pallet { } // Avoid rounding errors. - 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; + 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); } // Set Alpha in emission. diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index ba7d54ed3c..83567b6f57 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -213,6 +213,7 @@ impl Pallet { Self::finalize_all_subnet_root_dividends(netuid); // --- Perform the cleanup before removing the network. + T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; T::SwapInterface::clear_protocol_liquidity(netuid)?; T::CommitmentsInterface::purge_netuid(netuid); @@ -299,6 +300,7 @@ impl Pallet { SubnetMovingPrice::::remove(netuid); SubnetTaoFlow::::remove(netuid); SubnetEmaTaoFlow::::remove(netuid); + SubnetTaoProvided::::remove(netuid); // --- 13. Token / mechanism / registration toggles. TokenSymbol::::remove(netuid); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index f40d4e704b..2091946598 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -67,8 +67,7 @@ impl Pallet { let tao_to_swap_with: TaoCurrency = tou64!(excess_tao.get(netuid_i).unwrap_or(&asfloat!(0))).into(); - let (actual_injected_tao, actual_injected_alpha) = - T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); + T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); if tao_to_swap_with > TaoCurrency::ZERO { let buy_swap_result = Self::swap_tao_for_alpha( @@ -88,8 +87,7 @@ impl Pallet { AlphaCurrency::from(tou64!(*alpha_in.get(netuid_i).unwrap_or(&asfloat!(0)))); SubnetAlphaInEmission::::insert(*netuid_i, alpha_in_i); SubnetAlphaIn::::mutate(*netuid_i, |total| { - // Reserves also received fees in addition to alpha_in_i - *total = total.saturating_add(actual_injected_alpha); + *total = total.saturating_add(alpha_in_i); }); // Inject TAO in. @@ -97,8 +95,7 @@ impl Pallet { tou64!(*tao_in.get(netuid_i).unwrap_or(&asfloat!(0))).into(); SubnetTaoInEmission::::insert(*netuid_i, injected_tao); SubnetTAO::::mutate(*netuid_i, |total| { - // Reserves also received fees in addition to injected_tao - *total = total.saturating_add(actual_injected_tao); + *total = total.saturating_add(injected_tao); }); TotalStake::::mutate(|total| { *total = total.saturating_add(injected_tao); @@ -143,8 +140,7 @@ impl Pallet { log::debug!("alpha_emission_i: {alpha_emission_i:?}"); // Get subnet price. - let price_i: U96F32 = - U96F32::saturating_from_num(T::SwapInterface::current_alpha_price(netuid_i.into())); + let price_i: U96F32 = 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/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 2290b49b8d..f56b8a89a4 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -22,7 +22,6 @@ pub struct EpochTerms { pub validator_trust: u16, pub new_validator_permit: bool, pub bond: Vec<(u16, u16)>, - pub stake: AlphaCurrency, } pub struct EpochOutput(pub BTreeMap); @@ -989,10 +988,6 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); - let raw_stake: Vec = total_stake - .iter() - .map(|s| s.saturating_to_num::()) - .collect::>(); for (_hotkey, terms) in terms_map.iter_mut() { terms.dividend = cloned_dividends.get(terms.uid).copied().unwrap_or_default(); @@ -1017,7 +1012,6 @@ impl Pallet { .get(terms.uid) .copied() .unwrap_or_default(); - terms.stake = raw_stake.get(terms.uid).copied().unwrap_or_default().into(); let old_validator_permit = validator_permits .get(terms.uid) .copied() diff --git a/pallets/subtensor/src/extensions/check_coldkey_swap.rs b/pallets/subtensor/src/extensions/check_coldkey_swap.rs deleted file mode 100644 index db603f9614..0000000000 --- a/pallets/subtensor/src/extensions/check_coldkey_swap.rs +++ /dev/null @@ -1,384 +0,0 @@ -use crate::{Call, ColdkeySwapAnnouncements, ColdkeySwapDisputes, Config, CustomTransactionError}; -use codec::{Decode, DecodeWithMemTracking, Encode}; -use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; -use frame_support::traits::IsSubType; -use pallet_subtensor_proxy::Call as ProxyCall; -use scale_info::TypeInfo; -use sp_runtime::{ - impl_tx_ext_default, - traits::{ - AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, StaticLookup, - TransactionExtension, ValidateResult, - }, - transaction_validity::TransactionSource, -}; -use sp_std::marker::PhantomData; -use subtensor_macros::freeze_struct; - -type CallOf = ::RuntimeCall; -type OriginOf = ::RuntimeOrigin; -type LookupOf = ::Lookup; - -#[freeze_struct("483277dc74a5aa56")] -#[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] -pub struct CheckColdkeySwap(pub PhantomData); - -impl CheckColdkeySwap { - pub fn new() -> Self { - Self(Default::default()) - } -} - -impl sp_std::fmt::Debug for CheckColdkeySwap { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "CheckColdkeySwap") - } -} - -impl - TransactionExtension> for CheckColdkeySwap -where - CallOf: Dispatchable - + IsSubType> - + IsSubType> - + IsSubType>, - OriginOf: AsSystemOriginSigner + Clone, -{ - const IDENTIFIER: &'static str = "CheckColdkeySwap"; - - type Implicit = (); - type Val = (); - type Pre = (); - - fn validate( - &self, - origin: OriginOf, - call: &CallOf, - _info: &DispatchInfoOf>, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Implication, - _source: TransactionSource, - ) -> ValidateResult> { - // Ensure the transaction is signed, else we just skip the extension. - let Some(who) = origin.as_system_origin_signer() else { - return Ok((Default::default(), (), origin)); - }; - - // Get the real account and origin if we are behind a proxy. - let (who, call) = if let Some( - ProxyCall::proxy { real, call, .. } | ProxyCall::proxy_announced { real, call, .. }, - ) = call.is_sub_type() - { - let real = LookupOf::::lookup(real.clone()) - .map_err(|_| CustomTransactionError::InvalidRealAccount)?; - (real, (*call.clone()).into()) - } else { - (who.clone(), call.clone()) - }; - - if ColdkeySwapAnnouncements::::contains_key(&who) { - if ColdkeySwapDisputes::::contains_key(&who) { - return Err(CustomTransactionError::ColdkeySwapDisputed.into()); - } - - let is_allowed_direct = matches!( - call.is_sub_type(), - Some( - Call::announce_coldkey_swap { .. } - | Call::swap_coldkey_announced { .. } - | Call::dispute_coldkey_swap { .. } - ) - ); - - let is_mev_protected = matches!( - IsSubType::>::is_sub_type(&call), - Some(pallet_shield::Call::submit_encrypted { .. }) - ); - - if !is_allowed_direct && !is_mev_protected { - return Err(CustomTransactionError::ColdkeySwapAnnounced.into()); - } - } - - Ok((Default::default(), (), origin)) - } - - impl_tx_ext_default!(CallOf; weight prepare); -} - -#[cfg(test)] -#[allow(clippy::expect_used, clippy::unwrap_used)] -mod tests { - use super::*; - use crate::{BalancesCall, DefaultMinStake, tests::mock::*}; - use frame_support::testing_prelude::*; - use frame_support::{dispatch::GetDispatchInfo, traits::OriginTrait}; - use frame_system::Call as SystemCall; - use sp_core::U256; - use sp_runtime::{ - BoundedVec, - traits::{AsTransactionAuthorizedOrigin, Hash, TxBaseImplication}, - }; - use subtensor_runtime_common::{Currency, NetUid}; - - type HashingOf = ::Hashing; - - const CALL: RuntimeCall = RuntimeCall::System(SystemCall::remark { remark: vec![] }); - - #[test] - fn skipped_for_non_signed_origins() { - new_test_ext(1).execute_with(|| { - let info = CALL.get_dispatch_info(); - let len = 0_usize; - - let (_, _, origin) = CheckColdkeySwap::::new() - .validate( - None.into(), - &CALL, - &info, - len, - (), - &TxBaseImplication(CALL), - TransactionSource::External, - ) - .unwrap(); - assert!(!origin.is_transaction_authorized()); - - let (_, _, origin) = CheckColdkeySwap::::new() - .validate( - RuntimeOrigin::root().into(), - &CALL, - &info, - len, - (), - &TxBaseImplication(CALL), - TransactionSource::External, - ) - .unwrap(); - assert!(origin.as_system_ref().unwrap().is_root()); - }) - } - - #[test] - fn skipped_if_no_active_swap() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let info = CALL.get_dispatch_info(); - let len = 0_usize; - - let (_, _, origin) = CheckColdkeySwap::::new() - .validate( - RuntimeOrigin::signed(who).into(), - &CALL, - &info, - len, - (), - &TxBaseImplication(CALL), - TransactionSource::External, - ) - .unwrap(); - assert_eq!(origin.as_signer(), Some(&who)); - }) - } - - #[test] - fn validate_calls_correctly() { - new_test_ext(1).execute_with(|| { - let netuid = NetUid::from(1); - let stake = DefaultMinStake::::get().to_u64(); - let who = U256::from(1); - let now = System::block_number(); - let another_coldkey = U256::from(3); - let another_coldkey_hash = HashingOf::::hash_of(&another_coldkey); - let new_coldkey = U256::from(42); - let new_coldkey_hash = HashingOf::::hash_of(&new_coldkey); - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - - let reserve = stake * 10; - setup_reserves(netuid, reserve.into(), reserve.into()); - - // Setup network and neuron - let hotkey = U256::from(2); - add_network(netuid, 1, 0); - register_ok_neuron(netuid, hotkey, who, 0); - - SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); - - let forbidden_calls: Vec = vec![ - RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { - netuid, - coldkey: who, - }), - RuntimeCall::SubtensorModule(SubtensorCall::add_stake { - hotkey, - netuid, - amount_staked: stake.into(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit { - hotkey, - netuid, - amount_staked: stake.into(), - limit_price: stake.into(), - allow_partial: false, - }), - RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit { - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - limit_price: stake.into(), - allow_partial: false, - }), - RuntimeCall::SubtensorModule(SubtensorCall::move_stake { - origin_hotkey: hotkey, - destination_hotkey: hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { - destination_coldkey: new_coldkey, - hotkey, - origin_netuid: netuid, - destination_netuid: netuid, - alpha_amount: stake.into(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::remove_stake { - hotkey, - netuid, - amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), - }), - RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit { - hotkey, - netuid, - amount_unstaked: (stake * 2).into(), - limit_price: 123456789.into(), - allow_partial: true, - }), - RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }), - RuntimeCall::Balances(BalancesCall::transfer_all { - dest: new_coldkey, - keep_alive: false, - }), - RuntimeCall::Balances(BalancesCall::transfer_keep_alive { - dest: new_coldkey, - value: 100_000_000_000, - }), - RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: new_coldkey, - value: 100_000_000_000, - }), - ]; - - // Forbidden calls through direct origin - for call in &forbidden_calls { - assert_eq!( - ext_validate(who, call.clone()).unwrap_err(), - CustomTransactionError::ColdkeySwapAnnounced.into() - ); - } - - let delegate = U256::from(2); - - // Forbidden calls through proxy - for call in &forbidden_calls { - let proxy_calls = build_proxy_calls(who, delegate, call.clone()); - for proxy_call in proxy_calls { - assert_eq!( - ext_validate(delegate, proxy_call.clone()).unwrap_err(), - CustomTransactionError::ColdkeySwapAnnounced.into() - ); - } - } - - let authorized_calls: Vec = vec![ - RuntimeCall::SubtensorModule(SubtensorCall::announce_coldkey_swap { - new_coldkey_hash: another_coldkey_hash, - }), - RuntimeCall::SubtensorModule(SubtensorCall::swap_coldkey_announced { new_coldkey }), - RuntimeCall::SubtensorModule(SubtensorCall::dispute_coldkey_swap {}), - RuntimeCall::Shield(pallet_shield::Call::submit_encrypted { - commitment: ::Hashing::hash_of(&new_coldkey), - ciphertext: BoundedVec::truncate_from(vec![1, 2, 3, 4]), - }), - ]; - - // Authorized calls through direct origin - for call in &authorized_calls { - let (_, _, origin) = ext_validate(who, call.clone()).unwrap(); - assert_eq!(origin.as_signer(), Some(&who)); - } - - // Authorized calls through proxy - for call in &authorized_calls { - let proxy_calls = build_proxy_calls(who, delegate, call.clone()); - for proxy_call in proxy_calls { - let (_, _, origin) = ext_validate(delegate, proxy_call.clone()).unwrap(); - assert_eq!(origin.as_signer(), Some(&delegate)); - } - } - - ColdkeySwapDisputes::::insert(who, now); - - // All calls should fail when the coldkey swap is disputed - let all_calls = forbidden_calls.iter().chain(authorized_calls.iter()); - - // All calls through direct origin during dispute - for call in all_calls.clone() { - assert_eq!( - ext_validate(who, call.clone()).unwrap_err(), - CustomTransactionError::ColdkeySwapDisputed.into() - ); - } - - // All calls through proxy during dispute - for call in all_calls { - let proxy_calls = build_proxy_calls(who, delegate, call.clone()); - for proxy_call in proxy_calls { - assert_eq!( - ext_validate(delegate, proxy_call.clone()).unwrap_err(), - CustomTransactionError::ColdkeySwapDisputed.into() - ); - } - } - }) - } - - fn build_proxy_calls(who: U256, delegate: U256, call: RuntimeCall) -> Vec { - vec![ - RuntimeCall::Proxy(ProxyCall::proxy { - real: who, - force_proxy_type: None, - call: Box::new(call.clone()), - }), - RuntimeCall::Proxy(ProxyCall::proxy_announced { - delegate, - real: who, - force_proxy_type: None, - call: Box::new(call.clone()), - }), - ] - } - - fn ext_validate(who: U256, call: RuntimeCall) -> ValidateResult<(), RuntimeCall> { - let info = call.get_dispatch_info(); - let len = 0_usize; - - CheckColdkeySwap::::new().validate( - RuntimeOrigin::signed(who).into(), - &call.clone(), - &info, - len, - (), - &TxBaseImplication(call), - TransactionSource::External, - ) - } -} diff --git a/pallets/subtensor/src/extensions/mod.rs b/pallets/subtensor/src/extensions/mod.rs deleted file mode 100644 index d77987c583..0000000000 --- a/pallets/subtensor/src/extensions/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod check_coldkey_swap; -mod subtensor; - -pub use check_coldkey_swap::*; -pub use subtensor::*; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c137b92371..6ae43ac384 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1,7 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "512"] #![allow(clippy::too_many_arguments)] -#![allow(clippy::zero_prefixed_literal)] // Edit this file to define custom logic or remove it if it is not needed. // Learn more about FRAME and the core library of Substrate FRAME pallets: // @@ -36,7 +35,6 @@ mod benchmarks; // ========================= pub mod coinbase; pub mod epoch; -pub mod extensions; pub mod macros; pub mod migrations; pub mod rpc_info; @@ -47,10 +45,9 @@ pub mod utils; use crate::utils::rate_limiting::{Hyperparameter, TransactionType}; use macros::{config, dispatches, errors, events, genesis, hooks}; -pub use extensions::*; - #[cfg(test)] mod tests; +pub mod transaction_extension; // apparently this is stabilized since rust 1.36 extern crate alloc; @@ -947,16 +944,16 @@ pub mod pallet { (45875, 58982) } - /// Default value for coldkey swap announcement delay. + /// Default value for coldkey swap schedule duration #[pallet::type_value] - pub fn DefaultColdkeySwapAnnouncementDelay() -> BlockNumberFor { - T::InitialColdkeySwapAnnouncementDelay::get() + pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { + T::InitialColdkeySwapScheduleDuration::get() } - /// Default value for coldkey swap reannouncement delay. + /// Default value for coldkey swap reschedule duration #[pallet::type_value] - pub fn DefaultColdkeySwapReannouncementDelay() -> BlockNumberFor { - T::InitialColdkeySwapReannouncementDelay::get() + pub fn DefaultColdkeySwapRescheduleDuration() -> BlockNumberFor { + T::InitialColdkeySwapRescheduleDuration::get() } /// Default value for applying pending items (e.g. childkeys). @@ -1021,6 +1018,15 @@ pub mod pallet { 360 } + /// Default value for coldkey swap scheduled + #[pallet::type_value] + pub fn DefaultColdkeySwapScheduled() -> (BlockNumberFor, T::AccountId) { + #[allow(clippy::expect_used)] + let default_account = T::AccountId::decode(&mut TrailingZeroInput::zeroes()) + .expect("trailing zeroes always produce a valid account ID; qed"); + (BlockNumberFor::::from(0_u32), default_account) + } + /// Default value for setting subnet owner hotkey rate limit #[pallet::type_value] pub fn DefaultSetSNOwnerHotkeyRateLimit() -> u64 { @@ -1077,6 +1083,16 @@ pub mod pallet { pub type OwnerHyperparamRateLimit = StorageValue<_, u16, ValueQuery, DefaultOwnerHyperparamRateLimit>; + /// Duration of coldkey swap schedule before execution + #[pallet::storage] + pub type ColdkeySwapScheduleDuration = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; + + /// Duration of coldkey swap reschedule before execution + #[pallet::storage] + pub type ColdkeySwapRescheduleDuration = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapRescheduleDuration>; + /// Duration of dissolve network schedule before execution #[pallet::storage] pub type DissolveNetworkScheduleDuration = @@ -1289,6 +1305,11 @@ pub mod pallet { pub type SubnetTAO = StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; + /// --- MAP ( netuid ) --> tao_in_user_subnet | Returns the amount of TAO in the subnet reserve provided by users as liquidity. + #[pallet::storage] + pub type SubnetTaoProvided = + StorageMap<_, Identity, NetUid, TaoCurrency, ValueQuery, DefaultZeroTao>; + /// --- MAP ( netuid ) --> alpha_in_emission | Returns the amount of alph in emission into the pool per block. #[pallet::storage] pub type SubnetAlphaInEmission = @@ -1309,6 +1330,11 @@ pub mod pallet { pub type SubnetAlphaIn = StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; + /// --- MAP ( netuid ) --> alpha_supply_user_in_pool | Returns the amount of alpha in the pool provided by users as liquidity. + #[pallet::storage] + pub type SubnetAlphaInProvided = + StorageMap<_, Identity, NetUid, AlphaCurrency, ValueQuery, DefaultZeroAlpha>; + /// --- MAP ( netuid ) --> alpha_supply_in_subnet | Returns the amount of alpha in the subnet. #[pallet::storage] pub type SubnetAlphaOut = @@ -1348,27 +1374,16 @@ pub mod pallet { ValueQuery, >; - /// The delay after an announcement before a coldkey swap can be performed. - #[pallet::storage] - pub type ColdkeySwapAnnouncementDelay = - StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapAnnouncementDelay>; - - /// The delay after the initial delay has passed before a new announcement can be made. + /// --- DMAP ( cold ) --> (block_expected, new_coldkey), Maps coldkey to the block to swap at and new coldkey. #[pallet::storage] - pub type ColdkeySwapReannouncementDelay = - StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapReannouncementDelay>; - - /// A map of the coldkey swap announcements from a coldkey - /// to the block number the coldkey swap can be performed. - #[pallet::storage] - pub type ColdkeySwapAnnouncements = - StorageMap<_, Twox64Concat, T::AccountId, (BlockNumberFor, T::Hash), OptionQuery>; - - /// A map of the coldkey swap disputes from a coldkey to the - /// block number the coldkey swap was disputed. - #[pallet::storage] - pub type ColdkeySwapDisputes = - StorageMap<_, Twox64Concat, T::AccountId, BlockNumberFor, OptionQuery>; + pub type ColdkeySwapScheduled = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + (BlockNumberFor, T::AccountId), + ValueQuery, + DefaultColdkeySwapScheduled, + >; /// --- DMAP ( hot, netuid ) --> alpha | Returns the total amount of alpha a hotkey owns. #[pallet::storage] @@ -1879,52 +1894,8 @@ pub mod pallet { pub type SubtokenEnabled = StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse>; - // ======================================= - // ==== VotingPower Storage ==== - // ======================================= - - #[pallet::type_value] - /// Default VotingPower EMA alpha value (0.1 represented as u64 with 18 decimals) - /// alpha = 0.1 means slow response, 10% weight to new values per epoch - pub fn DefaultVotingPowerEmaAlpha() -> u64 { - 0_003_570_000_000_000_000 // 0.00357 * 10^18 = 2 weeks e-folding (time-constant) @ 361 - // blocks per tempo - // After 2 weeks -> EMA reaches 63.2% of a step change - // After ~4 weeks -> 86.5% - // After ~6 weeks -> 95% - } - - #[pallet::storage] - /// --- DMAP ( netuid, hotkey ) --> voting_power | EMA of stake for voting - /// This tracks stake EMA updated every epoch when VotingPowerTrackingEnabled is true. - /// Used by smart contracts to determine validator voting power for subnet governance. - pub type VotingPower = - StorageDoubleMap<_, Identity, NetUid, Blake2_128Concat, T::AccountId, u64, ValueQuery>; - - #[pallet::storage] - /// --- MAP ( netuid ) --> bool | Whether voting power tracking is enabled for this subnet. - /// When enabled, VotingPower EMA is updated every epoch. Default is false. - /// When disabled with disable_at_block set, tracking continues until that block. - pub type VotingPowerTrackingEnabled = - StorageMap<_, Identity, NetUid, bool, ValueQuery, DefaultFalse>; - - #[pallet::storage] - /// --- MAP ( netuid ) --> block_number | Block at which voting power tracking will be disabled. - /// When set (non-zero), tracking continues until this block, then automatically disables - /// and clears VotingPower entries for the subnet. Provides a 14-day grace period. - pub type VotingPowerDisableAtBlock = - StorageMap<_, Identity, NetUid, u64, ValueQuery>; - - #[pallet::storage] - /// --- MAP ( netuid ) --> u64 | EMA alpha value for voting power calculation. - /// Higher alpha = faster response to stake changes. - /// Stored as u64 with 18 decimal precision (1.0 = 10^18). - /// Only settable by sudo/root. - pub type VotingPowerEmaAlpha = - StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultVotingPowerEmaAlpha>; - - #[pallet::type_value] /// Default value for burn keys limit + #[pallet::type_value] pub fn DefaultImmuneOwnerUidsLimit() -> u16 { 1 } @@ -2357,17 +2328,12 @@ pub mod pallet { MechId::from(1) } - /// -- ITEM (Maximum number of mechanisms) + /// -- ITEM (Maximum number of sub-subnets) #[pallet::type_value] - pub fn DefaultMaxMechanismCount() -> MechId { + pub fn MaxMechanismCount() -> MechId { MechId::from(2) } - /// ITEM( max_mechanism_count ) - #[pallet::storage] - pub type MaxMechanismCount = - StorageValue<_, MechId, ValueQuery, DefaultMaxMechanismCount>; - /// -- ITEM (Rate limit for mechanism count updates) #[pallet::type_value] pub fn MechanismCountSetRateLimit() -> u64 { @@ -2475,7 +2441,7 @@ pub mod pallet { #[derive(Debug, PartialEq)] pub enum CustomTransactionError { - ColdkeySwapAnnounced, + ColdkeyInSwapSchedule, StakeAmountTooLow, BalanceTooLow, SubnetNotExists, @@ -2497,14 +2463,12 @@ pub enum CustomTransactionError { InputLengthsUnequal, UidNotFound, EvmKeyAssociateRateLimitExceeded, - ColdkeySwapDisputed, - InvalidRealAccount, } impl From for u8 { fn from(variant: CustomTransactionError) -> u8 { match variant { - CustomTransactionError::ColdkeySwapAnnounced => 0, + CustomTransactionError::ColdkeyInSwapSchedule => 0, CustomTransactionError::StakeAmountTooLow => 1, CustomTransactionError::BalanceTooLow => 2, CustomTransactionError::SubnetNotExists => 3, @@ -2526,8 +2490,6 @@ impl From for u8 { CustomTransactionError::InputLengthsUnequal => 18, CustomTransactionError::UidNotFound => 19, CustomTransactionError::EvmKeyAssociateRateLimitExceeded => 20, - CustomTransactionError::ColdkeySwapDisputed => 21, - CustomTransactionError::InvalidRealAccount => 22, } } } @@ -2552,7 +2514,7 @@ pub struct TaoCurrencyReserve(PhantomData); impl CurrencyReserve for TaoCurrencyReserve { #![deny(clippy::expect_used)] fn reserve(netuid: NetUid) -> TaoCurrency { - SubnetTAO::::get(netuid) + SubnetTAO::::get(netuid).saturating_add(SubnetTaoProvided::::get(netuid)) } fn increase_provided(netuid: NetUid, tao: TaoCurrency) { @@ -2570,7 +2532,7 @@ pub struct AlphaCurrencyReserve(PhantomData); impl CurrencyReserve for AlphaCurrencyReserve { #![deny(clippy::expect_used)] fn reserve(netuid: NetUid) -> AlphaCurrency { - SubnetAlphaIn::::get(netuid) + SubnetAlphaIn::::get(netuid).saturating_add(SubnetAlphaInProvided::::get(netuid)) } fn increase_provided(netuid: NetUid, alpha: AlphaCurrency) { @@ -2712,9 +2674,6 @@ pub enum RateLimitKey { // Last tx block delegate key limit per account ID #[codec(index = 5)] LastTxBlockDelegateTake(AccountId), - // "Add stake and burn" rate limit - #[codec(index = 6)] - AddStakeBurn(NetUid), } pub trait ProxyInterface { diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 528e5c9fd5..2124ec5f3f 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -8,7 +8,6 @@ mod config { use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha}; use pallet_commitments::GetCommitments; - use subtensor_runtime_common::AuthorshipInfo; use subtensor_swap_interface::{SwapEngine, SwapHandler}; /// Configure the pallet by specifying the parameters and types on which it depends. @@ -60,9 +59,6 @@ mod config { /// Rate limit for associating an EVM key. type EvmKeyAssociateRateLimit: Get; - /// Provider of current block author - type AuthorshipProvider: AuthorshipInfo; - /// ================================= /// ==== Initial Value Constants ==== /// ================================= @@ -218,14 +214,16 @@ mod config { #[pallet::constant] type LiquidAlphaOn: Get; /// A flag to indicate if Yuma3 is enabled. - #[pallet::constant] type Yuma3On: Get; - /// Coldkey swap announcement delay. + // /// Initial hotkey emission tempo. + // #[pallet::constant] + // type InitialHotkeyEmissionTempo: Get; + /// Coldkey swap schedule duartion. #[pallet::constant] - type InitialColdkeySwapAnnouncementDelay: Get>; - /// Coldkey swap reannouncement delay. + type InitialColdkeySwapScheduleDuration: Get>; + /// Coldkey swap reschedule duration. #[pallet::constant] - type InitialColdkeySwapReannouncementDelay: Get>; + type InitialColdkeySwapRescheduleDuration: Get>; /// Dissolve network schedule duration #[pallet::constant] type InitialDissolveNetworkScheduleDuration: Get>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 6330e07d1c..5c5d5ed1a7 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -6,10 +6,11 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod dispatches { use crate::subnets::leasing::SubnetLeasingWeightInfo; + use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_system::pallet_prelude::BlockNumberFor; use sp_core::ecdsa::Signature; - use sp_runtime::{Percent, Saturating, traits::Hash}; + use sp_runtime::{Percent, traits::Saturating}; use crate::MAX_CRV3_COMMIT_SIZE_BYTES; use crate::MAX_NUM_ROOT_CLAIMS; @@ -710,16 +711,16 @@ mod dispatches { /// - Errors stemming from transaction pallet. /// #[pallet::call_index(2)] - #[pallet::weight((Weight::from_parts(523_200_000, 0) - .saturating_add(T::DbWeight::get().reads(20_u64)) - .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(340_800_000, 0) + .saturating_add(T::DbWeight::get().reads(25_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( origin: OriginFor, hotkey: T::AccountId, netuid: NetUid, amount_staked: TaoCurrency, ) -> DispatchResult { - Self::do_add_stake(origin, hotkey, netuid, amount_staked).map(|_| ()) + Self::do_add_stake(origin, hotkey, netuid, amount_staked) } /// Remove stake from the staking account. The call must be made @@ -1040,9 +1041,9 @@ mod dispatches { /// User register a new subnetwork via burning token #[pallet::call_index(7)] - #[pallet::weight((Weight::from_parts(315_200_000, 0) - .saturating_add(T::DbWeight::get().reads(34_u64)) - .saturating_add(T::DbWeight::get().writes(29_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(354_200_000, 0) + .saturating_add(T::DbWeight::get().reads(47_u64)) + .saturating_add(T::DbWeight::get().writes(40_u64)), DispatchClass::Normal, Pays::Yes))] pub fn burned_register( origin: OriginFor, netuid: NetUid, @@ -1054,7 +1055,7 @@ mod dispatches { /// The extrinsic for user to change its hotkey in subnet or all subnets. #[pallet::call_index(70)] #[pallet::weight((Weight::from_parts(275_300_000, 0) - .saturating_add(T::DbWeight::get().reads(52_u64)) + .saturating_add(T::DbWeight::get().reads(50_u64)) .saturating_add(T::DbWeight::get().writes(35_u64)), DispatchClass::Normal, Pays::No))] pub fn swap_hotkey( origin: OriginFor, @@ -1065,9 +1066,21 @@ mod dispatches { Self::do_swap_hotkey(origin, &hotkey, &new_hotkey, netuid) } - /// Performs an arbitrary coldkey swap for any coldkey. + /// The extrinsic for user to change the coldkey associated with their account. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, must be signed by the old coldkey. + /// * `old_coldkey` - The current coldkey associated with the account. + /// * `new_coldkey` - The new coldkey to be associated with the account. + /// + /// # Returns /// - /// Only callable by root as it doesn't require an announcement and can be used to swap any coldkey. + /// Returns a `DispatchResultWithPostInfo` indicating success or failure of the operation. + /// + /// # Weight + /// + /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(71)] #[pallet::weight(Weight::from_parts(161_700_000, 0) .saturating_add(T::DbWeight::get().reads(16_u64)) @@ -1077,19 +1090,12 @@ mod dispatches { old_coldkey: T::AccountId, new_coldkey: T::AccountId, swap_cost: TaoCurrency, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { + // Ensure it's called with root privileges (scheduler has root privileges) ensure_root(origin)?; + log::debug!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); - if swap_cost.to_u64() > 0 { - Self::charge_swap_cost(&old_coldkey, swap_cost)?; - } - Self::do_swap_coldkey(&old_coldkey, &new_coldkey)?; - - // We also clear any announcement or dispute for security reasons - ColdkeySwapAnnouncements::::remove(&old_coldkey); - ColdkeySwapDisputes::::remove(old_coldkey); - - Ok(()) + Self::do_swap_coldkey(&old_coldkey, &new_coldkey, swap_cost) } /// Sets the childkey take for a given hotkey. @@ -1211,9 +1217,9 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] - #[pallet::weight((Weight::from_parts(238_500_000, 0) + #[pallet::weight((Weight::from_parts(235_400_000, 0) .saturating_add(T::DbWeight::get().reads(36_u64)) - .saturating_add(T::DbWeight::get().writes(50_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(52_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1315,15 +1321,94 @@ mod dispatches { /// Schedules a coldkey swap operation to be executed at a future block. /// - /// WARNING: This function is deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap` + /// This function allows a user to schedule the swapping of their coldkey to a new one + /// at a specified future block. The swap is not executed immediately but is scheduled + /// to occur at the specified block number. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which should be signed by the current coldkey owner. + /// * `new_coldkey` - The account ID of the new coldkey that will replace the current one. + /// * `when` - The block number at which the coldkey swap should be executed. + /// + /// # Returns + /// + /// Returns a `DispatchResultWithPostInfo` indicating whether the scheduling was successful. + /// + /// # Errors + /// + /// This function may return an error if: + /// * The origin is not signed. + /// * The scheduling fails due to conflicts or system constraints. + /// + /// # Notes + /// + /// - The actual swap is not performed by this function. It merely schedules the swap operation. + /// - The weight of this call is set to a fixed value and may need adjustment based on benchmarking. + /// + /// # TODO + /// + /// - Implement proper weight calculation based on the complexity of the operation. + /// - Consider adding checks to prevent scheduling too far into the future. + /// TODO: Benchmark this call #[pallet::call_index(73)] - #[pallet::weight(T::DbWeight::get().reads(5))] - #[deprecated(note = "Deprecated, please migrate to `announce_coldkey_swap`/`coldkey_swap`")] + #[pallet::weight((Weight::from_parts(37_830_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::Yes))] pub fn schedule_swap_coldkey( - _origin: OriginFor, - _new_coldkey: T::AccountId, - ) -> DispatchResult { - Err(Error::::Deprecated.into()) + origin: OriginFor, + new_coldkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let current_block = >::block_number(); + + // If the coldkey has a scheduled swap, check if we can reschedule it + if ColdkeySwapScheduled::::contains_key(&who) { + let (scheduled_block, _scheduled_coldkey) = ColdkeySwapScheduled::::get(&who); + let reschedule_duration = ColdkeySwapRescheduleDuration::::get(); + let redo_when = scheduled_block.saturating_add(reschedule_duration); + ensure!(redo_when <= current_block, Error::::SwapAlreadyScheduled); + } + + // Calculate the swap cost and ensure sufficient balance + let swap_cost = Self::get_key_swap_cost(); + ensure!( + Self::can_remove_balance_from_coldkey_account(&who, swap_cost.into()), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + + let current_block: BlockNumberFor = >::block_number(); + let duration: BlockNumberFor = ColdkeySwapScheduleDuration::::get(); + let when: BlockNumberFor = current_block.saturating_add(duration); + + let call = Call::::swap_coldkey { + old_coldkey: who.clone(), + new_coldkey: new_coldkey.clone(), + swap_cost, + }; + + let bound_call = ::Preimages::bound(LocalCallOf::::from(call.clone())) + .map_err(|_| Error::::FailedToSchedule)?; + + T::Scheduler::schedule( + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + bound_call, + ) + .map_err(|_| Error::::FailedToSchedule)?; + + ColdkeySwapScheduled::::insert(&who, (when, new_coldkey.clone())); + // Emit the SwapScheduled event + Self::deposit_event(Event::ColdkeySwapScheduled { + old_coldkey: who.clone(), + new_coldkey: new_coldkey.clone(), + execution_block: when, + swap_cost, + }); + + Ok(().into()) } /// ---- Set prometheus information for the neuron. @@ -1420,9 +1505,9 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] - #[pallet::weight((Weight::from_parts(235_700_000, 0) + #[pallet::weight((Weight::from_parts(234_200_000, 0) .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(49_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(51_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -1490,9 +1575,9 @@ mod dispatches { /// * `TxRateLimitExceeded`: /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] - #[pallet::weight((Weight::from_parts(486_500_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) - .saturating_add(T::DbWeight::get().writes(23_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(358_500_000, 0) + .saturating_add(T::DbWeight::get().reads(41_u64)) + .saturating_add(T::DbWeight::get().writes(26_u64)), DispatchClass::Normal, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) } @@ -1519,8 +1604,8 @@ mod dispatches { /// - The alpha stake amount to move. /// #[pallet::call_index(85)] - #[pallet::weight((Weight::from_parts(168_200_000, 0) - .saturating_add(T::DbWeight::get().reads(16_u64)) + #[pallet::weight((Weight::from_parts(164_300_000, 0) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Normal, Pays::Yes))] pub fn move_stake( origin: T::RuntimeOrigin, @@ -1562,8 +1647,8 @@ mod dispatches { /// # Events /// May emit a `StakeTransferred` event on success. #[pallet::call_index(86)] - #[pallet::weight((Weight::from_parts(163_400_000, 0) - .saturating_add(T::DbWeight::get().reads(14_u64)) + #[pallet::weight((Weight::from_parts(160_300_000, 0) + .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Normal, Pays::Yes))] pub fn transfer_stake( origin: T::RuntimeOrigin, @@ -1604,9 +1689,9 @@ mod dispatches { /// May emit a `StakeSwapped` event on success. #[pallet::call_index(87)] #[pallet::weight(( - Weight::from_parts(453_800_000, 0) - .saturating_add(T::DbWeight::get().reads(31_u64)) - .saturating_add(T::DbWeight::get().writes(21_u64)), + Weight::from_parts(351_300_000, 0) + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes ))] @@ -1669,9 +1754,9 @@ mod dispatches { /// - Errors stemming from transaction pallet. /// #[pallet::call_index(88)] - #[pallet::weight((Weight::from_parts(713_200_000, 0) - .saturating_add(T::DbWeight::get().reads(20_u64)) - .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(402_900_000, 0) + .saturating_add(T::DbWeight::get().reads(25_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake_limit( origin: OriginFor, hotkey: T::AccountId, @@ -1688,7 +1773,6 @@ mod dispatches { limit_price, allow_partial, ) - .map(|_| ()) } /// --- Removes stake from a hotkey on a subnet with a price limit. @@ -1734,9 +1818,9 @@ mod dispatches { /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. /// #[pallet::call_index(89)] - #[pallet::weight((Weight::from_parts(611_100_000, 0) - .saturating_add(T::DbWeight::get().reads(23_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(377_400_000, 0) + .saturating_add(T::DbWeight::get().reads(29_u64)) + .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_limit( origin: OriginFor, hotkey: T::AccountId, @@ -1778,9 +1862,9 @@ mod dispatches { /// May emit a `StakeSwapped` event on success. #[pallet::call_index(90)] #[pallet::weight(( - Weight::from_parts(661_800_000, 0) - .saturating_add(T::DbWeight::get().reads(31_u64)) - .saturating_add(T::DbWeight::get().writes(21_u64)), + Weight::from_parts(411_500_000, 0) + .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().writes(24_u64)), DispatchClass::Normal, Pays::Yes ))] @@ -1956,9 +2040,9 @@ mod dispatches { /// at which or better (higher) the staking should execute. /// Without limit_price it remove all the stake similar to `remove_stake` extrinsic #[pallet::call_index(103)] - #[pallet::weight((Weight::from_parts(615_000_000, 10142) - .saturating_add(T::DbWeight::get().reads(23_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)), DispatchClass::Normal, Pays::Yes))] + #[pallet::weight((Weight::from_parts(395_300_000, 10142) + .saturating_add(T::DbWeight::get().reads(29_u64)) + .saturating_add(T::DbWeight::get().writes(15_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_full_limit( origin: T::RuntimeOrigin, hotkey: T::AccountId, @@ -2332,242 +2416,5 @@ mod dispatches { Ok(()) } - - /// Announces a coldkey swap using BlakeTwo256 hash of the new coldkey. - /// - /// This is required before the coldkey swap can be performed - /// after the delay period. - /// - /// It can be reannounced after a delay of `ColdkeySwapReannouncementDelay` following - /// the first valid execution block of the original announcement. - /// - /// The dispatch origin of this call must be the original coldkey that made the announcement. - /// - /// - `new_coldkey_hash`: The hash of the new coldkey using BlakeTwo256. - /// - /// The `ColdkeySwapAnnounced` event is emitted on successful announcement. - /// - #[pallet::call_index(125)] - #[pallet::weight( - Weight::from_parts(55_700_000, 0) - .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - )] - pub fn announce_coldkey_swap( - origin: OriginFor, - new_coldkey_hash: T::Hash, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - let now = >::block_number(); - - if let Some((when, _)) = ColdkeySwapAnnouncements::::get(who.clone()) { - let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); - let new_when = when.saturating_add(reannouncement_delay); - ensure!(now >= new_when, Error::::ColdkeySwapReannouncedTooEarly); - } else { - // Only charge the swap cost on the first announcement - let swap_cost = Self::get_key_swap_cost(); - Self::charge_swap_cost(&who, swap_cost)?; - } - - let delay = ColdkeySwapAnnouncementDelay::::get(); - let when = now.saturating_add(delay); - ColdkeySwapAnnouncements::::insert(who.clone(), (when, new_coldkey_hash.clone())); - - Self::deposit_event(Event::ColdkeySwapAnnounced { - who, - new_coldkey_hash, - }); - Ok(()) - } - - /// Performs a coldkey swap if an announcement has been made. - /// - /// The dispatch origin of this call must be the original coldkey that made the announcement. - /// - /// - `new_coldkey`: The new coldkey to swap to. The BlakeTwo256 hash of the new coldkey must be - /// the same as the announced coldkey hash. - /// - /// The `ColdkeySwapped` event is emitted on successful swap. - #[pallet::call_index(126)] - #[pallet::weight( - Weight::from_parts(110_700_000, 0) - .saturating_add(T::DbWeight::get().reads(16_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) - )] - pub fn swap_coldkey_announced( - origin: OriginFor, - new_coldkey: T::AccountId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let (when, new_coldkey_hash) = ColdkeySwapAnnouncements::::take(who.clone()) - .ok_or(Error::::ColdkeySwapAnnouncementNotFound)?; - - ensure!( - new_coldkey_hash == T::Hashing::hash_of(&new_coldkey), - Error::::AnnouncedColdkeyHashDoesNotMatch - ); - - let now = >::block_number(); - ensure!(now >= when, Error::::ColdkeySwapTooEarly); - - Self::do_swap_coldkey(&who, &new_coldkey)?; - - Ok(()) - } - - /// Dispute a coldkey swap. - /// - /// This will prevent any further actions on the coldkey swap - /// until triumvirate step in to resolve the issue. - /// - /// - `coldkey`: The coldkey to dispute the swap for. - /// - #[pallet::call_index(127)] - #[pallet::weight( - Weight::from_parts(20_750_000, 0) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - )] - pub fn dispute_coldkey_swap(origin: OriginFor) -> DispatchResult { - let coldkey = ensure_signed(origin)?; - - ensure!( - ColdkeySwapAnnouncements::::contains_key(&coldkey), - Error::::ColdkeySwapAnnouncementNotFound - ); - ensure!( - !ColdkeySwapDisputes::::contains_key(&coldkey), - Error::::ColdkeySwapAlreadyDisputed - ); - - let now = >::block_number(); - ColdkeySwapDisputes::::insert(&coldkey, now); - - Self::deposit_event(Event::ColdkeySwapDisputed { coldkey }); - Ok(()) - } - - /// Reset a coldkey swap by clearing the announcement and dispute status. - /// - /// The dispatch origin of this call must be root. - /// - /// - `coldkey`: The coldkey to reset the swap for. - /// - #[pallet::call_index(128)] - #[pallet::weight( - Weight::from_parts(8_977_000, 0) - .saturating_add(T::DbWeight::get().reads(0_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - )] - pub fn reset_coldkey_swap(origin: OriginFor, coldkey: T::AccountId) -> DispatchResult { - ensure_root(origin)?; - - ColdkeySwapAnnouncements::::remove(&coldkey); - ColdkeySwapDisputes::::remove(&coldkey); - - Self::deposit_event(Event::ColdkeySwapReset { who: coldkey }); - Ok(()) - } - - /// Enables voting power tracking for a subnet. - /// - /// This function can be called by the subnet owner or root. - /// When enabled, voting power EMA is updated every epoch for all validators. - /// Voting power starts at 0 and increases over epochs. - /// - /// # Arguments: - /// * `origin` - The origin of the call, must be subnet owner or root. - /// * `netuid` - The subnet to enable voting power tracking for. - /// - /// # Errors: - /// * `SubnetNotExist` - If the subnet does not exist. - /// * `NotSubnetOwner` - If the caller is not the subnet owner or root. - #[pallet::call_index(129)] - #[pallet::weight(Weight::from_parts(10_000, 0) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)))] - pub fn enable_voting_power_tracking( - origin: OriginFor, - netuid: NetUid, - ) -> DispatchResult { - Self::ensure_subnet_owner_or_root(origin, netuid)?; - Self::do_enable_voting_power_tracking(netuid) - } - - /// Schedules disabling of voting power tracking for a subnet. - /// - /// This function can be called by the subnet owner or root. - /// Voting power tracking will continue for 14 days (grace period) after this call, - /// then automatically disable and clear all VotingPower entries for the subnet. - /// - /// # Arguments: - /// * `origin` - The origin of the call, must be subnet owner or root. - /// * `netuid` - The subnet to schedule disabling voting power tracking for. - /// - /// # Errors: - /// * `SubnetNotExist` - If the subnet does not exist. - /// * `NotSubnetOwner` - If the caller is not the subnet owner or root. - /// * `VotingPowerTrackingNotEnabled` - If voting power tracking is not enabled. - #[pallet::call_index(130)] - #[pallet::weight(Weight::from_parts(10_000, 0) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)))] - pub fn disable_voting_power_tracking( - origin: OriginFor, - netuid: NetUid, - ) -> DispatchResult { - Self::ensure_subnet_owner_or_root(origin, netuid)?; - Self::do_disable_voting_power_tracking(netuid) - } - - /// Sets the EMA alpha value for voting power calculation on a subnet. - /// - /// This function can only be called by root (sudo). - /// Higher alpha = faster response to stake changes. - /// Alpha is stored as u64 with 18 decimal precision (1.0 = 10^18). - /// - /// # Arguments: - /// * `origin` - The origin of the call, must be root. - /// * `netuid` - The subnet to set the alpha for. - /// * `alpha` - The new alpha value (u64 with 18 decimal precision). - /// - /// # Errors: - /// * `BadOrigin` - If the origin is not root. - /// * `SubnetNotExist` - If the subnet does not exist. - /// * `InvalidVotingPowerEmaAlpha` - If alpha is greater than 10^18 (1.0). - #[pallet::call_index(131)] - #[pallet::weight(Weight::from_parts(6_000, 0) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)))] - pub fn sudo_set_voting_power_ema_alpha( - origin: OriginFor, - netuid: NetUid, - alpha: u64, - ) -> DispatchResult { - ensure_root(origin)?; - Self::do_set_voting_power_ema_alpha(netuid, alpha) - } - - /// --- The extrinsic is a combination of add_stake(add_stake_limit) and burn_alpha. We buy - /// alpha token first and immediately burn the acquired amount of alpha (aka Subnet buyback). - #[pallet::call_index(132)] - #[pallet::weight(( - Weight::from_parts(757_700_000, 8556) - .saturating_add(T::DbWeight::get().reads(23_u64)) - .saturating_add(T::DbWeight::get().writes(15_u64)), - DispatchClass::Normal, - Pays::Yes - ))] - pub fn add_stake_burn( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - netuid: NetUid, - amount: TaoCurrency, - limit: Option, - ) -> DispatchResult { - Self::do_add_stake_burn(origin, hotkey, netuid, amount, limit) - } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 7d50373f19..6c3d7a35df 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -138,6 +138,8 @@ mod errors { ColdKeyAlreadyAssociated, /// The coldkey balance is not enough to pay for the swap NotEnoughBalanceToPaySwapColdKey, + /// The coldkey is in arbitration + ColdkeyIsInArbitration, /// Attempting to set an invalid child for a hotkey on a network. InvalidChild, /// Duplicate child when setting children. @@ -148,16 +150,10 @@ mod errors { TooManyChildren, /// Default transaction rate limit exceeded. TxRateLimitExceeded, - /// Coldkey swap announcement not found - ColdkeySwapAnnouncementNotFound, - /// Coldkey swap too early. - ColdkeySwapTooEarly, - /// Coldkey swap reannounced too early. - ColdkeySwapReannouncedTooEarly, - /// The announced coldkey hash does not match the new coldkey hash. - AnnouncedColdkeyHashDoesNotMatch, - /// Coldkey swap already disputed - ColdkeySwapAlreadyDisputed, + /// Swap already scheduled. + SwapAlreadyScheduled, + /// failed to swap coldkey + FailedToSchedule, /// New coldkey is hotkey NewColdKeyIsHotkey, /// Childkey take is invalid. @@ -270,17 +266,7 @@ mod errors { InvalidRootClaimThreshold, /// Exceeded subnet limit number or zero. InvalidSubnetNumber, - /// The maximum allowed UIDs times mechanism count should not exceed 256. - TooManyUIDsPerMechanism, - /// Voting power tracking is not enabled for this subnet. - VotingPowerTrackingNotEnabled, - /// Invalid voting power EMA alpha value (must be <= 10^18). - InvalidVotingPowerEmaAlpha, /// Unintended precision loss when unstaking alpha PrecisionLoss, - /// Deprecated call. - Deprecated, - /// "Add stake and burn" exceeded the operation rate limit - AddStakeBurnRateLimitExceeded, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 65c33aee87..c86cc1a1e5 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -172,29 +172,14 @@ mod events { MaxDelegateTakeSet(u16), /// minimum delegate take is set by sudo/admin transaction MinDelegateTakeSet(u16), - /// A coldkey swap announcement has been made. - ColdkeySwapAnnounced { - /// The account ID of the coldkey that made the announcement. - who: T::AccountId, - /// The hash of the new coldkey. - new_coldkey_hash: T::Hash, - }, - /// A coldkey swap has been reset. - ColdkeySwapReset { - /// The account ID of the coldkey for which the swap has been reset. - who: T::AccountId, - }, - /// A coldkey has been swapped. + /// A coldkey has been swapped ColdkeySwapped { - /// The account ID of old coldkey. + /// the account ID of old coldkey old_coldkey: T::AccountId, - /// The account ID of new coldkey. + /// the account ID of new coldkey new_coldkey: T::AccountId, - }, - /// A coldkey swap has been disputed. - ColdkeySwapDisputed { - /// The account ID of the coldkey that was disputed. - coldkey: T::AccountId, + /// the swap cost + swap_cost: TaoCurrency, }, /// All balance of a hotkey has been unstaked and transferred to a new coldkey AllBalanceUnstakedAndTransferredToNewColdkey { @@ -207,6 +192,17 @@ mod events { ::AccountId, >>::Balance, }, + /// A coldkey swap has been scheduled + ColdkeySwapScheduled { + /// The account ID of the old coldkey + old_coldkey: T::AccountId, + /// The account ID of the new coldkey + new_coldkey: T::AccountId, + /// The arbitration block for the coldkey swap + execution_block: BlockNumberFor, + /// The swap cost + swap_cost: TaoCurrency, + }, /// The arbitration period has been extended ArbitrationPeriodExtended { /// The account ID of the coldkey @@ -228,17 +224,15 @@ mod events { SubnetIdentityRemoved(NetUid), /// A dissolve network extrinsic scheduled. DissolveNetworkScheduled { - /// The account ID schedule the dissolve network extrinsic + /// The account ID schedule the dissolve network extrisnic account: T::AccountId, /// network ID will be dissolved netuid: NetUid, /// extrinsic execution block number execution_block: BlockNumberFor, }, - /// The coldkey swap announcement delay has been set. - ColdkeySwapAnnouncementDelaySet(BlockNumberFor), - /// The coldkey swap reannouncement delay has been set. - ColdkeySwapReannouncementDelaySet(BlockNumberFor), + /// The duration of schedule coldkey swap has been set + ColdkeySwapScheduleDurationSet(BlockNumberFor), /// The duration of dissolve network has been set DissolveNetworkScheduleDurationSet(BlockNumberFor), /// Commit-reveal v3 weights have been successfully committed. @@ -478,35 +472,6 @@ mod events { root_claim_type: RootClaimTypeEnum, }, - /// Voting power tracking has been enabled for a subnet. - VotingPowerTrackingEnabled { - /// The subnet ID - netuid: NetUid, - }, - - /// Voting power tracking has been scheduled for disabling. - /// Tracking will continue until disable_at_block, then stop and clear entries. - VotingPowerTrackingDisableScheduled { - /// The subnet ID - netuid: NetUid, - /// Block at which tracking will be disabled - disable_at_block: u64, - }, - - /// Voting power tracking has been fully disabled and entries cleared. - VotingPowerTrackingDisabled { - /// The subnet ID - netuid: NetUid, - }, - - /// Voting power EMA alpha has been set for a subnet. - VotingPowerEmaAlphaSet { - /// The subnet ID - netuid: NetUid, - /// The new alpha value (u64 with 18 decimal precision) - alpha: u64, - }, - /// Subnet lease dividends have been distributed. SubnetLeaseDividendsDistributed { /// The lease ID @@ -516,17 +481,5 @@ mod events { /// The amount of alpha distributed alpha: AlphaCurrency, }, - - /// "Add stake and burn" event: alpha token was purchased and burned. - AddStakeBurn { - /// The subnet ID - netuid: NetUid, - /// hotky account ID - hotkey: T::AccountId, - /// Tao provided - amount: TaoCurrency, - /// Alpha burned - alpha: AlphaCurrency, - }, } } diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index f16f2f7a3a..0014b63540 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -106,6 +106,7 @@ mod genesis { netuid, U64F64::saturating_from_num(1_000_000_000), ); + // TotalColdkeyAlpha::::insert(hotkey.clone(), netuid, 1_000_000_000); SubnetAlphaOut::::insert(netuid, AlphaCurrency::from(1_000_000_000)); let mut staking_hotkeys = StakingHotkeys::::get(hotkey.clone()); if !staking_hotkeys.contains(&hotkey) { diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 899e8d32f2..ed57d52c8b 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -164,9 +164,7 @@ mod hooks { // Remove unknown neuron axon, certificate prom .saturating_add(migrations::migrate_remove_unknown_neuron_axon_cert_prom::migrate_remove_unknown_neuron_axon_cert_prom::()) // Fix staking hot keys - .saturating_add(migrations::migrate_fix_staking_hot_keys::migrate_fix_staking_hot_keys::()) - // Migrate coldkey swap scheduled to announcements - .saturating_add(migrations::migrate_coldkey_swap_scheduled_to_announcements::migrate_coldkey_swap_scheduled_to_announcements::()); + .saturating_add(migrations::migrate_fix_staking_hot_keys::migrate_fix_staking_hot_keys::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_cleanup_swap_v3.rs b/pallets/subtensor/src/migrations/migrate_cleanup_swap_v3.rs deleted file mode 100644 index 13edf21033..0000000000 --- a/pallets/subtensor/src/migrations/migrate_cleanup_swap_v3.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::*; -use crate::HasMigrationRun; -use frame_support::{storage_alias, traits::Get, weights::Weight}; -use scale_info::prelude::string::String; - -pub mod deprecated_swap_maps { - use super::*; - - /// --- MAP ( netuid ) --> tao_in_user_subnet | Returns the amount of TAO in the subnet reserve provided by users as liquidity. - #[storage_alias] - pub type SubnetTaoProvided = - StorageMap, Identity, NetUid, TaoCurrency, ValueQuery>; - - /// --- MAP ( netuid ) --> alpha_supply_user_in_pool | Returns the amount of alpha in the pool provided by users as liquidity. - #[storage_alias] - pub type SubnetAlphaInProvided = - StorageMap, Identity, NetUid, AlphaCurrency, ValueQuery>; -} - -pub fn migrate_cleanup_swap_v3() -> Weight { - let migration_name = b"migrate_cleanup_swap_v3".to_vec(); - let mut weight = T::DbWeight::get().reads(1); - - if HasMigrationRun::::get(&migration_name) { - log::info!( - "Migration '{:?}' has already run. Skipping.", - String::from_utf8_lossy(&migration_name) - ); - return weight; - } - - log::info!( - "Running migration '{}'", - String::from_utf8_lossy(&migration_name), - ); - - // ------------------------------ - // Step 1: Move provided to reserves - // ------------------------------ - for (netuid, tao_provided) in deprecated_swap_maps::SubnetTaoProvided::::iter() { - SubnetTAO::::mutate(netuid, |total| { - *total = total.saturating_add(tao_provided); - }); - } - for (netuid, alpha_provided) in deprecated_swap_maps::SubnetAlphaInProvided::::iter() { - SubnetAlphaIn::::mutate(netuid, |total| { - *total = total.saturating_add(alpha_provided); - }); - } - - // ------------------------------ - // Step 2: Remove Map entries - // ------------------------------ - remove_prefix::("SubtensorModule", "SubnetTaoProvided", &mut weight); - remove_prefix::("SubtensorModule", "SubnetAlphaInProvided", &mut weight); - - // ------------------------------ - // Step 3: Mark Migration as Completed - // ------------------------------ - - HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - - log::info!( - "Migration '{:?}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight -} diff --git a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs index 243d953ac1..8854f76387 100644 --- a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs +++ b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled.rs @@ -55,6 +55,13 @@ pub fn migrate_coldkey_swap_scheduled() -> Weight { } } + let default_value = DefaultColdkeySwapScheduled::::get(); + ColdkeySwapScheduled::::translate::<(), _>(|_coldkey: AccountIdOf, _: ()| { + Some((default_value.0, default_value.1.clone())) + }); + // write once for each item in the map, no matter remove or translate + weight.saturating_accrue(T::DbWeight::get().writes(curr_keys.len() as u64)); + // ------------------------------ // Step 2: Mark Migration as Completed // ------------------------------ diff --git a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs b/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs deleted file mode 100644 index 12fa3f5768..0000000000 --- a/pallets/subtensor/src/migrations/migrate_coldkey_swap_scheduled_to_announcements.rs +++ /dev/null @@ -1,91 +0,0 @@ -use super::*; -use crate::AccountIdOf; -use frame_support::{pallet_prelude::Blake2_128Concat, traits::Get, weights::Weight}; -use frame_system::pallet_prelude::BlockNumberFor; -use scale_info::prelude::string::String; -use sp_io::storage::clear; -use sp_runtime::{Saturating, traits::Hash}; - -pub mod deprecated { - use super::*; - use frame_support::storage_alias; - - #[storage_alias] - pub type ColdkeySwapScheduleDuration = - StorageValue, BlockNumberFor, OptionQuery>; - - #[storage_alias] - pub type ColdkeySwapRescheduleDuration = - StorageValue, BlockNumberFor, OptionQuery>; - - #[storage_alias] - pub type ColdkeySwapScheduled = StorageMap< - Pallet, - Blake2_128Concat, - AccountIdOf, - (BlockNumberFor, AccountIdOf), - OptionQuery, - >; -} - -pub fn migrate_coldkey_swap_scheduled_to_announcements() -> Weight { - let migration_name = b"migrate_coldkey_swap_scheduled_to_announcements".to_vec(); - let mut weight = T::DbWeight::get().reads(1); - - if HasMigrationRun::::get(&migration_name) { - log::info!( - "Migration '{:?}' has already run. Skipping.", - String::from_utf8_lossy(&migration_name) - ); - return weight; - } - - log::info!( - "Running migration '{}'", - String::from_utf8_lossy(&migration_name) - ); - - // Remove ColdkeySwapScheduleDuration and ColdkeySwapRescheduleDuration - let pallet_name = twox_128(b"SubtensorModule"); - let storage_name1 = twox_128(b"ColdkeySwapScheduleDuration"); - let storage_name2 = twox_128(b"ColdkeySwapRescheduleDuration"); - clear(&[pallet_name, storage_name1].concat()); - clear(&[pallet_name, storage_name2].concat()); - weight.saturating_accrue(T::DbWeight::get().writes(2)); - - // Migrate the ColdkeySwapScheduled entries to ColdkeySwapAnnouncements entries - let now = >::block_number(); - let scheduled = deprecated::ColdkeySwapScheduled::::iter(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - - for (who, (when, new_coldkey)) in scheduled { - // Only migrate the scheduled coldkey swaps that are in the future - if when > now { - let coldkey_hash = ::Hashing::hash_of(&new_coldkey); - // The announcement should be at the scheduled time - delay to be able to call - // the swap_coldkey_announced call at the old scheduled time - ColdkeySwapAnnouncements::::insert(who, (when.saturating_sub(delay), coldkey_hash)); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - - let results = deprecated::ColdkeySwapScheduled::::clear(u32::MAX, None); - weight.saturating_accrue( - T::DbWeight::get().reads_writes(results.loops as u64, results.backend as u64), - ); - - // ------------------------------ - // Step 2: Mark Migration as Completed - // ------------------------------ - - HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - - log::info!( - "Migration '{:?}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight -} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 087c787424..a03da9289e 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -5,10 +5,8 @@ use sp_io::KillStorageResult; use sp_io::hashing::twox_128; use sp_io::storage::clear_prefix; pub mod migrate_auto_stake_destination; -pub mod migrate_cleanup_swap_v3; pub mod migrate_clear_rank_trust_pruning_maps; pub mod migrate_coldkey_swap_scheduled; -pub mod migrate_coldkey_swap_scheduled_to_announcements; pub mod migrate_commit_reveal_settings; pub mod migrate_commit_reveal_v2; pub mod migrate_create_root_network; diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 7169561d30..6a7966b4fb 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -365,6 +365,7 @@ impl Pallet { let subnet_token_enabled = Self::get_subtoken_enabled(netuid); let transfers_enabled = Self::get_transfer_toggle(netuid); let bonds_reset = Self::get_bonds_reset(netuid); + let user_liquidity_enabled: bool = Self::is_user_liquidity_enabled(netuid); Some(SubnetHyperparamsV2 { rho: rho.into(), @@ -399,7 +400,7 @@ impl Pallet { subnet_is_active: subnet_token_enabled, transfers_enabled, bonds_reset_enabled: bonds_reset, - user_liquidity_enabled: false, + user_liquidity_enabled, }) } diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index b0c71a3a48..ea33912bf1 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -42,7 +42,7 @@ impl Pallet { hotkey: T::AccountId, netuid: NetUid, stake_to_be_added: TaoCurrency, - ) -> Result { + ) -> dispatch::DispatchResult { // 1. We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::debug!( @@ -77,7 +77,10 @@ impl Pallet { T::SwapInterface::max_price(), true, false, - ) + )?; + + // Ok and return. + Ok(()) } /// ---- The implementation for the extrinsic add_stake_limit: Adds stake to a hotkey @@ -127,7 +130,7 @@ impl Pallet { stake_to_be_added: TaoCurrency, limit_price: TaoCurrency, allow_partial: bool, - ) -> Result { + ) -> dispatch::DispatchResult { // 1. We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::debug!( @@ -170,7 +173,10 @@ impl Pallet { limit_price, true, false, - ) + )?; + + // Ok and return. + Ok(()) } // Returns the maximum amount of RAO that can be executed with price limit diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 998d4f0086..099f8e26b6 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::{U64F64, U96F32}; +use substrate_fixed::types::U96F32; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; @@ -48,13 +48,15 @@ impl Pallet { Self::get_all_subnet_netuids() .into_iter() .map(|netuid| { - let alpha = U64F64::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( + let alpha = U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( hotkey, netuid, )); - let alpha_price = T::SwapInterface::current_alpha_price(netuid.into()); + let alpha_price = U96F32::saturating_from_num( + T::SwapInterface::current_alpha_price(netuid.into()), + ); alpha.saturating_mul(alpha_price) }) - .sum::() + .sum::() .saturating_to_num::() .into() } @@ -74,7 +76,7 @@ impl Pallet { let order = GetTaoForAlpha::::with_amount(alpha_stake); T::SwapInterface::sim_swap(netuid.into(), order) .map(|r| { - let fee: u64 = U64F64::saturating_from_num(r.fee_paid) + let fee: u64 = U96F32::saturating_from_num(r.fee_paid) .saturating_mul(T::SwapInterface::current_alpha_price( netuid.into(), )) @@ -108,7 +110,7 @@ impl Pallet { let order = GetTaoForAlpha::::with_amount(alpha_stake); T::SwapInterface::sim_swap(netuid.into(), order) .map(|r| { - let fee: u64 = U64F64::saturating_from_num(r.fee_paid) + let fee: u64 = U96F32::saturating_from_num(r.fee_paid) .saturating_mul(T::SwapInterface::current_alpha_price( netuid.into(), )) @@ -221,7 +223,7 @@ impl Pallet { let alpha_stake = Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid); let min_alpha_stake = - U64F64::saturating_from_num(Self::get_nominator_min_required_stake()) + U96F32::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() { @@ -350,6 +352,10 @@ impl Pallet { Ok(credit) } + pub fn is_user_liquidity_enabled(netuid: NetUid) -> bool { + T::SwapInterface::is_user_liquidity_enabled(netuid) + } + pub fn recycle_subnet_alpha(netuid: NetUid, amount: AlphaCurrency) { // TODO: record recycled alpha in a tracker SubnetAlphaOut::::mutate(netuid, |total| { diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 76d9c95d3a..a1d9b46d5b 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -418,8 +418,7 @@ impl Pallet { /// /// In the corner case when SubnetTAO(2) == SubnetTAO(1), no slippage is going to occur. /// - /// TODO: This formula only works for a single swap step, so it is not 100% correct for swap v3 or balancers. - /// We need an updated one. + /// TODO: This formula only works for a single swap step, so it is not 100% correct for swap v3. We need an updated one. /// pub fn get_max_amount_move( origin_netuid: NetUid, @@ -472,8 +471,10 @@ impl Pallet { } // Corner case: SubnetTAO for any of two subnets is zero - let subnet_tao_1 = SubnetTAO::::get(origin_netuid); - let subnet_tao_2 = SubnetTAO::::get(destination_netuid); + let subnet_tao_1 = SubnetTAO::::get(origin_netuid) + .saturating_add(SubnetTaoProvided::::get(origin_netuid)); + let subnet_tao_2 = SubnetTAO::::get(destination_netuid) + .saturating_add(SubnetTaoProvided::::get(destination_netuid)); if subnet_tao_1.is_zero() || subnet_tao_2.is_zero() { return Err(Error::::ZeroMaxStakeAmount.into()); } @@ -481,8 +482,10 @@ impl Pallet { let subnet_tao_2_float: U64F64 = U64F64::saturating_from_num(subnet_tao_2); // Corner case: SubnetAlphaIn for any of two subnets is zero - let alpha_in_1 = SubnetAlphaIn::::get(origin_netuid); - let alpha_in_2 = SubnetAlphaIn::::get(destination_netuid); + let alpha_in_1 = SubnetAlphaIn::::get(origin_netuid) + .saturating_add(SubnetAlphaInProvided::::get(origin_netuid)); + let alpha_in_2 = SubnetAlphaIn::::get(destination_netuid) + .saturating_add(SubnetAlphaInProvided::::get(destination_netuid)); if alpha_in_1.is_zero() || alpha_in_2.is_zero() { return Err(Error::::ZeroMaxStakeAmount.into()); } diff --git a/pallets/subtensor/src/staking/recycle_alpha.rs b/pallets/subtensor/src/staking/recycle_alpha.rs index b77982fa31..5229971ed0 100644 --- a/pallets/subtensor/src/staking/recycle_alpha.rs +++ b/pallets/subtensor/src/staking/recycle_alpha.rs @@ -134,43 +134,6 @@ impl Pallet { netuid, )); - Ok(()) - } - pub(crate) fn do_add_stake_burn( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - netuid: NetUid, - amount: TaoCurrency, - limit: Option, - ) -> DispatchResult { - Self::ensure_subnet_owner(origin.clone(), netuid)?; - - let current_block = Self::get_current_block_as_u64(); - let last_block = Self::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid)); - let rate_limit = TransactionType::AddStakeBurn.rate_limit_on_subnet::(netuid); - - ensure!( - last_block.is_zero() || current_block.saturating_sub(last_block) >= rate_limit, - Error::::AddStakeBurnRateLimitExceeded - ); - - let alpha = if let Some(limit) = limit { - Self::do_add_stake_limit(origin.clone(), hotkey.clone(), netuid, amount, limit, false)? - } else { - Self::do_add_stake(origin.clone(), hotkey.clone(), netuid, amount)? - }; - - Self::do_burn_alpha(origin, hotkey.clone(), alpha, netuid)?; - - Self::set_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid), current_block); - - Self::deposit_event(Event::AddStakeBurn { - netuid, - hotkey, - amount, - alpha, - }); - Ok(()) } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 735ec804df..74a6bf34a6 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -463,9 +463,7 @@ impl Pallet { .saturating_to_num::(); owner_emission_tao = if owner_alpha_u64 > 0 { - let cur_price: U96F32 = U96F32::saturating_from_num( - T::SwapInterface::current_alpha_price(netuid.into()), - ); + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); let val_u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() @@ -583,6 +581,7 @@ impl Pallet { } // 7.c) Remove α‑in/α‑out counters (fully destroyed). SubnetAlphaIn::::remove(netuid); + SubnetAlphaInProvided::::remove(netuid); SubnetAlphaOut::::remove(netuid); // Clear the locked balance on the subnet. diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index d030ee2d76..1aeeacc33c 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -3,7 +3,7 @@ use safe_math::*; use share_pool::{SharePool, SharePoolDataOperations}; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; -use subtensor_runtime_common::{AlphaCurrency, AuthorshipInfo, Currency, NetUid, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler, SwapResult}; impl Pallet { @@ -18,7 +18,13 @@ impl Pallet { /// # Returns /// * `u64` - The total alpha issuance for the specified subnet. pub fn get_alpha_issuance(netuid: NetUid) -> AlphaCurrency { - SubnetAlphaIn::::get(netuid).saturating_add(SubnetAlphaOut::::get(netuid)) + SubnetAlphaIn::::get(netuid) + .saturating_add(SubnetAlphaInProvided::::get(netuid)) + .saturating_add(SubnetAlphaOut::::get(netuid)) + } + + pub fn get_protocol_tao(netuid: NetUid) -> TaoCurrency { + T::SwapInterface::get_protocol_tao(netuid) } pub fn get_moving_alpha_price(netuid: NetUid) -> U96F32 { @@ -57,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(U96F32::saturating_from_num( + let current_price: U96F32 = alpha.saturating_mul( T::SwapInterface::current_alpha_price(netuid.into()) - .min(U64F64::saturating_from_num(1.0)), - )); + .min(U96F32::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`` @@ -590,7 +596,6 @@ impl Pallet { amount_paid_in: tao, amount_paid_out: tao.to_u64().into(), fee_paid: TaoCurrency::ZERO, - fee_to_block_author: TaoCurrency::ZERO, } }; @@ -644,17 +649,19 @@ impl Pallet { amount_paid_in: alpha, amount_paid_out: alpha.to_u64().into(), fee_paid: AlphaCurrency::ZERO, - fee_to_block_author: AlphaCurrency::ZERO, } }; - // Increase only the protocol Alpha reserve + // Increase only the protocol Alpha reserve. We only use the sum of + // (SubnetAlphaIn + SubnetAlphaInProvided) in alpha_reserve(), so it is irrelevant + // which one to increase. let alpha_delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs(); SubnetAlphaIn::::mutate(netuid, |total| { *total = total.saturating_add(alpha_delta.into()); }); // Decrease Alpha outstanding. + // TODO: Deprecate, not accurate in v3 anymore SubnetAlphaOut::::mutate(netuid, |total| { *total = total.saturating_sub(alpha_delta.into()); }); @@ -705,26 +712,6 @@ impl Pallet { Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, refund); } - // Swap (in a fee-less way) the block builder alpha fee - let mut fee_outflow = 0_u64; - let maybe_block_author_coldkey = T::AuthorshipProvider::author(); - if let Some(block_author_coldkey) = maybe_block_author_coldkey { - let bb_swap_result = Self::swap_alpha_for_tao( - netuid, - swap_result.fee_to_block_author, - T::SwapInterface::min_price::(), - true, - )?; - Self::add_balance_to_coldkey_account( - &block_author_coldkey, - bb_swap_result.amount_paid_out.into(), - ); - fee_outflow = bb_swap_result.amount_paid_out.into(); - } else { - // block author is not found, burn this alpha - Self::burn_subnet_alpha(netuid, swap_result.fee_to_block_author); - } - // If this is a root-stake if netuid == NetUid::ROOT { // Adjust root claimed value for this hotkey and coldkey. @@ -740,12 +727,7 @@ impl Pallet { // } // Record TAO outflow - Self::record_tao_outflow( - netuid, - swap_result - .amount_paid_out - .saturating_add(fee_outflow.into()), - ); + Self::record_tao_outflow(netuid, swap_result.amount_paid_out.into()); LastColdkeyHotkeyStakeBlock::::insert(coldkey, hotkey, Self::get_current_block_as_u64()); @@ -821,21 +803,6 @@ impl Pallet { StakingHotkeys::::insert(coldkey, staking_hotkeys.clone()); } - // Increase the balance of the block author - let maybe_block_author_coldkey = T::AuthorshipProvider::author(); - if let Some(block_author_coldkey) = maybe_block_author_coldkey { - Self::add_balance_to_coldkey_account( - &block_author_coldkey, - swap_result.fee_to_block_author.into(), - ); - } else { - // Block author is not found - burn this TAO - // Pallet balances total issuance was taken care of when balance was withdrawn for this swap - TotalIssuance::::mutate(|ti| { - *ti = ti.saturating_sub(swap_result.fee_to_block_author); - }); - } - // Record TAO inflow Self::record_tao_inflow(netuid, swap_result.amount_paid_in.into()); @@ -923,7 +890,7 @@ impl Pallet { let current_price = ::SwapInterface::current_alpha_price(netuid.into()); let tao_equivalent: TaoCurrency = current_price - .saturating_mul(U64F64::saturating_from_num(actual_alpha_moved)) + .saturating_mul(U96F32::saturating_from_num(actual_alpha_moved)) .saturating_to_num::() .into(); @@ -1252,34 +1219,42 @@ impl Pallet { } pub fn increase_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency) { - if !tao.is_zero() { - SubnetTAO::::mutate(netuid, |total| { - *total = total.saturating_add(tao); - }); - } + SubnetTaoProvided::::mutate(netuid, |total| { + *total = total.saturating_add(tao); + }); } pub fn decrease_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency) { - if !tao.is_zero() { - SubnetTAO::::mutate(netuid, |total| { - *total = total.saturating_sub(tao); - }); + // First, decrease SubnetTaoProvided, then deduct the rest from SubnetTAO + let subnet_tao = SubnetTAO::::get(netuid); + let subnet_tao_provided = SubnetTaoProvided::::get(netuid); + let remainder = subnet_tao_provided.saturating_sub(tao); + let carry_over = tao.saturating_sub(subnet_tao_provided); + if carry_over.is_zero() { + SubnetTaoProvided::::set(netuid, remainder); + } else { + SubnetTaoProvided::::set(netuid, TaoCurrency::ZERO); + SubnetTAO::::set(netuid, subnet_tao.saturating_sub(carry_over)); } } pub fn increase_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency) { - if !alpha.is_zero() { - SubnetAlphaIn::::mutate(netuid, |total| { - *total = total.saturating_add(alpha); - }); - } + SubnetAlphaInProvided::::mutate(netuid, |total| { + *total = total.saturating_add(alpha); + }); } pub fn decrease_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency) { - if !alpha.is_zero() { - SubnetAlphaIn::::mutate(netuid, |total| { - *total = total.saturating_sub(alpha); - }); + // First, decrease SubnetAlphaInProvided, then deduct the rest from SubnetAlphaIn + let subnet_alpha = SubnetAlphaIn::::get(netuid); + let subnet_alpha_provided = SubnetAlphaInProvided::::get(netuid); + let remainder = subnet_alpha_provided.saturating_sub(alpha); + let carry_over = alpha.saturating_sub(subnet_alpha_provided); + if carry_over.is_zero() { + SubnetAlphaInProvided::::set(netuid, remainder); + } else { + SubnetAlphaInProvided::::set(netuid, AlphaCurrency::ZERO); + SubnetAlphaIn::::set(netuid, subnet_alpha.saturating_sub(carry_over)); } } diff --git a/pallets/subtensor/src/subnets/mechanism.rs b/pallets/subtensor/src/subnets/mechanism.rs index 55e459c9ba..481974ef05 100644 --- a/pallets/subtensor/src/subnets/mechanism.rs +++ b/pallets/subtensor/src/subnets/mechanism.rs @@ -95,20 +95,7 @@ impl Pallet { Ok(()) } - pub fn ensure_max_uids_over_all_mechanisms( - max_uids: u16, - mechanism_count: MechId, - ) -> DispatchResult { - let max_uids_over_all_mechanisms = - max_uids.saturating_mul(u8::from(mechanism_count) as u16); - ensure!( - max_uids_over_all_mechanisms <= DefaultMaxAllowedUids::::get(), - Error::::TooManyUIDsPerMechanism - ); - Ok(()) - } - - /// Set the desired value of mechanism count for a subnet identified + /// Set the desired valus of sub-subnet count for a subnet identified /// by netuid pub fn do_set_mechanism_count(netuid: NetUid, mechanism_count: MechId) -> DispatchResult { // Make sure the subnet exists @@ -126,10 +113,6 @@ impl Pallet { Error::::InvalidValue ); - // Prevent chain bloat: Require max UIDs to be limited - let max_uids = MaxAllowedUids::::get(netuid); - Self::ensure_max_uids_over_all_mechanisms(max_uids, mechanism_count)?; - // Make sure we are not allowing numbers that will break the math ensure!( mechanism_count <= MechId::from(MAX_MECHANISM_COUNT_PER_SUBNET), @@ -141,22 +124,6 @@ impl Pallet { Ok(()) } - /// Set the global maximum number of mechanisms per subnet - pub fn do_set_max_mechanism_count(max_mechanism_count: MechId) -> DispatchResult { - // Max count cannot be zero - ensure!(max_mechanism_count > 0.into(), Error::::InvalidValue); - - // Make sure we are not allowing numbers that will break the math - ensure!( - max_mechanism_count <= MechId::from(MAX_MECHANISM_COUNT_PER_SUBNET), - Error::::InvalidValue - ); - - MaxMechanismCount::::set(max_mechanism_count); - - Ok(()) - } - /// Update current count for a subnet identified by netuid /// - Cleans up all sub-subnet maps if count is reduced /// @@ -355,7 +322,6 @@ impl Pallet { sub_weight, ); acc_terms.new_validator_permit |= terms.new_validator_permit; - acc_terms.stake = acc_terms.stake.saturating_add(terms.stake); }) .or_insert_with(|| { // weighted insert for the first sub-subnet seen for this hotkey @@ -383,8 +349,7 @@ impl Pallet { sub_weight, ), new_validator_permit: terms.new_validator_permit, - bond: Vec::new(), // aggregated map doesn't use bonds; keep empty - stake: terms.stake, + bond: Vec::new(), // aggregated map doesn’t use bonds; keep empty } }); acc @@ -393,9 +358,6 @@ impl Pallet { // State updates from epoch function Self::persist_netuid_epoch_terms(netuid, &aggregated); - // Update voting power EMA for all validators on this subnet - Self::update_voting_power_for_subnet(netuid, &aggregated); - // Remap BTreeMap back to Vec<(T::AccountId, AlphaCurrency, AlphaCurrency)> format // for processing emissions in run_coinbase // Emission tuples ( hotkeys, server_emission, validator_emission ) diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 86462dd06d..ecb5ce0452 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -217,6 +217,8 @@ impl Pallet { SubnetOwner::::insert(netuid_to_register, coldkey.clone()); SubnetOwnerHotkey::::insert(netuid_to_register, hotkey.clone()); SubnetLocked::::insert(netuid_to_register, actual_tao_lock_amount); + SubnetTaoProvided::::insert(netuid_to_register, TaoCurrency::ZERO); + SubnetAlphaInProvided::::insert(netuid_to_register, AlphaCurrency::ZERO); SubnetAlphaOut::::insert(netuid_to_register, AlphaCurrency::ZERO); SubnetVolume::::insert(netuid_to_register, 0u128); RAORecycledForRegistration::::insert( diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 54b07d9dbf..c81138b58c 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,134 +1,228 @@ use super::*; +use frame_support::weights::Weight; +use sp_core::Get; use substrate_fixed::types::U64F64; impl Pallet { - /// Transfer all assets, stakes, subnet ownerships, and hotkey associations from `old_coldkey` to - /// to `new_coldkey`. + /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which must be signed by the old coldkey. + /// * `new_coldkey` - The account ID of the new coldkey. + /// + /// # Returns + /// + /// Returns a `DispatchResultWithPostInfo` indicating success or failure, along with the weight consumed. + /// + /// # Errors + /// + /// This function will return an error if: + /// - The caller is not a valid signed origin. + /// - The old coldkey (caller) is in arbitration. + /// - The new coldkey is already associated with other hotkeys or is a hotkey itself. + /// - There's not enough balance to pay for the swap. + /// + /// # Events + /// + /// Emits a `ColdkeySwapped` event when successful. + /// + /// # Weight + /// + /// Weight is tracked and updated throughout the function execution. pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, - ) -> DispatchResult { + swap_cost: TaoCurrency, + ) -> DispatchResultWithPostInfo { + // 2. Initialize the weight for this operation + let mut weight: Weight = T::DbWeight::get().reads(2); + // 3. Ensure the new coldkey is not associated with any hotkeys ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // 4. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::NewColdKeyIsHotkey ); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); - // Swap the identity if the old coldkey has one and the new coldkey doesn't - if IdentitiesV2::::get(new_coldkey).is_none() - && let Some(identity) = IdentitiesV2::::take(old_coldkey) - { - IdentitiesV2::::insert(new_coldkey.clone(), identity); + // 5. Swap the identity if the old coldkey has one + if let Some(identity) = IdentitiesV2::::take(old_coldkey) { + IdentitiesV2::::insert(new_coldkey, identity); } - for netuid in Self::get_all_subnet_netuids() { - Self::transfer_subnet_ownership(netuid, old_coldkey, new_coldkey); - Self::transfer_auto_stake_destination(netuid, old_coldkey, new_coldkey); - Self::transfer_coldkey_stake(netuid, old_coldkey, new_coldkey); - } - Self::transfer_staking_hotkeys(old_coldkey, new_coldkey); - Self::transfer_hotkeys_ownership(old_coldkey, new_coldkey); + // 6. Ensure sufficient balance for the swap cost + ensure!( + Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost.into()), + Error::::NotEnoughBalanceToPaySwapColdKey + ); - // Transfer any remaining balance from old_coldkey to new_coldkey - let remaining_balance = Self::get_coldkey_balance(old_coldkey); - if remaining_balance > 0 { - Self::kill_coldkey_account(old_coldkey, remaining_balance)?; - Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); - } + // 7. Remove and recycle the swap cost from the old coldkey's account + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost.into())?; + Self::recycle_tao(actual_burn_amount); + + // 8. Update the weight for the balance operations + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 9. Perform the actual coldkey swap + let _ = Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); + // 10. Update the last transaction block for the new coldkey Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + // 11. Remove the coldkey swap scheduled record + ColdkeySwapScheduled::::remove(old_coldkey); + + // 12. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), + swap_cost, }); - Ok(()) - } - - /// Charges the swap cost from the coldkey's account and recycles the tokens. - pub fn charge_swap_cost(coldkey: &T::AccountId, swap_cost: TaoCurrency) -> DispatchResult { - let burn_amount = Self::remove_balance_from_coldkey_account(coldkey, swap_cost.into()) - .map_err(|_| Error::::NotEnoughBalanceToPaySwapColdKey)?; - - if burn_amount < swap_cost { - return Err(Error::::NotEnoughBalanceToPaySwapColdKey.into()); - } - - Self::recycle_tao(burn_amount); - Ok(()) + // 12. Return the result with the updated weight + Ok(Some(weight).into()) } - /// Transfer the ownership of the subnet to the new coldkey if it is owned by the old coldkey. - fn transfer_subnet_ownership( - netuid: NetUid, + /// Performs the actual coldkey swap operation, transferring all associated data and balances from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The account ID of the old coldkey. + /// * `new_coldkey` - The account ID of the new coldkey. + /// * `weight` - A mutable reference to the current transaction weight. + /// + /// # Returns + /// + /// Returns a `DispatchResult` indicating success or failure of the operation. + /// + /// # Steps + /// + /// 1. Swap TotalHotkeyColdkeyStakesThisInterval: + /// - For each hotkey owned by the old coldkey, transfer its stake and block data to the new coldkey. + /// + /// 2. Swap subnet ownership: + /// - For each subnet, if the old coldkey is the owner, transfer ownership to the new coldkey. + /// + /// 3. Swap Stakes: + /// - For each hotkey staking for the old coldkey, transfer its stake to the new coldkey. + /// + /// 4. Swap total coldkey stake: + /// - Transfer the total stake from the old coldkey to the new coldkey. + /// + /// 5. Swap StakingHotkeys: + /// - Transfer the list of staking hotkeys from the old coldkey to the new coldkey. + /// + /// 6. Swap hotkey owners: + /// - For each hotkey owned by the old coldkey, transfer ownership to the new coldkey. + /// - Update the list of owned hotkeys for both old and new coldkeys. + /// + /// 7. Transfer remaining balance: + /// - Transfer any remaining balance from the old coldkey to the new coldkey. + /// + /// Throughout the process, the function updates the transaction weight to reflect the operations performed. + /// + /// # Notes + /// + /// This function is a critical part of the coldkey swap process and should be called only after all necessary checks and validations have been performed. + pub fn perform_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, - ) { - let subnet_owner = SubnetOwner::::get(netuid); - if subnet_owner == *old_coldkey { - SubnetOwner::::insert(netuid, new_coldkey.clone()); - } - } + weight: &mut Weight, + ) -> DispatchResult { + // 1. Swap TotalHotkeyColdkeyStakesThisInterval + // TotalHotkeyColdkeyStakesThisInterval: MAP ( hotkey, coldkey ) --> ( stake, block ) | Stake of the hotkey for the coldkey. + // for hotkey in OwnedHotkeys::::get(old_coldkey).iter() { + // let (stake, block) = + // TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, old_coldkey); + // TotalHotkeyColdkeyStakesThisInterval::::remove(&hotkey, old_coldkey); + // TotalHotkeyColdkeyStakesThisInterval::::insert(&hotkey, new_coldkey, (stake, block)); + // weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + // } (DEPRECATED) - /// Transfer the auto stake destination from the old coldkey to the new coldkey if it is set. - fn transfer_auto_stake_destination( - netuid: NetUid, - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) { - if let Some(old_auto_stake_hotkey) = AutoStakeDestination::::get(old_coldkey, netuid) { - AutoStakeDestination::::remove(old_coldkey, netuid); - AutoStakeDestination::::insert(new_coldkey, netuid, old_auto_stake_hotkey.clone()); - AutoStakeDestinationColdkeys::::mutate(old_auto_stake_hotkey, netuid, |v| { - // Remove old/new coldkeys (avoid duplicates), then add the new one. - v.retain(|c| *c != *old_coldkey && *c != *new_coldkey); - v.push(new_coldkey.clone()); - }); + // 2. Swap subnet owner. + // SubnetOwner: MAP ( netuid ) --> (coldkey) | Owner of the subnet. + for netuid in Self::get_all_subnet_netuids() { + let subnet_owner = SubnetOwner::::get(netuid); + if subnet_owner == *old_coldkey { + SubnetOwner::::insert(netuid, new_coldkey.clone()); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + if let Some(old_auto_stake_hotkey) = AutoStakeDestination::::get(old_coldkey, netuid) + { + AutoStakeDestination::::remove(old_coldkey, netuid); + AutoStakeDestination::::insert( + new_coldkey, + netuid, + old_auto_stake_hotkey.clone(), + ); + AutoStakeDestinationColdkeys::::mutate(old_auto_stake_hotkey, netuid, |v| { + // Remove old/new coldkeys (avoid duplicates), then add the new one. + v.retain(|c| *c != *old_coldkey && *c != *new_coldkey); + v.push(new_coldkey.clone()); + }); + } } - } - /// Transfer the stake of all staking hotkeys linked to the old coldkey to the new coldkey. - fn transfer_coldkey_stake( - netuid: NetUid, - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) { + // 3. Swap Stake. + // StakingHotkeys: MAP ( coldkey ) --> Vec( hotkey ) for hotkey in StakingHotkeys::::get(old_coldkey) { - // Get the stake on the old (hot,coldkey) account. - let old_alpha: U64F64 = Alpha::::get((&hotkey, old_coldkey, netuid)); - // Get the stake on the new (hot,coldkey) account. - let new_alpha: U64F64 = Alpha::::get((&hotkey, new_coldkey, netuid)); - // Add the stake to new account. - Alpha::::insert( - (&hotkey, new_coldkey, netuid), - new_alpha.saturating_add(old_alpha), - ); - // Remove the value from the old account. - Alpha::::remove((&hotkey, old_coldkey, netuid)); - - if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { - Self::transfer_root_claimed_for_new_keys( - netuid, - &hotkey, - &hotkey, - old_coldkey, - new_coldkey, + // 3.1 Swap Alpha + for netuid in Self::get_all_subnet_netuids() { + // Get the stake on the old (hot,coldkey) account. + let old_alpha: U64F64 = Alpha::::get((&hotkey, old_coldkey, netuid)); + // Get the stake on the new (hot,coldkey) account. + let new_alpha: U64F64 = Alpha::::get((&hotkey, new_coldkey, netuid)); + // Add the stake to new account. + Alpha::::insert( + (&hotkey, new_coldkey, netuid), + new_alpha.saturating_add(old_alpha), ); + // Remove the value from the old account. + Alpha::::remove((&hotkey, old_coldkey, netuid)); + + if new_alpha.saturating_add(old_alpha) > U64F64::from(0u64) { + Self::transfer_root_claimed_for_new_keys( + netuid, + &hotkey, + &hotkey, + old_coldkey, + new_coldkey, + ); - if netuid == NetUid::ROOT { - // Register new coldkey with root stake - Self::maybe_add_coldkey_index(new_coldkey); + if netuid == NetUid::ROOT { + // Register new coldkey with root stake + Self::maybe_add_coldkey_index(new_coldkey); + } } } + // Add the weight for the read and write. + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - } - /// Transfer staking hotkeys from the old coldkey to the new coldkey. - fn transfer_staking_hotkeys(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { + // 4. Swap TotalColdkeyAlpha (DEPRECATED) + // for netuid in Self::get_all_subnet_netuids() { + // let old_alpha_stake: u64 = TotalColdkeyAlpha::::get(old_coldkey, netuid); + // let new_alpha_stake: u64 = TotalColdkeyAlpha::::get(new_coldkey, netuid); + // TotalColdkeyAlpha::::insert( + // new_coldkey, + // netuid, + // new_alpha_stake.saturating_add(old_alpha_stake), + // ); + // TotalColdkeyAlpha::::remove(old_coldkey, netuid); + // } + // weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + + // 5. Swap StakingHotkeys. + // StakingHotkeys: MAP ( coldkey ) --> Vec | Hotkeys staking for the coldkey. let old_staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); let mut new_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); for hotkey in old_staking_hotkeys { @@ -137,13 +231,13 @@ impl Pallet { new_staking_hotkeys.push(hotkey); } } - StakingHotkeys::::remove(old_coldkey); StakingHotkeys::::insert(new_coldkey, new_staking_hotkeys); - } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - /// Transfer the ownership of the hotkeys owned by the old coldkey to the new coldkey. - fn transfer_hotkeys_ownership(old_coldkey: &T::AccountId, new_coldkey: &T::AccountId) { + // 6. Swap hotkey owners. + // Owner: MAP ( hotkey ) --> coldkey | Owner of the hotkey. + // OwnedHotkeys: MAP ( coldkey ) --> Vec | Hotkeys owned by the coldkey. let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); let mut new_owned_hotkeys: Vec = OwnedHotkeys::::get(new_coldkey); for owned_hotkey in old_owned_hotkeys.iter() { @@ -158,5 +252,19 @@ impl Pallet { } OwnedHotkeys::::remove(old_coldkey); OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + + // 7. Transfer remaining balance. + // Balance: MAP ( coldkey ) --> u64 | Balance of the coldkey. + // Transfer any remaining balance from old_coldkey to new_coldkey + let remaining_balance = Self::get_coldkey_balance(old_coldkey); + if remaining_balance > 0 { + Self::kill_coldkey_account(old_coldkey, remaining_balance)?; + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + + // Return ok. + Ok(()) } } diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index a54a02a750..4fdf87fb7b 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -493,11 +493,6 @@ impl Pallet { // 8.3 Swap TaoDividendsPerSubnet // Tao dividends were removed - // 8.4 Swap VotingPower - // VotingPower( netuid, hotkey ) --> u64 -- the voting power EMA for the hotkey. - Self::swap_voting_power_for_hotkey(old_hotkey, new_hotkey, netuid); - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // 9. Swap Alpha // Alpha( hotkey, coldkey, netuid ) -> alpha let old_alpha_values: Vec<((T::AccountId, NetUid), U64F64)> = diff --git a/pallets/subtensor/src/tests/claim_root.rs b/pallets/subtensor/src/tests/claim_root.rs index 9d08c65e3c..717f3a5d28 100644 --- a/pallets/subtensor/src/tests/claim_root.rs +++ b/pallets/subtensor/src/tests/claim_root.rs @@ -19,7 +19,7 @@ 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, U96F32}; +use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; @@ -758,7 +758,6 @@ fn test_claim_root_with_drain_emissions_and_swap_claim_type() { }); } -/// cargo test --package pallet-subtensor --lib -- tests::claim_root::test_claim_root_with_run_coinbase --exact --nocapture #[test] fn test_claim_root_with_run_coinbase() { new_test_ext(1).execute_with(|| { @@ -791,15 +790,10 @@ fn test_claim_root_with_run_coinbase() { // Set moving price > 1.0 and price > 1.0 // So we turn ON root sell SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - let tao = TaoCurrency::from(10_000_000_000_000_u64); - let alpha = AlphaCurrency::from(1_000_000_000_000_u64); - SubnetTAO::::insert(netuid, tao); - SubnetAlphaIn::::insert(netuid, alpha); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .saturating_to_num::(); - assert_eq!(current_price, 10.0f64); - RootClaimableThreshold::::insert(netuid, I96F32::from_num(0)); + 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]); @@ -907,15 +901,10 @@ fn test_claim_root_with_block_emissions() { // Set moving price > 1.0 and price > 1.0 // So we turn ON root sell SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); - let tao = TaoCurrency::from(10_000_000_000_000_u64); - let alpha = AlphaCurrency::from(1_000_000_000_000_u64); - SubnetTAO::::insert(netuid, tao); - SubnetAlphaIn::::insert(netuid, alpha); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .saturating_to_num::(); - assert_eq!(current_price, 10.0f64); - RootClaimableThreshold::::insert(netuid, I96F32::from_num(0)); + 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]); @@ -1031,21 +1020,16 @@ fn test_claim_root_coinbase_distribution() { 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)); - let tao = TaoCurrency::from(100_000_000_000_u64); - let alpha = AlphaCurrency::from(100_000_000_000_u64); - SubnetTAO::::insert(netuid, tao); - SubnetAlphaIn::::insert(netuid, alpha); - // let current_price = - // ::SwapInterface::current_alpha_price(netuid.into()) - // .saturating_to_num::(); - // assert_eq!(current_price, 2.0f64); - RootClaimableThreshold::::insert(netuid, I96F32::from_num(0)); - - let initial_alpha_issuance = SubtensorModule::get_alpha_issuance(netuid); - let alpha_emissions: AlphaCurrency = 1_000_000_000u64.into(); + 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]); @@ -1200,7 +1184,13 @@ fn test_claim_root_with_swap_coldkey() { ); // Swap coldkey - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + let mut weight = Weight::zero(); + + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey, + &mut weight + )); // Check swapped keys claimed values diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 4e4caf2e9c..a79f4b713a 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -12,6 +12,7 @@ use crate::*; use alloc::collections::BTreeMap; use approx::assert_abs_diff_eq; use frame_support::assert_ok; +use pallet_subtensor_swap::position::PositionId; use sp_core::U256; use substrate_fixed::{ transcendental::sqrt, @@ -45,6 +46,36 @@ fn test_hotkey_take() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::coinbase::test_dynamic_function_various_values --exact --show-output --nocapture +#[test] +fn test_dynamic_function_various_values() { + new_test_ext(1).execute_with(|| { + let price_values: [f64; 9] = [0.001, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0, 200.0, 1000.0]; + let tao_in_values: [u64; 9] = [0, 1, 10, 100, 1_000, 1_000_000, 1_000_000_000, 1_000_000_000_000, 1_000_000_000_000_000 ]; + let alpha_emission_values: [u64; 9] = [0, 1, 10, 100, 1_000, 1_000_000, 1_000_000_000, 1_000_000_000_000, 1_000_000_000_000_000 ]; + + for &price in price_values.iter() { + for &tao_in in tao_in_values.iter() { + for &alpha_emission in alpha_emission_values.iter() { + // Set the price. + SubnetMechanism::::insert(NetUid::from(1), 1); + SubnetTAO::::insert(NetUid::from(1), TaoCurrency::from((price * 1_000_000_000.0) as u64)); + SubnetAlphaIn::::insert(NetUid::from(1), AlphaCurrency::from(1_000_000_000)); + let (tao_in_emission, alpha_in_emission, alpha_out_emission) = SubtensorModule::get_dynamic_tao_emission(1.into(), tao_in, alpha_emission); + assert!(tao_in_emission <= tao_in, "tao_in_emission is greater than tao_in"); + assert!(alpha_in_emission <= alpha_emission, "alpha_in_emission is greater than alpha_emission"); + assert!(alpha_out_emission <= 2 * alpha_emission, "alpha_out_emission is greater than 2 * alpha_emission"); + assert!((alpha_in_emission + alpha_out_emission) <= 2 * alpha_emission, "Sum of alpha_in_emission and alpha_out_emission is less than or equal to. 2 * alpha_emission"); + close( alpha_in_emission + alpha_out_emission, alpha_in_emission + alpha_emission, 10 ); + // if alpha_in_emission > 0 || tao_in_emission > 0 { + // assert!((tao_in_emission as f64 / alpha_in_emission as f64 - price).abs() < 1e-1, "Ratio of tao_in_emission to alpha_in_emission is not equal to price"); + // } + } + } + } + }); +} + // Test the base case of running coinbase with zero emission. // This test verifies that the coinbase mechanism can handle the edge case // of zero emission without errors or unexpected behavior. @@ -191,8 +222,20 @@ fn test_coinbase_tao_issuance_different_prices() { mock::setup_reserves(netuid2, initial_tao.into(), initial_alpha2.into()); // Force the swap to initialize - ::SwapInterface::init_swap(netuid1, None); - ::SwapInterface::init_swap(netuid2, None); + SubtensorModule::swap_tao_for_alpha( + netuid1, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); + SubtensorModule::swap_tao_for_alpha( + netuid2, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); // Make subnets dynamic. SubnetMechanism::::insert(netuid1, 1); @@ -255,8 +298,20 @@ fn test_coinbase_tao_issuance_different_prices() { // mock::setup_reserves(netuid2, initial_tao.into(), initial_alpha2.into()); // // Force the swap to initialize -// ::SwapInterface::init_swap(netuid1); -// ::SwapInterface::init_swap(netuid2); +// SubtensorModule::swap_tao_for_alpha( +// netuid1, +// TaoCurrency::ZERO, +// 1_000_000_000_000.into(), +// false, +// ) +// .unwrap(); +// SubtensorModule::swap_tao_for_alpha( +// netuid2, +// TaoCurrency::ZERO, +// 1_000_000_000_000.into(), +// false, +// ) +// .unwrap(); // // Set subnet prices to reversed proportion to ensure they don't affect emissions. // SubnetMovingPrice::::insert(netuid1, I96F32::from_num(2)); @@ -561,8 +616,20 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { SubnetTaoFlow::::insert(netuid2, 200_000_000_i64); // Force the swap to initialize - ::SwapInterface::init_swap(netuid1, None); - ::SwapInterface::init_swap(netuid2, None); + SubtensorModule::swap_tao_for_alpha( + netuid1, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); + SubtensorModule::swap_tao_for_alpha( + netuid2, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); // Get the prices before the run_coinbase let price_1_before = ::SwapInterface::current_alpha_price(netuid1); @@ -2662,6 +2729,54 @@ fn test_run_coinbase_not_started_start_after() { }); } +/// Test that coinbase updates protocol position liquidity +/// 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); + }); +} + // 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 #[test] fn test_drain_alpha_childkey_parentkey_with_burn() { @@ -2955,8 +3070,10 @@ fn test_mining_emission_distribution_with_no_root_sell() { // 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)); - let alpha = AlphaCurrency::from(1_000_000_000_000_000_000_u64); - SubnetAlphaIn::::insert(netuid, alpha); + 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]); @@ -3148,8 +3265,10 @@ fn test_mining_emission_distribution_with_root_sell() { // Make root sell happen // Set moving price > 1.0 // Set price > 1.0 - let alpha = AlphaCurrency::from(100_000_000_000_000); - SubnetAlphaIn::::insert(netuid, alpha); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); @@ -3275,7 +3394,7 @@ fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_palswap(netuid0, None); + Swap::maybe_initialize_v3(netuid0); // Set netuid0 to have price tao_emission / price > alpha_emission let alpha_emission = U96F32::saturating_from_num( @@ -3286,19 +3405,14 @@ fn test_coinbase_subnet_terms_with_alpha_in_gt_alpha_emission() { ); 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 - let tao = TaoCurrency::from(1_000_000_000_u64); - let alpha = AlphaCurrency::from( - (U64F64::saturating_from_num(u64::from(tao)) / price_to_set).to_num::(), - ); - SubnetTAO::::insert(netuid0, tao); - SubnetAlphaIn::::insert(netuid0, alpha); - + 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::(), @@ -3356,7 +3470,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_palswap(netuid0, None); + Swap::maybe_initialize_v3(netuid0); let alpha_emission = U96F32::saturating_from_num( SubtensorModule::get_block_emission_for_issuance( @@ -3366,7 +3480,7 @@ fn test_coinbase_subnet_terms_with_alpha_in_lte_alpha_emission() { ); let tao_emission = U96F32::saturating_from_num(34566756_u64); - let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); + let price: U96F32 = Swap::current_alpha_price(netuid0); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3419,7 +3533,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_palswap(netuid0, None); + Swap::maybe_initialize_v3(netuid0); let tao_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(123))]); let alpha_in = BTreeMap::from([(netuid0, U96F32::saturating_from_num(456))]); @@ -3553,7 +3667,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_palswap(netuid0, None); + Swap::maybe_initialize_v3(netuid0); let tao_emission = U96F32::saturating_from_num(12345678); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3567,7 +3681,7 @@ fn test_coinbase_emit_to_subnets_with_no_root_sell() { ) .unwrap_or(0), ); - let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); + let price: U96F32 = 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 @@ -3644,7 +3758,7 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { AlphaCurrency::from(1_000_000_000_000_000), ); // Initialize swap v3 - Swap::maybe_initialize_palswap(netuid0, None); + Swap::maybe_initialize_v3(netuid0); let tao_emission = U96F32::saturating_from_num(12345678); let subnet_emissions = BTreeMap::from([(netuid0, tao_emission)]); @@ -3658,7 +3772,7 @@ fn test_coinbase_emit_to_subnets_with_root_sell() { ) .unwrap_or(0), ); - let price: U96F32 = U96F32::saturating_from_num(Swap::current_alpha_price(netuid0)); + let price: U96F32 = 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 @@ -3773,10 +3887,10 @@ fn test_pending_emission_start_call_not_done() { // Make root sell happen // Set moving price > 1.0 // Set price > 1.0 - let tao = TaoCurrency::from(10_000_000_000_u64); - let alpha = AlphaCurrency::from(1_000_000_000_u64); - SubnetTAO::::insert(netuid, tao); - SubnetAlphaIn::::insert(netuid, alpha); + pallet_subtensor_swap::AlphaSqrtPrice::::insert( + netuid, + U64F64::saturating_from_num(10.0), + ); SubnetMovingPrice::::insert(netuid, I96F32::from_num(2)); diff --git a/pallets/subtensor/src/tests/mechanism.rs b/pallets/subtensor/src/tests/mechanism.rs index 7f0ead8918..9e6450e09c 100644 --- a/pallets/subtensor/src/tests/mechanism.rs +++ b/pallets/subtensor/src/tests/mechanism.rs @@ -210,7 +210,6 @@ fn do_set_mechanism_count_ok_at_effective_cap() { new_test_ext(1).execute_with(|| { let netuid = NetUid::from(4u16); NetworksAdded::::insert(NetUid::from(4u16), true); // base subnet exists - MaxAllowedUids::::insert(netuid, 128u16); // Effective bound is min(runtime cap, compile-time cap) let runtime_cap = MaxMechanismCount::::get(); // e.g., MechId::from(8) diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 04c9ffbaf6..bed77e797f 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -25,7 +25,7 @@ use pallet_drand::types::RoundNumber; use scale_info::prelude::collections::VecDeque; use sp_core::{H256, U256, crypto::Ss58Codec}; use sp_io::hashing::twox_128; -use sp_runtime::{traits::Hash, traits::Zero}; +use sp_runtime::traits::Zero; use substrate_fixed::types::extra::U2; use substrate_fixed::types::{I96F32, U64F64}; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; @@ -2727,6 +2727,9 @@ fn test_migrate_reset_unactive_sn() { 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); @@ -2797,6 +2800,9 @@ fn test_migrate_reset_unactive_sn() { 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); @@ -2869,6 +2875,7 @@ fn test_migrate_reset_unactive_sn_idempotence() { }); } +#[test] fn test_migrate_remove_old_identity_maps() { let migration = crate::migrations::migrate_remove_old_identity_maps::migrate_remove_old_identity_maps::; @@ -2961,123 +2968,3 @@ fn test_migrate_remove_unknown_neuron_axon_cert_prom() { } } } - -// cargo test --package pallet-subtensor --lib -- tests::migration::test_migrate_cleanup_swap_v3 --exact --nocapture -#[test] -fn test_migrate_cleanup_swap_v3() { - use crate::migrations::migrate_cleanup_swap_v3::deprecated_swap_maps; - use substrate_fixed::types::U64F64; - - new_test_ext(1).execute_with(|| { - let migration = crate::migrations::migrate_cleanup_swap_v3::migrate_cleanup_swap_v3::; - - const MIGRATION_NAME: &str = "migrate_cleanup_swap_v3"; - - let provided: u64 = 9876; - let reserves: u64 = 1_000_000; - - SubnetTAO::::insert(NetUid::from(1), TaoCurrency::from(reserves)); - SubnetAlphaIn::::insert(NetUid::from(1), AlphaCurrency::from(reserves)); - - // Insert deprecated maps values - deprecated_swap_maps::SubnetTaoProvided::::insert( - NetUid::from(1), - TaoCurrency::from(provided), - ); - deprecated_swap_maps::SubnetAlphaInProvided::::insert( - NetUid::from(1), - AlphaCurrency::from(provided), - ); - - // Run migration - let weight = migration(); - - // Test that values are removed from state - assert!(!deprecated_swap_maps::SubnetTaoProvided::::contains_key(NetUid::from(1)),); - assert!( - !deprecated_swap_maps::SubnetAlphaInProvided::::contains_key(NetUid::from(1)), - ); - - // Provided got added to reserves - assert_eq!( - u64::from(SubnetTAO::::get(NetUid::from(1))), - reserves + provided - ); - assert_eq!( - u64::from(SubnetAlphaIn::::get(NetUid::from(1))), - reserves + provided - ); - }); -} - -#[test] -fn test_migrate_coldkey_swap_scheduled_to_announcements() { - new_test_ext(1000).execute_with(|| { - const MIGRATION_NAME: &[u8] = b"migrate_coldkey_swap_scheduled_to_announcements"; - use crate::migrations::migrate_coldkey_swap_scheduled_to_announcements::*; - let now = frame_system::Pallet::::block_number(); - - // Set the schedule duration and reschedule duration - deprecated::ColdkeySwapScheduleDuration::::set(Some(now + 100)); - deprecated::ColdkeySwapRescheduleDuration::::set(Some(now + 200)); - - // Set some scheduled coldkey swaps - deprecated::ColdkeySwapScheduled::::insert( - U256::from(1), - (now + 100, U256::from(10)), - ); - deprecated::ColdkeySwapScheduled::::insert( - U256::from(2), - (now - 200, U256::from(20)), - ); - deprecated::ColdkeySwapScheduled::::insert( - U256::from(3), - (now + 200, U256::from(30)), - ); - deprecated::ColdkeySwapScheduled::::insert( - U256::from(4), - (now - 400, U256::from(40)), - ); - deprecated::ColdkeySwapScheduled::::insert( - U256::from(5), - (now + 300, U256::from(50)), - ); - - let w = migrate_coldkey_swap_scheduled_to_announcements::(); - - assert!(!w.is_zero(), "weight must be non-zero"); - assert!(HasMigrationRun::::get(MIGRATION_NAME)); - - // Ensure the deprecated storage is cleared - assert!(!deprecated::ColdkeySwapScheduleDuration::::exists()); - assert!(!deprecated::ColdkeySwapRescheduleDuration::::exists()); - assert_eq!(deprecated::ColdkeySwapScheduled::::iter().count(), 0); - - // Ensure scheduled have been migrated to announcements if not executed yet - // The announcement should be at the scheduled time - delay to be able to call - // the swap_coldkey_announced call at the old scheduled time - let delay = ColdkeySwapAnnouncementDelay::::get(); - assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 3); - assert_eq!( - ColdkeySwapAnnouncements::::get(U256::from(1)), - Some(( - now + 100 - delay, - ::Hashing::hash_of(&U256::from(10)) - )) - ); - assert_eq!( - ColdkeySwapAnnouncements::::get(U256::from(3)), - Some(( - now + 200 - delay, - ::Hashing::hash_of(&U256::from(30)) - )) - ); - assert_eq!( - ColdkeySwapAnnouncements::::get(U256::from(5)), - Some(( - now + 300 - delay, - ::Hashing::hash_of(&U256::from(50)) - )) - ); - }); -} diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 94ae2d240d..b744c9b771 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -20,16 +20,15 @@ use frame_system as system; use frame_system::{EnsureRoot, RawOrigin, limits, offchain::CreateTransactionBase}; use pallet_subtensor_proxy as pallet_proxy; use pallet_subtensor_utility as pallet_utility; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; use sp_runtime::{ BuildStorage, Percent, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentityLookup}, }; use sp_std::{cell::RefCell, cmp::Ordering, sync::OnceLock}; use sp_tracing::tracing_subscriber; -use subtensor_runtime_common::{AuthorshipInfo, NetUid, TaoCurrency}; +use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; type Block = frame_system::mocking::MockBlock; @@ -38,19 +37,16 @@ type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( pub enum Test { - System: frame_system = 1, - Balances: pallet_balances = 2, - Timestamp: pallet_timestamp = 3, - Aura: pallet_aura = 4, - Shield: pallet_shield = 5, - SubtensorModule: crate = 6, - Utility: pallet_utility = 7, - Scheduler: pallet_scheduler = 8, - Preimage: pallet_preimage = 9, - Drand: pallet_drand = 10, - Swap: pallet_subtensor_swap = 11, - Crowdloan: pallet_crowdloan = 12, - Proxy: pallet_subtensor_proxy = 13, + System: frame_system::{Pallet, Call, Config, Storage, Event} = 1, + Balances: pallet_balances::{Pallet, Call, Config, Storage, Event} = 2, + SubtensorModule: crate::{Pallet, Call, Storage, Event} = 7, + Utility: pallet_utility::{Pallet, Call, Storage, Event} = 8, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 9, + Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 10, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 11, + Swap: pallet_subtensor_swap::{Pallet, Call, Storage, Event} = 12, + Crowdloan: pallet_crowdloan::{Pallet, Call, Storage, Event} = 13, + Proxy: pallet_subtensor_proxy = 14, } ); @@ -153,14 +149,6 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } -pub struct MockAuthorshipProvider; - -impl AuthorshipInfo for MockAuthorshipProvider { - fn author() -> Option { - Some(U256::from(12345u64)) - } -} - parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -178,7 +166,7 @@ parameter_types! { pub const SelfOwnership: u64 = 2; pub const InitialImmunityPeriod: u16 = 2; pub const InitialMinAllowedUids: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 256; + pub const InitialMaxAllowedUids: u16 = 4; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty:u16 = u16::MAX; pub const InitialBondsResetOn: bool = false; @@ -224,8 +212,9 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; - pub const InitialColdkeySwapReannouncementDelay: u64 = 10; + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -295,8 +284,8 @@ impl crate::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = Preimage; - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; @@ -310,13 +299,13 @@ impl crate::Config for Test { type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; type CommitmentsInterface = CommitmentsI; type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = MockAuthorshipProvider; } // Swap-related parameter types parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const SwapMaxFeeRate: u16 = 10000; // 15.26% + pub const SwapMaxPositions: u32 = 100; pub const SwapMinimumLiquidity: u64 = 1_000; pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(100).unwrap(); } @@ -328,6 +317,7 @@ impl pallet_subtensor_swap::Config for Test { type TaoReserve = TaoCurrencyReserve; type AlphaReserve = AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; type MinimumReserve = SwapMinimumReserve; type WeightInfo = (); @@ -562,41 +552,6 @@ where } } -#[derive_impl(pallet_timestamp::config_preludes::TestDefaultConfig)] -impl pallet_timestamp::Config for Test { - type MinimumPeriod = ConstU64<0>; -} - -parameter_types! { - pub const MaxAuthorities: u32 = 32; - pub const AllowMultipleBlocksPerSlot: bool = false; - pub const SlotDuration: u64 = 6000; -} - -impl pallet_aura::Config for Test { - type AuthorityId = AuraId; - // For tests we don't need dynamic disabling; just use unit type. - type DisabledValidators = (); - type MaxAuthorities = MaxAuthorities; - type AllowMultipleBlocksPerSlot = AllowMultipleBlocksPerSlot; - type SlotDuration = SlotDuration; -} - -pub struct TestAuthorityOrigin; - -impl pallet_shield::AuthorityOriginExt for TestAuthorityOrigin { - type AccountId = U256; - - fn ensure_validator(_origin: RuntimeOrigin) -> Result { - Ok(U256::from(0)) - } -} - -impl pallet_shield::Config for Test { - type RuntimeCall = RuntimeCall; - type AuthorityOrigin = TestAuthorityOrigin; -} - static TEST_LOGS_INIT: OnceLock<()> = OnceLock::new(); pub fn init_logs_for_tests() { diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 8f07572e25..bbaf25af58 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -15,7 +15,7 @@ mod leasing; mod math; mod mechanism; mod migration; -pub(crate) mod mock; +mod mock; mod move_stake; mod networks; mod neuron_info; @@ -30,5 +30,4 @@ mod swap_coldkey; mod swap_hotkey; mod swap_hotkey_with_subnet; mod uids; -mod voting_power; mod weights; diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 9c9f92d8ac..dfd9927da4 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -619,9 +619,8 @@ fn test_do_move_event_emission() { // Move stake and capture events System::reset_events(); - let current_price = U96F32::from_num( - ::SwapInterface::current_alpha_price(netuid.into()), - ); + let current_price = + ::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), @@ -711,7 +710,7 @@ fn test_do_move_storage_updates() { destination_netuid ), alpha2, - epsilon = 50.into() + epsilon = 2.into() ); }); } diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index fe141c040f..4605ac8bef 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -6,16 +6,10 @@ 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() { @@ -253,9 +247,8 @@ fn dissolve_owner_cut_refund_logic() { // Use the current alpha price to estimate the TAO equivalent. let owner_emission_tao = { - let price: U96F32 = U96F32::from_num( - ::SwapInterface::current_alpha_price(net.into()), - ); + let price: U96F32 = + ::SwapInterface::current_alpha_price(net.into()); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -372,6 +365,8 @@ fn dissolve_clears_all_per_subnet_storages() { // Token / price / provided reserves TokenSymbol::::insert(net, b"XX".to_vec()); SubnetMovingPrice::::insert(net, substrate_fixed::types::I96F32::from_num(1)); + SubnetTaoProvided::::insert(net, TaoCurrency::from(1)); + SubnetAlphaInProvided::::insert(net, AlphaCurrency::from(1)); // TAO Flow SubnetTaoFlow::::insert(net, 0i64); @@ -534,6 +529,8 @@ fn dissolve_clears_all_per_subnet_storages() { // Token / price / provided reserves assert!(!TokenSymbol::::contains_key(net)); assert!(!SubnetMovingPrice::::contains_key(net)); + assert!(!SubnetTaoProvided::::contains_key(net)); + assert!(!SubnetAlphaInProvided::::contains_key(net)); // Subnet locks assert!(!TransferToggle::::contains_key(net)); @@ -829,8 +826,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { netuid.into(), min_stake, ); - // Double the fees because fee is calculated for min_stake, not for min_amount - min_stake + fee * 2.into() + min_stake.saturating_add(fee) }; const N: usize = 20; @@ -910,9 +906,8 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let owner_emission_tao: u64 = { // Fallback matches the pallet's fallback - let price: U96F32 = U96F32::from_num( - ::SwapInterface::current_alpha_price(netuid.into()), - ); + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -991,9 +986,8 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { .saturating_to_num::(); let owner_emission_tao_u64 = { - let price: U96F32 = U96F32::from_num( - ::SwapInterface::current_alpha_price(netuid.into()), - ); + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -1780,6 +1774,458 @@ 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]; + + // TODO: Revise when user liquidity is available + // 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); + // pallet_subtensor_swap::EnabledUserLiquidity::::insert(net, true); + // 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 + // ──────────────────────────────────────────────────────────────────── + // TODO: Revise when user liquidity is available + // 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 + // TODO: Revise when user liquidity is available + // 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] fn dissolve_clears_all_mechanism_scoped_maps_for_all_mechanisms() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/tests/recycle_alpha.rs b/pallets/subtensor/src/tests/recycle_alpha.rs index 5cf589de97..32a95c700d 100644 --- a/pallets/subtensor/src/tests/recycle_alpha.rs +++ b/pallets/subtensor/src/tests/recycle_alpha.rs @@ -1,12 +1,12 @@ -use super::mock; -use super::mock::*; -use crate::*; use approx::assert_abs_diff_eq; use frame_support::{assert_noop, assert_ok, traits::Currency}; use sp_core::U256; -use substrate_fixed::types::{U64F64, U96F32}; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT}; -use subtensor_swap_interface::SwapHandler; + +use super::mock; +use super::mock::*; +use crate::*; #[test] fn test_recycle_success() { @@ -618,288 +618,3 @@ fn test_burn_precision_loss() { ); }); } - -#[test] -fn test_add_stake_burn_success() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(533453); - let coldkey_account_id = U256::from(55453); - let amount = DefaultMinStake::::get().to_u64() * 10; - - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - mock::setup_reserves( - netuid, - (amount * 1_000_000).into(), - (amount * 10_000_000).into(), - ); - - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); - - // Check we have zero staked before transfer - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoCurrency::ZERO - ); - - // Execute add_stake_burn - this stakes TAO to get Alpha, then burns the Alpha - assert_ok!(SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - None, - )); - - // After "add stake and burn", hotkey should have zero stake since alpha is burned immediately - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoCurrency::ZERO - ); - - // We spent TAO - assert_abs_diff_eq!( - SubtensorModule::get_coldkey_balance(&coldkey_account_id), - 0u64, - epsilon = 1u64 - ); - - // Verify AlphaBurned event was emitted - assert!(System::events().iter().any(|e| { - matches!( - &e.event, - RuntimeEvent::SubtensorModule(Event::AlphaBurned(..)) - ) - })); - - // Verify AddStakeBurn event was emitted - assert!(System::events().iter().any(|e| { - matches!( - &e.event, - RuntimeEvent::SubtensorModule(Event::AddStakeBurn { .. }) - ) - })); - }); -} - -#[test] -fn test_add_stake_burn_with_limit_success() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(533453); - let coldkey_account_id = U256::from(55453); - let amount: u64 = 100_000_000_000; // 100 TAO - moderate amount - - // Add network - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - // Setup reserves with large liquidity to minimize slippage - let tao_reserve = TaoCurrency::from(1_000_000_000_000); // 1000 TAO - let alpha_in = AlphaCurrency::from(1_000_000_000_000); // 1000 Alpha - mock::setup_reserves(netuid, tao_reserve, alpha_in); - - // Verify current price is 1.0 - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); - assert_eq!(current_price, U96F32::from_num(1.0)); - - // Give coldkey sufficient balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); - - let initial_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); - - // Setup limit price at 2.0 TAO per Alpha - // With 100 TAO into 1000/1000 pool, price moves from 1.0 to ~1.21 - let limit_price = TaoCurrency::from(2_000_000_000); // 2.0 TAO per Alpha - - // Execute add_stake_burn with limit - assert_ok!(SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - Some(limit_price), - )); - - // After "add stake and burn", hotkey should have zero stake since alpha is burned immediately - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - TaoCurrency::ZERO - ); - - // TAO should have been spent - let final_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); - assert!( - final_balance < initial_balance, - "TAO should have been spent" - ); - - // Final price should be between initial (1.0) and limit (2.0) - let final_price = - ::SwapInterface::current_alpha_price(netuid.into()); - assert!( - final_price.to_num::() >= 1.0 && final_price.to_num::() <= 2.0, - "Final price {} should be between 1.0 and 2.0", - final_price.to_num::() - ); - - // Verify AlphaBurned event was emitted - assert!(System::events().iter().any(|e| { - matches!( - &e.event, - RuntimeEvent::SubtensorModule(Event::AlphaBurned(..)) - ) - })); - }); -} - -#[test] -fn test_add_stake_burn_non_owner_fails() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(2); - let non_owner_coldkey = U256::from(3); - let amount = DefaultMinStake::::get().to_u64() * 10; - - // Add network with coldkey_account_id as owner - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - mock::setup_reserves( - netuid, - (amount * 1_000_000).into(), - (amount * 10_000_000).into(), - ); - - // Give non-owner some balance - SubtensorModule::add_balance_to_coldkey_account(&non_owner_coldkey, amount); - - // Non-owner trying to call add_stake_burn should fail with BadOrigin - assert_noop!( - SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(non_owner_coldkey), - hotkey_account_id, - netuid, - amount.into(), - None, - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_add_stake_burn_nonexistent_subnet_fails() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(2); - let amount = DefaultMinStake::::get().to_u64() * 10; - - // Give some balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); - - // Try to call add_stake_burn on non-existent subnet - let nonexistent_netuid = NetUid::from(999); - assert_noop!( - SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - nonexistent_netuid, - amount.into(), - None, - ), - DispatchError::BadOrigin - ); - }); -} - -#[test] -fn test_add_stake_burn_insufficient_balance_fails() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(1); - let coldkey_account_id = U256::from(2); - let amount = DefaultMinStake::::get().to_u64() * 10; - - // Add network - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - mock::setup_reserves( - netuid, - (amount * 1_000_000).into(), - (amount * 10_000_000).into(), - ); - - // Try to call add_stake_burn without sufficient balance - assert_noop!( - SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - None, - ), - Error::::NotEnoughBalanceToStake - ); - }); -} - -#[test] -fn test_add_stake_burn_rate_limit_exceeded() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(533453); - let coldkey_account_id = U256::from(55453); - let amount: u64 = 10_000_000_000; // 10 TAO - - // Add network - let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); - - // Setup reserves with large liquidity - let tao_reserve = TaoCurrency::from(1_000_000_000_000); - let alpha_in = AlphaCurrency::from(1_000_000_000_000); - mock::setup_reserves(netuid, tao_reserve, alpha_in); - - // Give coldkey sufficient balance for multiple "add stake and burn" operations. - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount * 10); - - assert_eq!( - SubtensorModule::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid)), - 0 - ); - - // First "add stake and burn" should succeed - assert_ok!(SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - None, - )); - - assert_eq!( - SubtensorModule::get_rate_limited_last_block(&RateLimitKey::AddStakeBurn(netuid)), - SubtensorModule::get_current_block_as_u64() - ); - - // Second "add stake and burn" immediately after should fail due to rate limit - assert_noop!( - SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - None, - ), - Error::::AddStakeBurnRateLimitExceeded - ); - - // After stepping past the rate limit, "add stake and burn" should succeed again - let rate_limit = TransactionType::AddStakeBurn.rate_limit_on_subnet::(netuid); - step_block(rate_limit as u16); - - assert_ok!(SubtensorModule::add_stake_burn( - RuntimeOrigin::signed(coldkey_account_id), - hotkey_account_id, - netuid, - amount.into(), - None, - )); - }); -} diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 635b996cea..c82e173907 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -13,7 +13,7 @@ use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT, NetUid, Net use super::mock; use super::mock::*; -use crate::extensions::SubtensorTransactionExtension; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::{AxonInfoOf, CustomTransactionError, Error}; /******************************************** diff --git a/pallets/subtensor/src/tests/serving.rs b/pallets/subtensor/src/tests/serving.rs index 552af372e3..b52666bf26 100644 --- a/pallets/subtensor/src/tests/serving.rs +++ b/pallets/subtensor/src/tests/serving.rs @@ -2,7 +2,7 @@ use super::mock::*; use crate::Error; -use crate::extensions::SubtensorTransactionExtension; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; use frame_support::assert_noop; use frame_support::{ diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 86ba26c2e2..6c7e18b707 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -6,9 +6,9 @@ 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::tick::TickIndex; use safe_math::FixedExt; use sp_core::{Get, H256, U256}; -// use sp_runtime::traits::Dispatchable; use substrate_fixed::traits::FromFixed; use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{ @@ -572,7 +572,13 @@ fn test_add_stake_partial_below_min_stake_fails() { mock::setup_reserves(netuid, (amount * 10).into(), (amount * 10).into()); // Force the swap to initialize - ::SwapInterface::init_swap(netuid, None); + SubtensorModule::swap_tao_for_alpha( + netuid, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); // Get the current price (should be 1.0) let current_price = @@ -686,9 +692,6 @@ fn test_remove_stake_total_balance_no_change() { let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Set fee rate to 0 so that alpha fee is not moved to block producer - pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Some basic assertions assert_eq!( SubtensorModule::get_total_stake(), @@ -711,10 +714,8 @@ fn test_remove_stake_total_balance_no_change() { ); // Add subnet TAO for the equivalent amount added at price - let amount_tao = U96F32::from_num(amount) - * U96F32::from_num( - ::SwapInterface::current_alpha_price(netuid.into()), - ); + let amount_tao = U96F32::saturating_from_num(amount) + * ::SwapInterface::current_alpha_price(netuid.into()); SubnetTAO::::mutate(netuid, |v| { *v += amount_tao.saturating_to_num::().into() }); @@ -799,7 +800,7 @@ fn test_add_stake_insufficient_liquidity_one_side_ok() { SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount_staked); // Set the liquidity at lowest possible value so that all staking requests fail - let reserve_alpha = 1_000_000_000_u64; + let reserve_alpha = u64::from(mock::SwapMinimumReserve::get()); let reserve_tao = u64::from(mock::SwapMinimumReserve::get()) - 1; mock::setup_reserves(netuid, reserve_tao.into(), reserve_alpha.into()); @@ -883,9 +884,9 @@ fn test_remove_stake_insufficient_liquidity() { Error::::InsufficientLiquidity ); - // Mock more liquidity - remove becomes successful - SubnetTAO::::insert(netuid, TaoCurrency::from(amount_staked + 1)); - SubnetAlphaIn::::insert(netuid, AlphaCurrency::from(1)); + // Mock provided liquidity - remove becomes successful + SubnetTaoProvided::::insert(netuid, TaoCurrency::from(amount_staked + 1)); + SubnetAlphaInProvided::::insert(netuid, AlphaCurrency::from(1)); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey), hotkey, @@ -909,9 +910,6 @@ fn test_remove_stake_total_issuance_no_change() { let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Set fee rate to 0 so that alpha fee is not moved to block producer - pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Give it some $$$ in his coldkey balance SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); @@ -977,7 +975,7 @@ fn test_remove_stake_total_issuance_no_change() { assert_abs_diff_eq!( SubtensorModule::get_total_stake(), SubtensorModule::get_network_min_lock() + total_fee.into(), - epsilon = TaoCurrency::from(fee) / 1000.into() + 1.into() + epsilon = TaoCurrency::from(fee) / 1000.into() ); // Check if total issuance is equal to the added stake, even after remove stake (no fee, @@ -1639,9 +1637,6 @@ fn test_clear_small_nominations() { let fee = DefaultMinStake::::get().to_u64(); let init_balance = amount + fee + ExistentialDeposit::get(); - // Set fee rate to 0 so that alpha fee is not moved to block producer - pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Register hot1. register_ok_neuron(netuid, hot1, cold1, 0); Delegates::::insert(hot1, SubtensorModule::get_min_delegate_take()); @@ -2181,9 +2176,8 @@ fn test_get_total_delegated_stake_after_unstaking() { netuid, unstake_amount_alpha.into() )); - let current_price = U96F32::from_num( - ::SwapInterface::current_alpha_price(netuid.into()), - ); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); // Calculate the expected delegated stake let unstake_amount = @@ -2803,7 +2797,7 @@ fn test_max_amount_add_stable() { // cargo test --package pallet-subtensor --lib -- tests::staking::test_max_amount_add_dynamic --exact --show-output #[test] fn test_max_amount_add_dynamic() { - // tao_in, alpha_in, limit_price, expected_max_swappable (with 0.05% fees) + // tao_in, alpha_in, limit_price, expected_max_swappable [ // Zero handling (no panics) ( @@ -2817,16 +2811,16 @@ fn test_max_amount_add_dynamic() { // Low bounds (100, 100, 1_100_000_000, Ok(4)), (1_000, 1_000, 1_100_000_000, Ok(48)), - (10_000, 10_000, 1_100_000_000, Ok(488)), + (10_000, 10_000, 1_100_000_000, Ok(489)), // Basic math - (1_000_000, 1_000_000, 4_000_000_000, Ok(1_000_500)), - (1_000_000, 1_000_000, 9_000_000_000, Ok(2_001_000)), - (1_000_000, 1_000_000, 16_000_000_000, Ok(3_001_500)), + (1_000_000, 1_000_000, 4_000_000_000, Ok(1_000_000)), + (1_000_000, 1_000_000, 9_000_000_000, Ok(2_000_000)), + (1_000_000, 1_000_000, 16_000_000_000, Ok(3_000_000)), ( 1_000_000_000_000, 1_000_000_000_000, 16_000_000_000, - Ok(3_001_500_000_000), + Ok(3_000_000_000_000), ), // Normal range values with edge cases ( @@ -2861,20 +2855,13 @@ fn test_max_amount_add_dynamic() { pallet_subtensor_swap::Error::::PriceLimitExceeded, )), ), - ( - 150_000_000_000, - 100_000_000_000, - 1_500_000_000, - Err(DispatchError::from( - pallet_subtensor_swap::Error::::PriceLimitExceeded, - )), - ), - (150_000_000_000, 100_000_000_000, 1_500_000_001, Ok(49)), + (150_000_000_000, 100_000_000_000, 1_500_000_000, Ok(5)), + (150_000_000_000, 100_000_000_000, 1_500_000_001, Ok(51)), ( 150_000_000_000, 100_000_000_000, 6_000_000_000, - Ok(150_075_000_000), + Ok(150_000_000_000), ), // Miscellaneous overflows and underflows (u64::MAX / 2, u64::MAX, u64::MAX, Ok(u64::MAX)), @@ -2892,7 +2879,13 @@ fn test_max_amount_add_dynamic() { SubnetAlphaIn::::insert(netuid, alpha_in); // Force the swap to initialize - ::SwapInterface::init_swap(netuid, None); + SubtensorModule::swap_tao_for_alpha( + netuid, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); if !alpha_in.is_zero() { let expected_price = U96F32::from_num(tao_in) / U96F32::from_num(alpha_in); @@ -2912,7 +2905,7 @@ fn test_max_amount_add_dynamic() { Ok(v) => assert_abs_diff_eq!( SubtensorModule::get_max_amount_add(netuid, limit_price.into()).unwrap(), v, - epsilon = v / 10000 + epsilon = v / 100 ), } }); @@ -3006,7 +2999,7 @@ fn test_max_amount_remove_dynamic() { let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); - // tao_in, alpha_in, limit_price, expected_max_swappable (+ 0.05% fee) + // tao_in, alpha_in, limit_price, expected_max_swappable [ // Zero handling (no panics) ( @@ -3028,24 +3021,21 @@ fn test_max_amount_remove_dynamic() { (10_000_000_000, 10_000_000_000, 0, Ok(u64::MAX)), // Low bounds (numbers are empirical, it is only important that result // is sharply decreasing when limit price increases) - (1_000, 1_000, 0, Ok(u64::MAX)), - (1_001, 1_001, 0, Ok(u64::MAX)), - (1_001, 1_001, 1, Ok(17_646)), - (1_001, 1_001, 2, Ok(17_646)), - (1_001, 1_001, 1_001, Ok(17_646)), - (1_001, 1_001, 10_000, Ok(17_646)), - (1_001, 1_001, 100_000, Ok(17_646)), - (1_001, 1_001, 1_000_000, Ok(17_646)), - (1_001, 1_001, 10_000_000, Ok(9_103)), - (1_001, 1_001, 100_000_000, Ok(2_186)), + (1_000, 1_000, 0, Ok(4_308_000_000_000)), + (1_001, 1_001, 0, Ok(4_310_000_000_000)), + (1_001, 1_001, 1, Ok(31_750_000)), + (1_001, 1_001, 2, Ok(22_500_000)), + (1_001, 1_001, 1_001, Ok(1_000_000)), + (1_001, 1_001, 10_000, Ok(316_000)), + (1_001, 1_001, 100_000, Ok(100_000)), // Basic math - (1_000_000, 1_000_000, 250_000_000, Ok(1_010_000)), - (1_000_000, 1_000_000, 62_500_000, Ok(3_030_000)), + (1_000_000, 1_000_000, 250_000_000, Ok(1_000_000)), + (1_000_000, 1_000_000, 62_500_000, Ok(3_000_000)), ( 1_000_000_000_000, 1_000_000_000_000, 62_500_000, - Ok(3_030_000_000_000), + Ok(3_000_000_000_000), ), // Normal range values with edge cases and sanity checks (200_000_000_000, 100_000_000_000, 0, Ok(u64::MAX)), @@ -3053,13 +3043,13 @@ fn test_max_amount_remove_dynamic() { 200_000_000_000, 100_000_000_000, 500_000_000, - Ok(101_000_000_000), + Ok(100_000_000_000), ), ( 200_000_000_000, 100_000_000_000, 125_000_000, - Ok(303_000_000_000), + Ok(300_000_000_000), ), ( 200_000_000_000, @@ -3078,15 +3068,15 @@ fn test_max_amount_remove_dynamic() { )), ), (200_000_000_000, 100_000_000_000, 1_999_999_999, Ok(24)), - (200_000_000_000, 100_000_000_000, 1_999_999_990, Ok(250)), + (200_000_000_000, 100_000_000_000, 1_999_999_990, Ok(252)), // Miscellaneous overflows and underflows ( 21_000_000_000_000_000, 1_000_000, 21_000_000_000_000_000, - Ok(17_630_088), + Ok(30_700_000), ), - (21_000_000_000_000_000, 1_000_000, u64::MAX, Ok(67_000)), + (21_000_000_000_000_000, 1_000_000, u64::MAX, Ok(67_164)), ( 21_000_000_000_000_000, 1_000_000_000_000_000_000, @@ -3099,13 +3089,13 @@ fn test_max_amount_remove_dynamic() { 21_000_000_000_000_000, 1_000_000_000_000_000_000, 20_000_000, - Ok(24_700_000_000_000_000), + Ok(24_800_000_000_000_000), ), ( 21_000_000_000_000_000, 21_000_000_000_000_000, 999_999_999, - Ok(10_605_000), + Ok(10_500_000), ), ( 21_000_000_000_000_000, @@ -3122,7 +3112,7 @@ fn test_max_amount_remove_dynamic() { SubnetAlphaIn::::insert(netuid, alpha_in); if !alpha_in.is_zero() { - let expected_price = U64F64::from_num(tao_in) / U64F64::from_num(alpha_in); + let expected_price = I96F32::from_num(tao_in) / I96F32::from_num(alpha_in); assert_eq!( ::SwapInterface::current_alpha_price(netuid.into()), expected_price @@ -3311,7 +3301,7 @@ fn test_max_amount_move_stable_dynamic() { dynamic_netuid, TaoCurrency::from(2_000_000_000) ), - Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 3.0 price => max is 0 @@ -3444,8 +3434,8 @@ fn test_max_amount_move_dynamic_stable() { assert_abs_diff_eq!( SubtensorModule::get_max_amount_move(dynamic_netuid, stable_netuid, 375_000_000.into()) .unwrap(), - alpha_in + alpha_in / 2000.into(), // + 0.05% fee - epsilon = alpha_in / 10_000.into(), + alpha_in, + epsilon = alpha_in / 1000.into(), ); // Precision test: @@ -3681,27 +3671,29 @@ fn test_max_amount_move_dynamic_dynamic() { expected_max_swappable, precision, )| { + let alpha_in_1 = AlphaCurrency::from(alpha_in_1); + let alpha_in_2 = AlphaCurrency::from(alpha_in_2); let expected_max_swappable = AlphaCurrency::from(expected_max_swappable); // Forse-set alpha in and tao reserve to achieve relative price of subnets SubnetTAO::::insert(origin_netuid, TaoCurrency::from(tao_in_1)); - SubnetAlphaIn::::insert(origin_netuid, AlphaCurrency::from(alpha_in_1)); + SubnetAlphaIn::::insert(origin_netuid, alpha_in_1); SubnetTAO::::insert(destination_netuid, TaoCurrency::from(tao_in_2)); - SubnetAlphaIn::::insert(destination_netuid, AlphaCurrency::from(alpha_in_2)); + SubnetAlphaIn::::insert(destination_netuid, alpha_in_2); if !alpha_in_1.is_zero() && !alpha_in_2.is_zero() { - let origin_price = tao_in_1 as f64 / alpha_in_1 as f64; - let dest_price = tao_in_2 as f64 / alpha_in_2 as f64; - if dest_price != 0. { + let origin_price = + I96F32::from_num(tao_in_1) / I96F32::from_num(u64::from(alpha_in_1)); + let dest_price = + I96F32::from_num(tao_in_2) / I96F32::from_num(u64::from(alpha_in_2)); + if dest_price != 0 { let expected_price = origin_price / dest_price; - assert_abs_diff_eq!( - (::SwapInterface::current_alpha_price( + assert_eq!( + ::SwapInterface::current_alpha_price( origin_netuid.into() ) / ::SwapInterface::current_alpha_price( destination_netuid.into() - )) - .to_num::(), - expected_price, - epsilon = 0.000_000_001 + ), + expected_price ); } } @@ -3798,7 +3790,7 @@ fn test_add_stake_limit_fill_or_kill() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); - let amount = 300_000_000_000; // over the maximum + let amount = 900_000_000_000; // over the maximum // add network let netuid = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); @@ -3818,7 +3810,9 @@ fn test_add_stake_limit_fill_or_kill() { SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); // Setup limit price so that it doesn't peak above 4x of current price - let limit_price = TaoCurrency::from(6_000_000_000); + // The amount that can be executed at this price is 450 TAO only + // Alpha produced will be equal to 25 = 100 - 450*100/(150+450) + let limit_price = TaoCurrency::from(24_000_000_000); // Add stake with slippage safety and check if it fails assert_noop!( @@ -3834,7 +3828,7 @@ fn test_add_stake_limit_fill_or_kill() { ); // Lower the amount and it should succeed now - let amount_ok = TaoCurrency::from(150_000_000_000); // fits the maximum + let amount_ok = TaoCurrency::from(450_000_000_000); // fits the maximum assert_ok!(SubtensorModule::add_stake_limit( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, @@ -4097,10 +4091,6 @@ fn test_remove_99_9991_per_cent_stake_removes_all() { let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Set fee rate to 0 so that alpha fee is not moved to block producer - // and the hotkey stake does drop to 0 - pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Give it some $$$ in his coldkey balance SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); @@ -4162,10 +4152,6 @@ fn test_remove_99_9989_per_cent_stake_leaves_a_little() { let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); - // Set fee rate to 0 so that alpha fee is not moved to block producer - // to avoid false success in this test - pallet_subtensor_swap::FeeRate::::insert(netuid, 0); - // Give it some $$$ in his coldkey balance SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); @@ -4574,15 +4560,13 @@ fn test_stake_into_subnet_low_amount() { false, false, )); - let expected_stake = (amount as f64) * 0.997 / current_price; + let expected_stake = AlphaCurrency::from(((amount as f64) * 0.997 / current_price) as u64); // Check if stake has increased assert_abs_diff_eq!( - u64::from(SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, &coldkey, netuid - )) as f64, + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid), expected_stake, - epsilon = expected_stake / 100. + epsilon = expected_stake / 100.into() ); }); } @@ -4854,16 +4838,40 @@ 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 block_builder = U256::from(12345u64); let amount = 1_000_000_000; let owner_balance_before = amount * 10; let user_balance_before = amount * 100; @@ -4872,6 +4880,9 @@ fn test_swap_fees_tao_correctness() { 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); @@ -4879,11 +4890,8 @@ fn test_swap_fees_tao_correctness() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Check starting "total TAO" - let block_builder_balance_before = SubtensorModule::get_coldkey_balance(&block_builder); - let total_tao_before = user_balance_before - + owner_balance_before - + SubnetTAO::::get(netuid).to_u64() - + block_builder_balance_before; + let total_tao_before = + user_balance_before + owner_balance_before + SubnetTAO::::get(netuid).to_u64(); // Get alpha for owner assert_ok!(SubtensorModule::add_stake( @@ -4892,6 +4900,7 @@ fn test_swap_fees_tao_correctness() { 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 @@ -4900,6 +4909,18 @@ fn test_swap_fees_tao_correctness() { .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( @@ -4910,6 +4931,7 @@ fn test_swap_fees_tao_correctness() { ((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, @@ -4923,25 +4945,15 @@ fn test_swap_fees_tao_correctness() { netuid, user_alpha, )); - - // Cause tao fees to propagate to SubnetTAO - let (claimed_tao_fees, _) = - ::SwapInterface::adjust_protocol_liquidity( - netuid, - 0.into(), - 0.into(), - ); - SubnetTAO::::mutate(netuid, |tao| *tao += claimed_tao_fees); + // 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 block_builder_balance_after = SubtensorModule::get_coldkey_balance(&block_builder); - let total_tao_after = user_balance_after + owner_balance_after + SubnetTAO::::get(netuid).to_u64() - + block_builder_balance_after; + + fees; // Total TAO does not change, leave some epsilon for rounding assert_abs_diff_eq!(total_tao_before, total_tao_after, epsilon = 2); @@ -5039,7 +5051,7 @@ fn test_remove_stake_full_limit_ok() { ); let new_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); - assert_abs_diff_eq!(new_balance, 9_086_700_000, epsilon = 1_000_000); + assert_abs_diff_eq!(new_balance, 9_086_000_000, epsilon = 1_000_000); }); } @@ -5123,10 +5135,9 @@ fn test_remove_stake_full_limit_ok_with_no_limit_price() { ); let new_balance = SubtensorModule::get_coldkey_balance(&coldkey_account_id); - assert_abs_diff_eq!(new_balance, 9_086_700_000, epsilon = 1_000_000); + assert_abs_diff_eq!(new_balance, 9_086_000_000, epsilon = 1_000_000); }); } - /// This test verifies that minimum stake amount is sufficient to move price and apply /// non-zero staking fees #[test] @@ -5189,6 +5200,181 @@ fn test_default_min_stake_sufficiency() { }); } +/// Test that modify_position always credits fees +/// +/// 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: Revise when user liquidity is available +// 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(|| { @@ -5199,13 +5385,19 @@ fn test_large_swap() { // add network let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); - let tao = TaoCurrency::from(100_000_000u64); - let alpha = AlphaCurrency::from(1_000_000_000_000_000_u64); - SubnetTAO::::insert(netuid, tao); - SubnetAlphaIn::::insert(netuid, alpha); + pallet_subtensor_swap::EnabledUserLiquidity::::insert(NetUid::from(netuid), true); // Force the swap to initialize - ::SwapInterface::init_swap(netuid, None); + SubtensorModule::swap_tao_for_alpha( + netuid, + TaoCurrency::ZERO, + 1_000_000_000_000.into(), + false, + ) + .unwrap(); + + // TODO: Revise when user liquidity is available + // setup_positions(netuid.into()); let swap_amount = TaoCurrency::from(100_000_000_000_000); assert_ok!(SubtensorModule::add_stake( @@ -5446,15 +5638,11 @@ fn test_staking_records_flow() { )); // Check that outflow has been recorded (less unstaking fees) - // The block builder will receive a fraction of the fees in alpha and will be forced - // to unstake it. So, the additional out-flow is recorded for this. - let unstaked_block_builder_fraction = 1.; - let expected_unstake_fee = - expected_flow * fee_rate * (1. - unstaked_block_builder_fraction); + let expected_unstake_fee = expected_flow * fee_rate; assert_abs_diff_eq!( SubnetTaoFlow::::get(netuid), expected_unstake_fee as i64, - epsilon = ((expected_unstake_fee / 100.0) as i64).max(1) + epsilon = (expected_unstake_fee / 100.0) as i64 ); }); } diff --git a/pallets/subtensor/src/tests/subnet.rs b/pallets/subtensor/src/tests/subnet.rs index 4eb13fe5fb..a547b30a14 100644 --- a/pallets/subtensor/src/tests/subnet.rs +++ b/pallets/subtensor/src/tests/subnet.rs @@ -714,6 +714,63 @@ fn test_subtoken_enable_ok_for_burn_register_before_enable() { }); } +// #[test] +// fn test_user_liquidity_access_control() { +// new_test_ext(1).execute_with(|| { +// let owner_hotkey = U256::from(1); +// let owner_coldkey = U256::from(2); +// let not_owner = U256::from(999); // arbitrary non-owner + +// // add network +// let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + +// // Not owner, not root: should fail +// assert_noop!( +// Swap::toggle_user_liquidity(RuntimeOrigin::signed(not_owner), netuid, true), +// DispatchError::BadOrigin +// ); + +// // Subnet owner can enable +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::signed(owner_coldkey), +// netuid, +// true +// )); +// assert!(pallet_subtensor_swap::EnabledUserLiquidity::::get( +// NetUid::from(netuid) +// )); + +// // Root can disable +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid, +// false +// )); +// assert!(!pallet_subtensor_swap::EnabledUserLiquidity::::get( +// NetUid::from(netuid) +// )); + +// // Root can enable again +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid, +// true +// )); +// assert!(pallet_subtensor_swap::EnabledUserLiquidity::::get( +// NetUid::from(netuid) +// )); + +// // Subnet owner cannot disable (only root can disable) +// assert_noop!( +// Swap::toggle_user_liquidity(RuntimeOrigin::signed(owner_coldkey), netuid, false), +// DispatchError::BadOrigin +// ); +// assert!(pallet_subtensor_swap::EnabledUserLiquidity::::get( +// NetUid::from(netuid) +// )); +// }); +// } + // cargo test --package pallet-subtensor --lib -- tests::subnet::test_no_duplicates_in_symbol_static --exact --show-output #[test] fn test_no_duplicates_in_symbol_static() { diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 36d083344c..9d3bdbfc62 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -3,22 +3,21 @@ clippy::expect_used, clippy::indexing_slicing, clippy::panic, - clippy::unwrap_used, - clippy::arithmetic_side_effects + clippy::unwrap_used )] use approx::assert_abs_diff_eq; use codec::Encode; -use frame_support::dispatch::{DispatchInfo, GetDispatchInfo}; +use frame_support::dispatch::DispatchInfo; use frame_support::error::BadOrigin; use frame_support::traits::OnInitialize; use frame_support::traits::schedule::DispatchTime; use frame_support::traits::schedule::v3::Named as ScheduleNamed; +use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; use sp_core::{Get, H256, U256}; -use sp_runtime::traits::Hash; -use sp_runtime::traits::{DispatchInfoOf, DispatchTransaction, TransactionExtension}; +use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, SubnetInfo, TaoCurrency}; @@ -26,767 +25,501 @@ use subtensor_swap_interface::{SwapEngine, SwapHandler}; use super::mock; use super::mock::*; -use crate::extensions::SubtensorTransactionExtension; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; -use crate::{Call, Error}; - -fn run_to_block(n: u64) { - System::run_to_block_with::( - n, - frame_system::RunToBlockHooks::default().before_finalize(|bn| { - Timestamp::set_timestamp(bn); - }), - ); -} - +use crate::{Call, ColdkeySwapScheduleDuration, Error}; +// // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture +// #[test] +// fn test_swap_total_hotkey_coldkey_stakes_this_interval() { +// new_test_ext(1).execute_with(|| { +// let old_coldkey = U256::from(1); +// let new_coldkey = U256::from(2); +// let hotkey = U256::from(3); +// let stake = 100; +// let block = 42; + +// OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); +// TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey, old_coldkey, (stake, block)); + +// let mut weight = Weight::zero(); +// assert_ok!(SubtensorModule::perform_swap_coldkey( +// &old_coldkey, +// &new_coldkey, +// &mut weight +// )); + +// assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( +// hotkey, +// old_coldkey +// )); +// assert_eq!( +// TotalHotkeyColdkeyStakesThisInterval::::get(hotkey, new_coldkey), +// (stake, block) +// ); +// }); +// } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_subnet_owner --exact --nocapture #[test] -fn test_announce_coldkey_swap_works() { +fn test_swap_subnet_owner() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let ed = ExistentialDeposit::get(); - - assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let netuid = NetUid::from(1u16); - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + ed); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), swap_cost + ed); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, old_coldkey); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight )); - let delay = ColdkeySwapAnnouncementDelay::::get(); - let now = System::block_number(); - assert_eq!( - ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now + delay, new_coldkey_hash))] - ); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), ed); - assert_eq!( - last_event(), - RuntimeEvent::SubtensorModule(Event::ColdkeySwapAnnounced { - who, - new_coldkey_hash, - }) - ); + assert_eq!(SubnetOwner::::get(netuid), new_coldkey); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_total_coldkey_stake --exact --show-output #[test] -fn test_announce_coldkey_swap_with_existing_announcement_past_delay_works() { +fn test_swap_total_coldkey_stake() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let new_coldkey_2 = U256::from(3); - let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); + let other_coldkey = U256::from(3); + let hotkey = U256::from(4); + let other_hotkey = U256::from(5); + let stake = DefaultMinStake::::get().to_u64() * 10; - assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); + let netuid = NetUid::from(1u16); + add_network(netuid, 1, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake * 2 + 1_000); + register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); + register_ok_neuron(netuid, other_hotkey, other_coldkey, 1001000); - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - SubtensorModule::add_balance_to_coldkey_account(&who, 2 * swap_cost); + let reserve = stake * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + netuid, + stake.into() )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + other_hotkey, + netuid, + stake.into() + )); + let total_stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - assert_eq!( - ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now + delay, new_coldkey_hash))] - ); - - let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); - run_to_block(now + delay + reannouncement_delay); - - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_2_hash, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight )); - let now = System::block_number(); assert_eq!( - ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now + delay, new_coldkey_2_hash))] + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + total_stake_before_swap ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_staking_hotkeys --exact --nocapture #[test] -fn test_announce_coldkey_swap_only_pays_swap_cost_if_no_announcement_exists() { +fn test_swap_staking_hotkeys() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let new_coldkey_2 = U256::from(3); - let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); - let ed = ExistentialDeposit::get(); + let hotkey = U256::from(3); - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + ed); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), swap_cost + ed); + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight )); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), ed); - - let now = System::block_number(); - let base_delay = ColdkeySwapAnnouncementDelay::::get(); - let reannouncement_delay = ColdkeySwapReannouncementDelay::::get(); - run_to_block(now + base_delay + reannouncement_delay); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_2_hash, - )); - assert_eq!(SubtensorModule::get_coldkey_balance(&who), ed); + assert!(StakingHotkeys::::get(old_coldkey).is_empty()); + assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey]); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_hotkey_owners --exact --nocapture #[test] -fn test_announce_coldkey_swap_with_bad_origin_fails() { +fn test_swap_hotkey_owners() { new_test_ext(1).execute_with(|| { - let new_coldkey = U256::from(1); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::none(), new_coldkey_hash), - BadOrigin - ); + Owner::::insert(hotkey, old_coldkey); + OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::root(), new_coldkey_hash), - BadOrigin - ); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + + assert_eq!(Owner::::get(hotkey), new_coldkey); + assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); + assert_eq!(OwnedHotkeys::::get(new_coldkey), vec![hotkey]); }); } - +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_transfer_remaining_balance --exact --nocapture #[test] -fn test_announce_coldkey_swap_with_existing_announcement_not_past_delay_fails() { +fn test_transfer_remaining_balance() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let new_coldkey_2 = U256::from(3); - let new_coldkey_2_hash = ::Hashing::hash_of(&new_coldkey_2); + let balance = 100; - assert_eq!(ColdkeySwapAnnouncements::::iter().count(), 0); - - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - let ed = ExistentialDeposit::get(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + ed); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight )); - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - assert_eq!( - ColdkeySwapAnnouncements::::iter().collect::>(), - vec![(who, (now + delay, new_coldkey_hash))] - ); - - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_2_hash,), - Error::::ColdkeySwapReannouncedTooEarly - ); + assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); + assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_no_stake --exact --show-output #[test] -fn test_swap_coldkey_announced_works() { +fn test_swap_with_no_stake() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let hotkey1 = U256::from(1001); - let hotkey2 = U256::from(1002); - let hotkey3 = U256::from(1003); - let ed = ExistentialDeposit::get(); - let min_stake = DefaultMinStake::::get().to_u64(); - let stake1 = min_stake * 10; - let stake2 = min_stake * 20; - let stake3 = min_stake * 30; - let now = System::block_number(); - - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - - // Run some blocks for the announcement to be past the delay - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; - run_to_block(now + delay); - - SubtensorModule::add_balance_to_coldkey_account(&who, stake1 + stake2 + stake3 + ed); - - let ( - netuid1, - netuid2, - hotkeys, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - identity, - balance_before, - total_stake_before, - ) = comprehensive_setup!( - who, - new_coldkey, - new_coldkey_hash, - stake1, - stake2, - stake3, - hotkey1, - hotkey2, - hotkey3 - ); - assert_ok!(SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who), - new_coldkey + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight )); - comprehensive_checks!( - who, - hotkey1, - hotkey2, - hotkey3, - hotkeys, - new_coldkey, - balance_before, - identity, - netuid1, - netuid2, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - total_stake_before, - 0_u64 // Charged on announcement + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + TaoCurrency::ZERO ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_multiple_hotkeys --exact --nocapture #[test] -fn test_swap_coldkey_announced_with_bad_origin_fails() { +fn test_swap_with_multiple_hotkeys() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); - assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::none(), new_coldkey), - BadOrigin - ); + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); - assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::root(), new_coldkey), - BadOrigin + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + + assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); + assert_eq!( + OwnedHotkeys::::get(new_coldkey), + vec![hotkey1, hotkey2] ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_multiple_subnets --exact --nocapture #[test] -fn test_swap_coldkey_announced_without_announcement_fails() { +fn test_swap_with_multiple_subnets() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); - assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), new_coldkey), - Error::::ColdkeySwapAnnouncementNotFound - ); - }) -} - -#[test] -fn test_swap_coldkey_announced_with_mismatched_coldkey_hash_fails() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let other_coldkey = U256::from(3); - let now = System::block_number(); + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid1, old_coldkey); + SubnetOwner::::insert(netuid2, old_coldkey); - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); - assert_noop!( - SubtensorModule::swap_coldkey_announced(RuntimeOrigin::signed(who), other_coldkey), - Error::::AnnouncedColdkeyHashDoesNotMatch - ); - }) + assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); + assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); + }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_zero_balance --exact --nocapture #[test] -fn test_swap_coldkey_announced_too_early_fails() { +fn test_swap_with_zero_balance() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - // Now case - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get(); - ColdkeySwapAnnouncements::::insert(who, (now + delay, new_coldkey_hash)); - - assert_noop!( - SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who), - new_coldkey - ), - Error::::ColdkeySwapTooEarly - ); - - // Now + delay - 1 case - run_to_block(now + delay - 1); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); - assert_noop!( - SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who), - new_coldkey - ), - Error::::ColdkeySwapTooEarly - ); - }) + assert_eq!(Balances::free_balance(old_coldkey), 0); + assert_eq!(Balances::free_balance(new_coldkey), 0); + }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_idempotency --exact --show-output #[test] -fn test_swap_coldkey_announced_with_already_associated_coldkey_fails() { +fn test_swap_idempotency() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); let hotkey = U256::from(3); + let netuid = NetUid::from(1u16); + let stake = DefaultMinStake::::get().to_u64() * 10; + let reserve = stake * 10; - let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); - let ed = ExistentialDeposit::get(); - SubtensorModule::add_balance_to_coldkey_account(&who, swap_cost + ed); + mock::setup_reserves(netuid, reserve.into(), reserve.into()); - assert_ok!(SubtensorModule::announce_coldkey_swap( - RuntimeOrigin::signed(who), - new_coldkey_hash, + // Add a network + add_network(netuid, 1, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake); // Give old coldkey some balance + // Stake to a hotkey + register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + netuid, + stake.into() )); - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; - run_to_block(now + delay); + // Get stake before swap + let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); - assert_noop!( - SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who), - new_coldkey - ), - Error::::ColdKeyAlreadyAssociated + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO ); - }) + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + stake_before_swap + ); + }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_max_values --exact --show-output #[test] -fn test_swap_coldkey_announced_with_hotkey_fails() { +fn test_swap_with_max_values() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let hotkey_hash = ::Hashing::hash_of(&hotkey); - let now = System::block_number(); - - ColdkeySwapAnnouncements::::insert(who, (now, hotkey_hash)); + let old_coldkey2 = U256::from(3); + let new_coldkey2 = U256::from(4); + let hotkey = U256::from(5); + let hotkey2 = U256::from(6); + let other_coldkey = U256::from(7); + let netuid = NetUid::from(1); + let netuid2 = NetUid::from(2); + let stake = 10_000; + let max_stake = 21_000_000_000_000_000; // 21 Million TAO; max possible balance. - let now = System::block_number(); - let delay = ColdkeySwapAnnouncementDelay::::get() + 1; - run_to_block(now + delay); + // Add a network + add_network(netuid, 1, 0); + add_network(netuid2, 1, 0); - SubtensorModule::create_account_if_non_existent(&new_coldkey, &hotkey); + // Register hotkey on each subnet. + // hotkey2 is owned by other_coldkey. + register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); + register_ok_neuron(netuid2, hotkey2, other_coldkey, 1001000); - assert_noop!( - SubtensorModule::swap_coldkey_announced( - ::RuntimeOrigin::signed(who), - hotkey - ), - Error::::NewColdKeyIsHotkey - ); - }) -} + // Give balance to old_coldkey and old_coldkey2. + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, max_stake + 1_000); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey2, max_stake + 1_000); -#[test] -fn test_swap_coldkey_works() { - new_test_ext(1000).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let hotkey1 = U256::from(1001); - let hotkey2 = U256::from(1002); - let hotkey3 = U256::from(1003); - let ed = ExistentialDeposit::get(); - let swap_cost = SubtensorModule::get_key_swap_cost(); - let min_stake = DefaultMinStake::::get().to_u64(); - let stake1 = min_stake * 10; - let stake2 = min_stake * 20; - let stake3 = min_stake * 30; + let reserve = max_stake * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + mock::setup_reserves(netuid2, reserve.into(), reserve.into()); - SubtensorModule::add_balance_to_coldkey_account( + // Stake to hotkey on each subnet. + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + netuid, + max_stake.into() + )); + let expected_stake1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &old_coldkey, - swap_cost.to_u64() + stake1 + stake2 + stake3 + ed, + netuid, ); - // Some old announcement and dispute that will be cleared - let now = System::block_number() - 100; - ColdkeySwapAnnouncements::::insert(old_coldkey, (now, new_coldkey_hash)); - ColdkeySwapDisputes::::insert(old_coldkey, now); - - let ( - netuid1, - netuid2, - hotkeys, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - identity, - balance_before, - total_stake_before, - ) = comprehensive_setup!( - old_coldkey, - new_coldkey, - new_coldkey_hash, - stake1, - stake2, - stake3, - hotkey1, + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey2), hotkey2, - hotkey3 + netuid2, + max_stake.into() + )); + let expected_stake2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey2, + netuid2, ); - assert_ok!(SubtensorModule::swap_coldkey( - ::RuntimeOrigin::root(), - old_coldkey, - new_coldkey, - swap_cost, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey2, + &new_coldkey2, + &mut weight )); - comprehensive_checks!( - old_coldkey, - hotkey1, - hotkey2, - hotkey3, - hotkeys, - new_coldkey, - balance_before, - identity, - netuid1, - netuid2, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - total_stake_before, - swap_cost.to_u64() + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + expected_stake1.to_u64().into(), + epsilon = TaoCurrency::from(expected_stake1.to_u64()) / 1000.into() + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey2), + TaoCurrency::ZERO + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey2), + expected_stake2.to_u64().into(), + epsilon = TaoCurrency::from(expected_stake2.to_u64()) / 1000.into() ); - - // Check that the old announcement and dispute are cleared - assert!(!ColdkeySwapAnnouncements::::contains_key(old_coldkey)); - assert!(!ColdkeySwapDisputes::::contains_key(old_coldkey)); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_with_non_existent_new_coldkey --exact --show-output #[test] -fn test_swap_coldkey_works_with_zero_cost() { +fn test_swap_with_non_existent_new_coldkey() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let hotkey1 = U256::from(1001); - let hotkey2 = U256::from(1002); - let hotkey3 = U256::from(1003); - let ed = ExistentialDeposit::get(); - let swap_cost = 0u64; - let min_stake = DefaultMinStake::::get().to_u64(); - let stake1 = min_stake * 10; - let stake2 = min_stake * 20; - let stake3 = min_stake * 30; + let hotkey = U256::from(3); + let stake = DefaultMinStake::::get().to_u64() * 10; + let netuid = NetUid::from(1); - SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake1 + stake2 + stake3 + ed, - ); + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); + // Give old coldkey some balance. + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake + 1_000); - let ( - netuid1, - netuid2, - hotkeys, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - identity, - balance_before, - total_stake_before, - ) = comprehensive_setup!( - old_coldkey, - new_coldkey, - new_coldkey_hash, - stake1, - stake2, - stake3, - hotkey1, - hotkey2, - hotkey3 - ); + let reserve = stake * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); - assert_ok!(SubtensorModule::swap_coldkey( - ::RuntimeOrigin::root(), - old_coldkey, - new_coldkey, - swap_cost.into(), + // Stake to hotkey. + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + netuid, + stake.into() )); - - comprehensive_checks!( - old_coldkey, - hotkey1, - hotkey2, - hotkey3, - hotkeys, - new_coldkey, - balance_before, - identity, - netuid1, - netuid2, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - total_stake_before, - swap_cost - ); - }); -} - -#[test] -fn test_swap_coldkey_with_bad_origin_fails() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let old_coldkey = U256::from(2); - let new_coldkey = U256::from(3); - let swap_cost = SubtensorModule::get_key_swap_cost(); - - assert_noop!( - SubtensorModule::swap_coldkey( - ::RuntimeOrigin::signed(who), - old_coldkey, - new_coldkey, - swap_cost, - ), - BadOrigin - ); - - assert_noop!( - SubtensorModule::swap_coldkey( - ::RuntimeOrigin::none(), - old_coldkey, - new_coldkey, - swap_cost - ), - BadOrigin - ); - }); -} - -#[test] -fn test_swap_coldkey_with_not_enough_balance_to_pay_swap_cost_fails() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let swap_cost = SubtensorModule::get_key_swap_cost(); - - // No balance to pay swap cost - assert_noop!( - SubtensorModule::swap_coldkey( - RuntimeOrigin::root(), - old_coldkey, - new_coldkey, - swap_cost - ), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - - // Needs to preserve ED - let balance = SubtensorModule::get_key_swap_cost().to_u64() + ExistentialDeposit::get() - 1; - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - assert_noop!( - SubtensorModule::swap_coldkey( - RuntimeOrigin::root(), - old_coldkey, - new_coldkey, - swap_cost - ), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - }); -} - -#[test] -fn test_do_swap_coldkey_preserves_new_coldkey_identity() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - - let old_identity = ChainIdentityV2 { - name: b"Old identity".to_vec(), - ..Default::default() - }; - IdentitiesV2::::insert(who, old_identity.clone()); - - let new_identity = ChainIdentityV2 { - name: b"New identity".to_vec(), - ..Default::default() - }; - IdentitiesV2::::insert(new_coldkey, new_identity.clone()); - - assert_ok!(SubtensorModule::do_swap_coldkey(&who, &new_coldkey,)); - - // Identity is preserved - assert_eq!(IdentitiesV2::::get(who), Some(old_identity)); - assert_eq!(IdentitiesV2::::get(new_coldkey), Some(new_identity)); - }); -} - -#[test] -fn test_announce_coldkey_swap_with_not_enough_balance_to_pay_swap_cost_fails() { - new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - - // No balance to pay swap cost - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - - // Needs to preserve ED - let balance = SubtensorModule::get_key_swap_cost().to_u64() + ExistentialDeposit::get() - 1; - SubtensorModule::add_balance_to_coldkey_account(&who, balance); - assert_noop!( - SubtensorModule::announce_coldkey_swap(RuntimeOrigin::signed(who), new_coldkey_hash), - Error::::NotEnoughBalanceToPaySwapColdKey + let expected_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &old_coldkey, + netuid, ); - }); -} - -#[test] -fn test_do_swap_coldkey_with_no_stake() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), TaoCurrency::ZERO ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - TaoCurrency::ZERO + + let actual_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &new_coldkey, + netuid, + ); + assert_abs_diff_eq!( + actual_stake, + expected_stake, + epsilon = expected_stake / 1000.into() ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_max_hotkeys --exact --nocapture #[test] -fn test_do_swap_coldkey_with_max_values() { +fn test_swap_with_max_hotkeys() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let old_coldkey2 = U256::from(3); - let new_coldkey2 = U256::from(4); - let hotkey = U256::from(5); - let hotkey2 = U256::from(6); - let other_coldkey = U256::from(7); - let netuid = NetUid::from(1); - let netuid2 = NetUid::from(2); - let stake = 10_000; - let max_stake = 21_000_000_000_000_000; // 21 Million TAO; max possible balance. - - // Add a network - add_network(netuid, 1, 0); - add_network(netuid2, 1, 0); - - // Register hotkey on each subnet. - // hotkey2 is owned by other_coldkey. - register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); - register_ok_neuron(netuid2, hotkey2, other_coldkey, 1001000); - - // Give balance to old_coldkey and old_coldkey2. - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, max_stake + 1_000); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey2, max_stake + 1_000); + let max_hotkeys = 1000; + let hotkeys: Vec = (0..max_hotkeys).map(U256::from).collect(); - let reserve = max_stake * 10; - mock::setup_reserves(netuid, reserve.into(), reserve.into()); - mock::setup_reserves(netuid2, reserve.into(), reserve.into()); + OwnedHotkeys::::insert(old_coldkey, hotkeys.clone()); - // Stake to hotkey on each subnet. - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey, - netuid, - max_stake.into() - )); - let expected_stake1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, - netuid, - ); - - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey2), - hotkey2, - netuid2, - max_stake.into() - )); - let expected_stake2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey2, - netuid2, - ); - - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); - assert_ok!(SubtensorModule::do_swap_coldkey( - &old_coldkey2, - &new_coldkey2, + &new_coldkey, + &mut weight )); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), - TaoCurrency::ZERO - ); - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - expected_stake1.to_u64().into(), - epsilon = TaoCurrency::from(expected_stake1.to_u64()) / 1000.into() - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&old_coldkey2), - TaoCurrency::ZERO - ); - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_coldkey(&new_coldkey2), - expected_stake2.to_u64().into(), - epsilon = TaoCurrency::from(expected_stake2.to_u64()) / 1000.into() - ); + assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); + assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_effect_on_delegated_stake --exact --nocapture #[test] -fn test_do_swap_coldkey_effect_on_delegated_stake() { +fn test_swap_effect_on_delegated_stake() { new_test_ext(1).execute_with(|| { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); @@ -819,7 +552,12 @@ fn test_do_swap_coldkey_effect_on_delegated_stake() { let coldkey_stake_before = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); let delegator_stake_before = SubtensorModule::get_total_stake_for_coldkey(&delegator); - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -839,96 +577,596 @@ fn test_do_swap_coldkey_effect_on_delegated_stake() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_concurrent_modifications --exact --show-output #[test] -fn test_swap_delegated_stake_for_coldkey() { +fn test_swap_concurrent_modifications() { new_test_ext(1).execute_with(|| { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let other_coldkey = U256::from(3); - let hotkey1 = U256::from(4); - let hotkey2 = U256::from(5); - let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; - let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; + let hotkey = U256::from(3); let netuid = NetUid::from(1); + let initial_stake = 1_000_000_000_000; + let additional_stake = 500_000_000_000; - // Setup initial state - add_network(netuid, 1, 0); - register_ok_neuron(netuid, hotkey1, other_coldkey, 0); - register_ok_neuron(netuid, hotkey2, other_coldkey, 0); - - let reserve = (stake_amount1 + stake_amount2) * 10; + let reserve = (initial_stake + additional_stake) * 1000; mock::setup_reserves(netuid, reserve.into(), reserve.into()); - // Notice hotkey1 and hotkey2 are Owned by other_coldkey - // old_coldkey and new_coldkey therefore delegates stake to them - // === Give old_coldkey some balance === + // Setup initial state + add_network(netuid, 1, 1); SubtensorModule::add_balance_to_coldkey_account( - &old_coldkey, - stake_amount1 + stake_amount2 + 1_000_000, + &new_coldkey, + initial_stake + additional_stake + 1_000_000, ); + register_ok_neuron(netuid, hotkey, new_coldkey, 1001000); - // === Stake to hotkeys === assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey1, + <::RuntimeOrigin>::signed(new_coldkey), + hotkey, netuid, - stake_amount1.into() + initial_stake.into() )); - let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, + + // Verify initial stake + let stake_before_swap = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &new_coldkey, netuid, ); - let (expected_stake_alpha2, fee) = mock::swap_tao_to_alpha(netuid, stake_amount2.into()); + // Wait some blocks + step_block(10); + + // Simulate concurrent stake addition assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(old_coldkey), - hotkey2, + <::RuntimeOrigin>::signed(new_coldkey), + hotkey, netuid, - stake_amount2.into() + additional_stake.into() )); - let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, - netuid, - ); - let fee = (expected_stake_alpha2.to_u64() as f64 * 0.003) as u64; - // Record initial values - let initial_total_issuance = SubtensorModule::get_total_issuance(); - let initial_total_stake = SubtensorModule::get_total_stake(); - let coldkey_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); - let stake_coldkey_hotkey1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, - &old_coldkey, - netuid, - ); - let stake_coldkey_hotkey2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &old_coldkey, + let stake_with_additional = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &new_coldkey, netuid, ); - let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); - let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey,)); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); - // Verify stake transfer assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey1, + &hotkey, &new_coldkey, netuid ), - expected_stake_alpha1 + stake_with_additional ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey2, - &new_coldkey, - netuid - ), + assert!(stake_with_additional > stake_before_swap); + assert!(!Alpha::::contains_key((hotkey, old_coldkey, netuid))); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_invalid_subnet_ownership --exact --nocapture +#[test] +fn test_swap_with_invalid_subnet_ownership() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let netuid = NetUid::from(1u16); + + SubnetOwner::::insert(netuid, old_coldkey); + + // Simulate an invalid state where the subnet owner doesn't match the old_coldkey + SubnetOwner::::insert(netuid, U256::from(3)); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + + // The swap should not affect the mismatched subnet ownership + assert_eq!(SubnetOwner::::get(netuid), U256::from(3)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_do_swap_coldkey_success --exact --show-output +#[test] +fn test_do_swap_coldkey_success() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let netuid = NetUid::from(1u16); + let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; + let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; + let swap_cost = SubtensorModule::get_key_swap_cost(); + let free_balance_old = 12345 + swap_cost.to_u64(); + + let reserve = (stake_amount1 + stake_amount2) * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey1, old_coldkey, 0); + register_ok_neuron(netuid, hotkey2, old_coldkey, 0); + + // Add balance to old coldkey + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + free_balance_old, + ); + + // Log initial state + log::info!( + "Initial total stake: {}", + SubtensorModule::get_total_stake() + ); + log::info!( + "Initial old coldkey stake: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) + ); + log::info!( + "Initial new coldkey stake: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) + ); + + // Add stake to the neurons + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + netuid, + stake_amount1.into() + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey2, + netuid, + stake_amount2.into() + )); + + // Insert an Identity + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity: ChainIdentityV2 = ChainIdentityV2 { + name: name.clone(), + url: vec![], + github_repo: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + IdentitiesV2::::insert(old_coldkey, identity.clone()); + + assert!(IdentitiesV2::::get(old_coldkey).is_some()); + assert!(IdentitiesV2::::get(new_coldkey).is_none()); + + // Log state after adding stake + log::info!( + "Total stake after adding: {}", + SubtensorModule::get_total_stake() + ); + log::info!( + "Old coldkey stake after adding: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) + ); + log::info!( + "New coldkey stake after adding: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) + ); + + // Record total stake before swap + let total_stake_before_swap = SubtensorModule::get_total_stake(); + + let hk1_alpha = Alpha::::get((hotkey1, old_coldkey, netuid)); + let hk2_alpha = Alpha::::get((hotkey2, old_coldkey, netuid)); + let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey( + // <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, + &new_coldkey, + swap_cost + )); + + // Log state after swap + log::info!( + "Total stake after swap: {}", + SubtensorModule::get_total_stake() + ); + log::info!( + "Old coldkey stake after swap: {}", + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey) + ); + log::info!( + "New coldkey stake after swap: {}", + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey) + ); + + // Verify the swap + assert_eq!(Owner::::get(hotkey1), new_coldkey); + assert_eq!(Owner::::get(hotkey2), new_coldkey); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + total_ck_stake + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO + ); + assert_eq!( + Alpha::::get((hotkey1, new_coldkey, netuid)), + hk1_alpha + ); + assert_eq!( + Alpha::::get((hotkey2, new_coldkey, netuid)), + hk2_alpha + ); + assert!(!Alpha::::contains_key((hotkey1, old_coldkey, netuid))); + assert!(!Alpha::::contains_key((hotkey2, old_coldkey, netuid))); + + // Verify OwnedHotkeys + let new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + assert!(new_owned_hotkeys.contains(&hotkey1)); + assert!(new_owned_hotkeys.contains(&hotkey2)); + assert_eq!(new_owned_hotkeys.len(), 2); + assert!(!OwnedHotkeys::::contains_key(old_coldkey)); + + // Verify balance transfer + assert_eq!( + SubtensorModule::get_coldkey_balance(&new_coldkey), + free_balance_old - swap_cost.to_u64() + ); + assert_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); + + // Verify total stake remains unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + total_stake_before_swap, + "Total stake changed unexpectedly" + ); + + // Verify identities were swapped + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + assert!(IdentitiesV2::::get(new_coldkey).is_some()); + assert_eq!( + IdentitiesV2::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + + // Verify event emission + System::assert_last_event( + Event::ColdkeySwapped { + old_coldkey, + new_coldkey, + swap_cost, + } + .into(), + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_stake_for_coldkey --exact --show-output +#[test] +fn test_swap_stake_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey1 = U256::from(3); + let hotkey2 = U256::from(4); + let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; + let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; + let stake_amount3 = DefaultMinStake::::get().to_u64() * 30; + let mut weight = Weight::zero(); + + // Setup initial state + // Add a network + let netuid = NetUid::from(1u16); + add_network(netuid, 1, 0); + + // Register hotkeys + register_ok_neuron(netuid, hotkey1, old_coldkey, 0); + register_ok_neuron(netuid, hotkey2, old_coldkey, 0); + // Give some balance to old coldkey + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + 1_000_000, + ); + + let reserve = (stake_amount1 + stake_amount2 + stake_amount3) * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + // Stake to hotkeys + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + netuid, + stake_amount1.into() + )); + let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid, + ); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey2, + netuid, + stake_amount2.into() + )); + let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid, + ); + + // Insert existing for same hotkey1 + // give new coldkey some balance + SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, stake_amount3 + 1_000_000); + // Stake to hotkey1 + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(new_coldkey), + hotkey1, + netuid, + stake_amount3.into() + )); + let expected_stake_alpha3 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid, + ); + + // Record initial values + let initial_total_issuance = SubtensorModule::get_total_issuance(); + let initial_total_stake = SubtensorModule::get_total_stake(); + let initial_total_stake_for_old_coldkey = + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + let initial_total_stake_for_new_coldkey = + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey); + let initial_total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); + let initial_total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); + + // Perform the swap + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify stake is additive, not replaced + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + initial_total_stake_for_old_coldkey + initial_total_stake_for_new_coldkey, + epsilon = 2.into() + ); + + // Verify ownership transfer + assert_eq!( + SubtensorModule::get_owned_hotkeys(&new_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!(SubtensorModule::get_owned_hotkeys(&old_coldkey), vec![]); + + // Verify stake transfer + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid + ), + expected_stake_alpha1 + expected_stake_alpha3 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &new_coldkey, + netuid + ), + expected_stake_alpha2 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid + ), + AlphaCurrency::ZERO + ); + + // Verify TotalHotkeyStake remains unchanged + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey1), + initial_total_hotkey1_stake + ); + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey2), + initial_total_hotkey2_stake + ); + + // Verify total stake and issuance remain unchanged + assert_eq!( + SubtensorModule::get_total_stake(), + initial_total_stake, + "Total stake changed unexpectedly" + ); + assert_eq!( + SubtensorModule::get_total_issuance(), + initial_total_issuance, + "Total issuance changed unexpectedly" + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_staking_hotkeys_for_coldkey --exact --show-output +#[test] +fn test_swap_staking_hotkeys_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let other_coldkey = U256::from(3); + let hotkey1 = U256::from(4); + let hotkey2 = U256::from(5); + let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; + let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; + let mut weight = Weight::zero(); + + // Setup initial state + // Add a network + let netuid = NetUid::from(1u16); + add_network(netuid, 1, 0); + // Give some balance to old coldkey + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + 1_000_000, + ); // Register hotkeys + register_ok_neuron(netuid, hotkey1, old_coldkey, 0); + register_ok_neuron(netuid, hotkey2, other_coldkey, 0); + + let reserve = (stake_amount1 + stake_amount2) * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + // Stake to hotkeys + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + netuid, + stake_amount1.into() + )); + let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid, + ); + + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey2, + netuid, + stake_amount2.into() + )); + let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid, + ); + + // Perform the swap + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify StakingHotkeys transfer + assert_eq!( + StakingHotkeys::::get(new_coldkey), + vec![hotkey1, hotkey2] + ); + assert_eq!(StakingHotkeys::::get(old_coldkey), vec![]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_swap_delegated_stake_for_coldkey --exact --show-output +#[test] +fn test_swap_delegated_stake_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let other_coldkey = U256::from(3); + let hotkey1 = U256::from(4); + let hotkey2 = U256::from(5); + let stake_amount1 = DefaultMinStake::::get().to_u64() * 10; + let stake_amount2 = DefaultMinStake::::get().to_u64() * 20; + let mut weight = Weight::zero(); + let netuid = NetUid::from(1); + + // Setup initial state + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey1, other_coldkey, 0); + register_ok_neuron(netuid, hotkey2, other_coldkey, 0); + + let reserve = (stake_amount1 + stake_amount2) * 10; + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + // Notice hotkey1 and hotkey2 are Owned by other_coldkey + // old_coldkey and new_coldkey therefore delegates stake to them + // === Give old_coldkey some balance === + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + 1_000_000, + ); + + // === Stake to hotkeys === + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey1, + netuid, + stake_amount1.into() + )); + let expected_stake_alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid, + ); + + let (expected_stake_alpha2, fee) = mock::swap_tao_to_alpha(netuid, stake_amount2.into()); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey2, + netuid, + stake_amount2.into() + )); + let expected_stake_alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid, + ); + let fee = (expected_stake_alpha2.to_u64() as f64 * 0.003) as u64; + + // Record initial values + let initial_total_issuance = SubtensorModule::get_total_issuance(); + let initial_total_stake = SubtensorModule::get_total_stake(); + let coldkey_stake = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + let stake_coldkey_hotkey1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &old_coldkey, + netuid, + ); + let stake_coldkey_hotkey2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &old_coldkey, + netuid, + ); + let total_hotkey1_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey1); + let total_hotkey2_stake = SubtensorModule::get_total_stake_for_hotkey(&hotkey2); + + // Perform the swap + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify stake transfer + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey1, + &new_coldkey, + netuid + ), + expected_stake_alpha1 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey2, + &new_coldkey, + netuid + ), expected_stake_alpha2 ); assert_eq!( @@ -982,6 +1220,85 @@ fn test_swap_delegated_stake_for_coldkey() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_subnet_owner_for_coldkey --exact --nocapture +#[test] +fn test_swap_subnet_owner_for_coldkey() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let netuid1 = NetUid::from(1); + let netuid2 = NetUid::from(2); + let mut weight = Weight::zero(); + + // Initialize SubnetOwner for old_coldkey + add_network(netuid1, 13, 0); + add_network(netuid2, 14, 0); + SubnetOwner::::insert(netuid1, old_coldkey); + SubnetOwner::::insert(netuid2, old_coldkey); + + // Set up TotalNetworks + TotalNetworks::::put(3); + + // Perform the swap + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); + + // Verify the swap + assert_eq!(SubnetOwner::::get(netuid1), new_coldkey); + assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_do_swap_coldkey_with_subnet_ownership --exact --nocapture +#[test] +fn test_do_swap_coldkey_with_subnet_ownership() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let netuid = NetUid::from(1u16); + let stake_amount = 1000; + let swap_cost = SubtensorModule::get_key_swap_cost().to_u64(); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 0); + + // Set TotalNetworks because swap relies on it + crate::TotalNetworks::::set(1); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, stake_amount + swap_cost); + SubnetOwner::::insert(netuid, old_coldkey); + + // Populate OwnedHotkeys map + OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + swap_cost.into() + )); + + // Verify subnet ownership transfer + assert_eq!(SubnetOwner::::get(netuid), new_coldkey); + }); +} +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_coldkey_has_associated_hotkeys --exact --nocapture +#[test] +fn test_coldkey_has_associated_hotkeys() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = NetUid::from(1u16); + + // Setup initial state + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1000); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_swap_total --exact --show-output #[test] fn test_coldkey_swap_total() { new_test_ext(1).execute_with(|| { @@ -1206,7 +1523,12 @@ fn test_coldkey_swap_total() { SubtensorModule::get_total_stake_for_coldkey(&coldkey), ck_stake ); - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey, + &mut weight + )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), ck_stake @@ -1295,8 +1617,9 @@ fn test_coldkey_swap_total() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_delegations --exact --show-output #[test] -fn test_do_swap_coldkey_effect_on_delegations() { +fn test_coldkey_delegations() { new_test_ext(1).execute_with(|| { let new_coldkey = U256::from(0); let owner = U256::from(1); @@ -1340,417 +1663,954 @@ fn test_do_swap_coldkey_effect_on_delegations() { stake.into() )); - // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&coldkey, &new_coldkey,)); + // Perform the swap + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &coldkey, + &new_coldkey, + &mut weight + )); + + // Verify stake was moved for the delegate + let approx_total_stake = TaoCurrency::from(stake * 2 - fee * 2); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_hotkey(&delegate), + approx_total_stake, + epsilon = approx_total_stake / 100.into() + ); + assert_eq!( + SubtensorModule::get_total_stake_for_coldkey(&coldkey), + TaoCurrency::ZERO + ); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), + approx_total_stake, + epsilon = approx_total_stake / 100.into() + ); + assert_eq!( + expected_stake, + Alpha::::get((delegate, new_coldkey, netuid)) + .to_num::() + .into(), + ); + assert_eq!(Alpha::::get((delegate, coldkey, netuid)), 0); + + assert_eq!( + expected_stake, + Alpha::::get((delegate, new_coldkey, netuid2)) + .to_num::() + .into() + ); + assert_eq!(Alpha::::get((delegate, coldkey, netuid2)), 0); + }); +} + +#[test] +fn test_schedule_swap_coldkey_success() { + new_test_ext(1).execute_with(|| { + // Initialize test accounts + let old_coldkey: U256 = U256::from(1); + let new_coldkey: U256 = U256::from(2); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + + // Add balance to the old coldkey account + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 1_000); + + // Schedule the coldkey swap + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + )); + + // Get the current block number + let current_block: u64 = System::block_number(); + + // Calculate the expected execution block (5 days from now) + let expected_execution_block: u64 = current_block + 5 * 24 * 60 * 60 / 12; + + // Check for the SwapScheduled event + System::assert_last_event( + Event::ColdkeySwapScheduled { + old_coldkey, + new_coldkey, + execution_block: expected_execution_block, + swap_cost, + } + .into(), + ); + + // TODO: Add additional checks to ensure the swap is correctly scheduled in the system + // For example, verify that the swap is present in the appropriate storage or scheduler + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_duplicate --exact --nocapture +#[test] +fn test_schedule_swap_coldkey_duplicate() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 2_000); + + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + )); + + // Attempt to schedule again + assert_noop!( + SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + ), + Error::::SwapAlreadyScheduled + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_schedule_swap_coldkey_execution --exact --show-output --nocapture +#[test] +fn test_schedule_swap_coldkey_execution() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let netuid = NetUid::from(1u16); + let stake_amount = DefaultMinStake::::get().to_u64() * 10; + let reserve = stake_amount * 10; + + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000000000000000); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + netuid, + stake_amount.into() + )); + + // Check initial ownership + assert_eq!( + Owner::::get(hotkey), + old_coldkey, + "Initial ownership check failed" + ); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + + // Schedule the swap + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + )); + + // Get the scheduled execution block + let current_block = System::block_number(); + let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); + + System::assert_last_event( + Event::ColdkeySwapScheduled { + old_coldkey, + new_coldkey, + execution_block, + swap_cost, + } + .into(), + ); + + run_to_block(execution_block - 1); - // Verify stake was moved for the delegate - let approx_total_stake = TaoCurrency::from(stake * 2 - fee * 2); - assert_abs_diff_eq!( - SubtensorModule::get_total_stake_for_hotkey(&delegate), - approx_total_stake, - epsilon = approx_total_stake / 100.into() + let stake_before_swap = SubtensorModule::get_total_stake_for_coldkey(&old_coldkey); + + run_to_block(execution_block); + + // Run on_initialize for the execution block + >::on_initialize(execution_block); + + // Also run Scheduler's on_initialize + as OnInitialize>::on_initialize( + execution_block, ); + + // Check if the swap has occurred + let new_owner = Owner::::get(hotkey); assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&coldkey), - TaoCurrency::ZERO + new_owner, new_coldkey, + "Ownership was not updated as expected" ); - assert_abs_diff_eq!( + + assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - approx_total_stake, - epsilon = approx_total_stake / 100.into() + stake_before_swap, + "Stake was not transferred to new coldkey" ); assert_eq!( - expected_stake, - Alpha::::get((delegate, new_coldkey, netuid)) - .to_num::() - .into(), + SubtensorModule::get_total_stake_for_coldkey(&old_coldkey), + TaoCurrency::ZERO, + "Old coldkey still has stake" ); - assert_eq!(Alpha::::get((delegate, coldkey, netuid)), 0); - assert_eq!( - expected_stake, - Alpha::::get((delegate, new_coldkey, netuid2)) - .to_num::() - .into() + // Check for the SwapExecuted event + System::assert_has_event( + Event::ColdkeySwapped { + old_coldkey, + new_coldkey, + swap_cost, + } + .into(), ); - assert_eq!(Alpha::::get((delegate, coldkey, netuid2)), 0); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_direct_swap_coldkey_call_fails --exact --nocapture #[test] -fn test_dispute_coldkey_swap_works() { +fn test_direct_swap_coldkey_call_fails() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); - - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - - assert_ok!(SubtensorModule::dispute_coldkey_swap( - RuntimeOrigin::signed(who) - )); - assert_eq!(ColdkeySwapDisputes::::get(who), Some(now)); - assert!(matches!( - last_event(), - RuntimeEvent::SubtensorModule(Event::ColdkeySwapDisputed { coldkey: _ }) - )); + assert_noop!( + SubtensorModule::swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + old_coldkey, + new_coldkey, + TaoCurrency::ZERO + ), + BadOrigin + ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_with_pending_swap --exact --nocapture #[test] -fn test_dispute_coldkey_swap_with_bad_origin_fails() { +fn test_schedule_swap_coldkey_with_pending_swap() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); + let old_coldkey = U256::from(1); + let new_coldkey1 = U256::from(2); + let new_coldkey2 = U256::from(3); - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); + let swap_cost = SubtensorModule::get_key_swap_cost(); - assert_noop!( - SubtensorModule::dispute_coldkey_swap(RuntimeOrigin::root()), - BadOrigin - ); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost.to_u64() + 1_000); + + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey1 + )); + // Attempt to schedule another swap before the first one executes assert_noop!( - SubtensorModule::dispute_coldkey_swap(RuntimeOrigin::none()), - BadOrigin + SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey2 + ), + Error::::SwapAlreadyScheduled ); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_failure_and_reschedule --exact --nocapture #[test] -fn test_dispute_coldkey_swap_without_announcement_fails() { +fn test_schedule_swap_coldkey_failure_and_reschedule() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); + let old_coldkey = U256::from(1); + let new_coldkey1 = U256::from(2); + let new_coldkey2 = U256::from(3); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + + // Two swaps + SubtensorModule::add_balance_to_coldkey_account( + &old_coldkey, + swap_cost.to_u64() + 1_000 * 2, + ); + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey1 + )); + + let current_block = >::block_number(); + let duration = ColdkeySwapScheduleDuration::::get(); + let when = current_block.saturating_add(duration); + + // Setup first key to fail + // -- will fail if the new coldkey is already a hotkey (has an Owner) + Owner::::insert(new_coldkey1, U256::from(4)); + + // First swap fails + run_to_block(when - 1); + next_block(); + + // Check the failure + next_block(); // Still in the scheduled-swap map + assert!(ColdkeySwapScheduled::::contains_key(old_coldkey)); + + // Try to schedule the second swap assert_noop!( - SubtensorModule::dispute_coldkey_swap(RuntimeOrigin::signed(who)), - Error::::ColdkeySwapAnnouncementNotFound + SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey2 + ), + Error::::SwapAlreadyScheduled ); + + // Wait for correct duration after first swap fails + let fail_duration = ColdkeySwapRescheduleDuration::::get(); + run_to_block(when + fail_duration); + + // Schedule the second swap + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey2 + )); + + let current_block = >::block_number(); + let duration = ColdkeySwapScheduleDuration::::get(); + let when = current_block.saturating_add(duration); + run_to_block(when - 1); + next_block(); + + // Check the success + next_block(); // Now in the scheduled-swap map + assert!(!ColdkeySwapScheduled::::contains_key(old_coldkey)); }); } #[test] -fn test_dispute_coldkey_swap_already_disputed_fails() { +fn test_coldkey_swap_delegate_identity_updated() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - ColdkeySwapDisputes::::insert(who, now); + let netuid = NetUid::from(1); + let burn_cost = TaoCurrency::from(10); + let tempo = 1; - assert_noop!( - SubtensorModule::dispute_coldkey_swap(RuntimeOrigin::signed(who)), - Error::::ColdkeySwapAlreadyDisputed + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Third Coolest Identity".to_vec(); + let identity: ChainIdentityV2 = ChainIdentityV2 { + name: name.clone(), + url: vec![], + image: vec![], + github_repo: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + IdentitiesV2::::insert(old_coldkey, identity.clone()); + + assert!(IdentitiesV2::::get(old_coldkey).is_some()); + assert!(IdentitiesV2::::get(new_coldkey).is_none()); + + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost + )); + + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + assert!(IdentitiesV2::::get(new_coldkey).is_some()); + assert_eq!( + IdentitiesV2::::get(new_coldkey).expect("Expected an Identity"), + identity ); }); } #[test] -fn test_reset_coldkey_swap_works() { +fn test_coldkey_swap_no_identity_no_changes() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); + let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - let new_coldkey_hash = ::Hashing::hash_of(&new_coldkey); - let now = System::block_number(); - ColdkeySwapAnnouncements::::insert(who, (now, new_coldkey_hash)); - ColdkeySwapDisputes::::insert(who, now); + let netuid = NetUid::from(1); + let burn_cost = TaoCurrency::from(10); + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); - assert_ok!(SubtensorModule::reset_coldkey_swap( - RuntimeOrigin::root(), - who, + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey )); - assert!(!ColdkeySwapAnnouncements::::contains_key(who)); - assert!(!ColdkeySwapDisputes::::contains_key(who)); - assert!(matches!( - last_event(), - RuntimeEvent::SubtensorModule(Event::ColdkeySwapReset { who }) + // Ensure the old coldkey does not have an identity before the swap + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost )); + + // Ensure no identities have been changed + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + assert!(IdentitiesV2::::get(new_coldkey).is_none()); }); } #[test] -fn test_reset_coldkey_swap_with_bad_origin_fails() { +fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { new_test_ext(1).execute_with(|| { - let who = U256::from(1); - let coldkey = U256::from(2); + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); - assert_noop!( - SubtensorModule::reset_coldkey_swap(RuntimeOrigin::signed(who), coldkey), - BadOrigin - ); + let netuid = NetUid::from(1); + let burn_cost = TaoCurrency::from(10); + let tempo = 1; - assert_noop!( - SubtensorModule::reset_coldkey_swap(RuntimeOrigin::none(), coldkey), - BadOrigin - ); + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + mock::setup_reserves(netuid, 1_000_000_000_000.into(), 1_000_000_000_000.into()); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Coolest Identity".to_vec(); + let identity: ChainIdentityV2 = ChainIdentityV2 { + name: name.clone(), + url: vec![], + github_repo: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + IdentitiesV2::::insert(new_coldkey, identity.clone()); + // Ensure the new coldkey does have an identity before the swap + assert!(IdentitiesV2::::get(new_coldkey).is_some()); + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost + )); + + // Ensure no identities have been changed + assert!(IdentitiesV2::::get(old_coldkey).is_none()); + assert!(IdentitiesV2::::get(new_coldkey).is_some()); }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_cant_schedule_swap_without_enough_to_burn --exact --nocapture #[test] -#[allow(deprecated)] -fn test_schedule_swap_coldkey_deprecated() { +fn test_cant_schedule_swap_without_enough_to_burn() { new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); + let hotkey = U256::from(5); + let burn_cost = SubtensorModule::get_key_swap_cost(); assert_noop!( SubtensorModule::schedule_swap_coldkey( - <::RuntimeOrigin>::root(), - new_coldkey, + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey ), - Error::::Deprecated + Error::::NotEnoughBalanceToPaySwapColdKey ); }); } -#[macro_export] -macro_rules! comprehensive_setup { - ( - $who:expr, - $new_coldkey:expr, - $new_coldkey_hash:expr, - $stake1:expr, - $stake2:expr, - $stake3:expr, - $hotkey1:expr, - $hotkey2:expr, - $hotkey3:expr - ) => {{ - // Setup networks and subnet ownerships - let netuid1 = NetUid::from(1); - let netuid2 = NetUid::from(2); - add_network(netuid1, 1, 0); - add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid1, $who); - SubnetOwner::::insert(netuid2, $who); - - // Setup reserves - let reserve1 = ($stake1 + $stake3) * 10; - let reserve2 = $stake2 * 10; - mock::setup_reserves(netuid1, reserve1.into(), reserve1.into()); - mock::setup_reserves(netuid2, reserve2.into(), reserve2.into()); - - // Setup auto stake destinations - AutoStakeDestination::::insert($who, netuid1, $hotkey1); - AutoStakeDestination::::insert($who, netuid2, $hotkey2); - AutoStakeDestinationColdkeys::::insert( - $hotkey1, - netuid1, - vec![$who, U256::from(3), U256::from(4)], - ); - AutoStakeDestinationColdkeys::::insert( - $hotkey2, - netuid2, - vec![U256::from(7), U256::from(8), $who], - ); +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_in_swap_schedule_prevents_funds_usage --exact --show-output --nocapture +#[test] +fn test_coldkey_in_swap_schedule_prevents_funds_usage() { + // Testing the signed extension validate function + // correctly filters transactions that attempt to use funds + // while a coldkey swap is scheduled. + + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let version_key: u64 = 0; + let coldkey = U256::from(0); + let new_coldkey = U256::from(1); + let hotkey: U256 = U256::from(2); // Add the hotkey field + assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! - // Setup neurons with stake - register_ok_neuron(netuid1, $hotkey1, $who, 0); - register_ok_neuron(netuid2, $hotkey2, $who, 0); - register_ok_neuron(netuid1, $hotkey3, $who, 0); + let stake = 100_000_000_000; + let reserve = stake * 100; + + mock::setup_reserves(netuid, reserve.into(), reserve.into()); + + let who = coldkey; // The coldkey signs this transaction + + // Disallowed transactions are + // - add_stake + // - add_stake_limit + // - swap_stake + // - swap_stake_limit + // - move_stake + // - transfer_stake + // - balances.transfer_all + // - balances.transfer_allow_death + // - balances.transfer_keep_alive + + // Allowed transactions are: + // - remove_stake + // - remove_stake_limit + // others... + + // Create netuid + add_network(netuid, 1, 0); + // Register the hotkey + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); - let hotkeys = vec![$hotkey1, $hotkey2, $hotkey3]; - assert_eq!(StakingHotkeys::::get($who), hotkeys); - assert_eq!(OwnedHotkeys::::get($who), hotkeys); - assert_eq!(Owner::::get($hotkey1), $who); - assert_eq!(Owner::::get($hotkey2), $who); - assert_eq!(Owner::::get($hotkey3), $who); + SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); + // Set the minimum stake to 0. + SubtensorModule::set_stake_threshold(0); + // Add stake to the hotkey assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed($who), - $hotkey1, - netuid1, - $stake1.into() + <::RuntimeOrigin>::signed(who), + hotkey, + netuid, + stake.into() )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed($who), - $hotkey2, - netuid2, - $stake2.into() + + // Schedule the coldkey for a swap + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(who), + new_coldkey )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed($who), - $hotkey3, - netuid1, - $stake3.into() - )); - let hk1_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey1, &$who, netuid1); - let hk2_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey2, &$who, netuid2); - let hk3_alpha = - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey3, &$who, netuid1); - let total_ck_stake = SubtensorModule::get_total_stake_for_coldkey(&$who); - - // Setup identity - let identity = ChainIdentityV2::default(); - IdentitiesV2::::insert($who, identity.clone()); - assert_eq!(IdentitiesV2::::get($who), Some(identity.clone())); - assert!(IdentitiesV2::::get($new_coldkey).is_none()); - - let balance_before = SubtensorModule::get_coldkey_balance(&$who); - let total_stake_before = SubtensorModule::get_total_stake(); - - ( - netuid1, - netuid2, - hotkeys, - hk1_alpha, - hk2_alpha, - hk3_alpha, - total_ck_stake, - identity, - balance_before, - total_stake_before, - ) - }}; + + assert!(ColdkeySwapScheduled::::contains_key(who)); + + // Setup the extension + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); + + // Try each call + + // Add stake + let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey, + netuid, + amount_staked: stake.into(), + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Add stake limit + let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake_limit { + hotkey, + netuid, + amount_staked: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Swap stake + let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_stake { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Swap stake limit + let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_stake_limit { + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + limit_price: stake.into(), + allow_partial: false, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Move stake + let call = RuntimeCall::SubtensorModule(SubtensorCall::move_stake { + origin_hotkey: hotkey, + destination_hotkey: hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Transfer stake + let call = RuntimeCall::SubtensorModule(SubtensorCall::transfer_stake { + destination_coldkey: new_coldkey, + hotkey, + origin_netuid: netuid, + destination_netuid: netuid, + alpha_amount: stake.into(), + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Transfer all + let call = RuntimeCall::Balances(BalancesCall::transfer_all { + dest: new_coldkey, + keep_alive: false, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Transfer keep alive + let call = RuntimeCall::Balances(BalancesCall::transfer_keep_alive { + dest: new_coldkey, + value: 100_000_000_000, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Transfer allow death + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: new_coldkey, + value: 100_000_000_000, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Burned register + let call = RuntimeCall::SubtensorModule(SubtensorCall::burned_register { netuid, hotkey }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + remove_stake_rate_limit_for_tests(&hotkey, &coldkey, netuid); + + // Remove stake + let call = RuntimeCall::SubtensorModule(SubtensorCall::remove_stake { + hotkey, + netuid, + amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Remove stake limit + let call = RuntimeCall::SubtensorModule(SubtensorCall::remove_stake_limit { + hotkey, + netuid, + amount_unstaked: (DefaultMinStake::::get().to_u64() * 2).into(), + limit_price: 123456789.into(), // should be low enough + allow_partial: true, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should fail + assert_eq!( + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() + ); + + // Schedule swap should succeed + let call = RuntimeCall::SubtensorModule(SubtensorCall::schedule_swap_coldkey { + new_coldkey: hotkey, + }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, + ); + // Should be ok + assert_ok!(result); + }); } -#[macro_export] -macro_rules! comprehensive_checks { - ( - $who:expr, - $hotkey1:expr, - $hotkey2:expr, - $hotkey3:expr, - $hotkeys:expr, - $new_coldkey:expr, - $balance_before:expr, - $identity:expr, - $netuid1:expr, - $netuid2:expr, - $hk1_alpha:expr, - $hk2_alpha:expr, - $hk3_alpha:expr, - $total_ck_stake:expr, - $total_stake_before:expr, - $swap_cost:expr - ) => { - // Ensure the announcement has been consumed - assert!(!ColdkeySwapAnnouncements::::contains_key($who)); +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --package pallet-subtensor --lib -- tests::swap_coldkey::test_coldkey_in_swap_schedule_prevents_critical_calls --exact --show-output --nocapture +#[test] +fn test_coldkey_in_swap_schedule_prevents_critical_calls() { + // Testing the signed extension validate function + // correctly filters transactions that are critical + // while a coldkey swap is scheduled. - // Ensure the cost has been withdrawn from the old coldkey and recycled - let balance_after = SubtensorModule::get_coldkey_balance(&$who); - let ed = ExistentialDeposit::get(); - assert_eq!($balance_before - $swap_cost, balance_after + ed); + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let version_key: u64 = 0; + let coldkey = U256::from(0); + let new_coldkey = U256::from(1); + let hotkey: U256 = U256::from(2); // Add the hotkey field + assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! + let stake = 100_000_000_000; + let reserve = stake * 10; - // Ensure the identity is correctly swapped - assert!(IdentitiesV2::::get($who).is_none()); - assert_eq!(IdentitiesV2::::get($new_coldkey), Some($identity)); + mock::setup_reserves(netuid, reserve.into(), reserve.into()); - // Ensure the subnet ownerships are correctly swapped - assert_eq!(SubnetOwner::::get($netuid1), $new_coldkey); - assert_eq!(SubnetOwner::::get($netuid2), $new_coldkey); + let who = coldkey; // The coldkey signs this transaction - // Ensure the auto stake destinations are correctly swapped - assert!(AutoStakeDestination::::get($who, $netuid1).is_none()); - assert!(AutoStakeDestination::::get($who, $netuid2).is_none()); - assert_eq!( - AutoStakeDestination::::get($new_coldkey, $netuid1), - Some($hotkey1) - ); - assert_eq!( - AutoStakeDestination::::get($new_coldkey, $netuid2), - Some($hotkey2) - ); - assert_eq!( - AutoStakeDestinationColdkeys::::get($hotkey1, $netuid1), - vec![U256::from(3), U256::from(4), $new_coldkey] - ); - assert_eq!( - AutoStakeDestinationColdkeys::::get($hotkey2, $netuid2), - vec![U256::from(7), U256::from(8), $new_coldkey] - ); + // Disallowed transactions are + // - dissolve_network - // Ensure the coldkey stake is correctly swapped - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey1, &$who, $netuid1), - 0.into(), - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey2, &$who, $netuid2), - 0.into(), - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&$hotkey3, &$who, $netuid1), - 0.into(), - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &$hotkey1, - &$new_coldkey, - $netuid1 - ), - $hk1_alpha - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &$hotkey2, - &$new_coldkey, - $netuid2 - ), - $hk2_alpha - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &$hotkey3, - &$new_coldkey, - $netuid1 - ), - $hk3_alpha - ); - assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&$who), - TaoCurrency::ZERO + // Create netuid + add_network(netuid, 1, 0); + // Register the hotkey + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + + SubtensorModule::add_balance_to_coldkey_account(&who, u64::MAX); + + // Set the minimum stake to 0. + SubtensorModule::set_stake_threshold(0); + // Add stake to the hotkey + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(who), + hotkey, + netuid, + stake.into() + )); + + // Schedule the coldkey for a swap + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(who), + new_coldkey + )); + + assert!(ColdkeySwapScheduled::::contains_key(who)); + + // Setup the extension + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); + + // Try each call + + // Dissolve network + let call = + RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { netuid, coldkey }); + let result = extension.validate( + RawOrigin::Signed(who).into(), + &call.clone(), + &info, + 10, + (), + &TxBaseImplication(()), + TransactionSource::External, ); + // Should fail assert_eq!( - SubtensorModule::get_total_stake_for_coldkey(&$new_coldkey), - $total_ck_stake, + // Should get an invalid transaction error + result.unwrap_err(), + CustomTransactionError::ColdkeyInSwapSchedule.into() ); + }); +} + +#[test] +fn test_swap_auto_stake_destination_coldkeys() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + let netuid = NetUid::from(1u16); + let coldkeys = vec![U256::from(4), U256::from(5), old_coldkey]; - // Ensure the staking hotkeys are correctly swapped - assert!(StakingHotkeys::::get($who).is_empty()); - assert_eq!(StakingHotkeys::::get($new_coldkey), $hotkeys); + add_network(netuid, 1, 0); + AutoStakeDestinationColdkeys::::insert(hotkey, netuid, coldkeys.clone()); + AutoStakeDestination::::insert(old_coldkey, netuid, hotkey); - // Ensure the hotkey ownership is correctly swapped - assert!(OwnedHotkeys::::get($who).is_empty()); - assert_eq!(OwnedHotkeys::::get($new_coldkey), $hotkeys); - assert_eq!(Owner::::get($hotkey1), $new_coldkey); - assert_eq!(Owner::::get($hotkey2), $new_coldkey); - assert_eq!(Owner::::get($hotkey3), $new_coldkey); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); - // Ensure the remaining balance is transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(&$who), 0); + let new_coldkeys = AutoStakeDestinationColdkeys::::get(hotkey, netuid); + assert!(new_coldkeys.contains(&new_coldkey)); + assert!(!new_coldkeys.contains(&old_coldkey)); assert_eq!( - SubtensorModule::get_coldkey_balance(&$new_coldkey), - ExistentialDeposit::get() + AutoStakeDestination::::try_get(old_coldkey, netuid), + Err(()) ); - - // Ensure total stake is unchanged assert_eq!( - SubtensorModule::get_total_stake(), - $total_stake_before, - "Total stake changed unexpectedly" + AutoStakeDestination::::try_get(new_coldkey, netuid), + Ok(hotkey) ); - - // Verify event emission - System::assert_last_event( - Event::ColdkeySwapped { - old_coldkey: $who, - new_coldkey: $new_coldkey, - } - .into(), - ); - }; + }); } diff --git a/pallets/subtensor/src/tests/voting_power.rs b/pallets/subtensor/src/tests/voting_power.rs deleted file mode 100644 index 63418fb6a9..0000000000 --- a/pallets/subtensor/src/tests/voting_power.rs +++ /dev/null @@ -1,760 +0,0 @@ -#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] - -use alloc::collections::BTreeMap; -use frame_support::weights::Weight; -use frame_support::{assert_err, assert_noop, assert_ok}; -use frame_system::RawOrigin; -use sp_core::U256; -use subtensor_runtime_common::NetUid; - -use super::mock; -use super::mock::*; -use crate::epoch::run_epoch::EpochTerms; -use crate::utils::voting_power::{ - MAX_VOTING_POWER_EMA_ALPHA, VOTING_POWER_DISABLE_GRACE_PERIOD_BLOCKS, -}; -use crate::*; - -// ============================================ -// === Test Helpers === -// ============================================ - -const DEFAULT_STAKE_AMOUNT: u64 = 1_000_000_000_000; // 1 million RAO - -/// Build epoch output from current state for testing voting power updates. -fn build_mock_epoch_output(netuid: NetUid) -> BTreeMap { - let n = SubtensorModule::get_subnetwork_n(netuid); - let validator_permits = ValidatorPermit::::get(netuid); - - let mut output = BTreeMap::new(); - for uid in 0..n { - if let Ok(hotkey) = SubtensorModule::get_hotkey_for_net_and_uid(netuid, uid) { - let has_permit = validator_permits - .get(uid as usize) - .copied() - .unwrap_or(false); - let stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid).to_u64(); - output.insert( - hotkey, - EpochTerms { - uid: uid as usize, - new_validator_permit: has_permit, - stake: stake.into(), - ..Default::default() - }, - ); - } - } - output -} - -/// Test fixture containing common test setup data -struct VotingPowerTestFixture { - hotkey: U256, - coldkey: U256, - netuid: NetUid, -} - -impl VotingPowerTestFixture { - /// Create a basic fixture with a dynamic network - fn new() -> Self { - let hotkey = U256::from(1); - let coldkey = U256::from(2); - let netuid = add_dynamic_network(&hotkey, &coldkey); - Self { - hotkey, - coldkey, - netuid, - } - } - - /// Setup reserves and add balance to coldkey for staking - fn setup_for_staking(&self) { - self.setup_for_staking_with_amount(DEFAULT_STAKE_AMOUNT); - } - - /// Setup reserves and add balance with custom amount - #[allow(clippy::arithmetic_side_effects)] - fn setup_for_staking_with_amount(&self, amount: u64) { - mock::setup_reserves(self.netuid, (amount * 100).into(), (amount * 100).into()); - SubtensorModule::add_balance_to_coldkey_account(&self.coldkey, amount * 10); - } - - /// Enable voting power tracking for the subnet - fn enable_tracking(&self) { - assert_ok!(SubtensorModule::enable_voting_power_tracking( - RuntimeOrigin::signed(self.coldkey), - self.netuid - )); - } - - /// Add stake from coldkey to hotkey - fn add_stake(&self, amount: u64) { - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(self.coldkey), - self.hotkey, - self.netuid, - amount.into() - )); - } - - /// Set validator permit for the hotkey (uid 0) - fn set_validator_permit(&self, has_permit: bool) { - ValidatorPermit::::insert(self.netuid, vec![has_permit]); - } - - /// Run voting power update for N epochs - fn run_epochs(&self, n: u32) { - for _ in 0..n { - let epoch_output = build_mock_epoch_output(self.netuid); - SubtensorModule::update_voting_power_for_subnet(self.netuid, &epoch_output); - } - } - - /// Get current voting power for the hotkey - fn get_voting_power(&self) -> u64 { - SubtensorModule::get_voting_power(self.netuid, &self.hotkey) - } - - /// Full setup: reserves, balance, tracking enabled, stake added, validator permit - fn setup_full(&self) { - self.setup_for_staking(); - self.enable_tracking(); - self.add_stake(DEFAULT_STAKE_AMOUNT); - self.set_validator_permit(true); - } -} - -// ============================================ -// === Test Enable/Disable Voting Power === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_enable_voting_power_tracking --exact --nocapture -#[test] -fn test_enable_voting_power_tracking() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Initially disabled - assert!(!SubtensorModule::get_voting_power_tracking_enabled( - f.netuid - )); - - // Enable tracking (subnet owner can do this) - f.enable_tracking(); - - // Now enabled - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - assert_eq!( - SubtensorModule::get_voting_power_disable_at_block(f.netuid), - 0 - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_enable_voting_power_tracking_root_can_enable --exact --nocapture -#[test] -fn test_enable_voting_power_tracking_root_can_enable() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Root can enable - assert_ok!(SubtensorModule::enable_voting_power_tracking( - RuntimeOrigin::root(), - f.netuid - )); - - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_disable_voting_power_tracking_schedules_disable --exact --nocapture -#[test] -fn test_disable_voting_power_tracking_schedules_disable() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.enable_tracking(); - - let current_block = SubtensorModule::get_current_block_as_u64(); - - // Schedule disable - assert_ok!(SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(f.coldkey), - f.netuid - )); - - // Still enabled, but scheduled for disable - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - let disable_at = SubtensorModule::get_voting_power_disable_at_block(f.netuid); - assert_eq!( - disable_at, - current_block + VOTING_POWER_DISABLE_GRACE_PERIOD_BLOCKS - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_disable_voting_power_tracking_fails_when_not_enabled --exact --nocapture -#[test] -fn test_disable_voting_power_tracking_fails_when_not_enabled() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Try to disable when not enabled - assert_noop!( - SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(f.coldkey), - f.netuid - ), - Error::::VotingPowerTrackingNotEnabled - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_enable_voting_power_tracking_non_owner_fails --exact --nocapture -#[test] -fn test_enable_voting_power_tracking_non_owner_fails() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - let random_account = U256::from(999); - - // Non-owner cannot enable (returns BadOrigin) - assert_noop!( - SubtensorModule::enable_voting_power_tracking( - RuntimeOrigin::signed(random_account), - f.netuid - ), - sp_runtime::DispatchError::BadOrigin - ); - - // Should still be disabled - assert!(!SubtensorModule::get_voting_power_tracking_enabled( - f.netuid - )); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_disable_voting_power_tracking_non_owner_fails --exact --nocapture -#[test] -fn test_disable_voting_power_tracking_non_owner_fails() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - let random_account = U256::from(999); - f.enable_tracking(); - - // Non-owner cannot disable (returns BadOrigin) - assert_noop!( - SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(random_account), - f.netuid - ), - sp_runtime::DispatchError::BadOrigin - ); - - // Should still be enabled with no disable scheduled - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - assert_eq!( - SubtensorModule::get_voting_power_disable_at_block(f.netuid), - 0 - ); - }); -} - -// ============================================ -// === Test EMA Alpha === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_set_voting_power_ema_alpha --exact --nocapture -#[test] -fn test_set_voting_power_ema_alpha() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Get default alpha - let default_alpha = SubtensorModule::get_voting_power_ema_alpha(f.netuid); - assert_eq!(default_alpha, 3_570_000_000_000_000); // 0.00357 * 10^18 = 2 weeks e-folding - - // Set new alpha (only root can do this) - let new_alpha: u64 = 500_000_000_000_000_000; // 0.5 * 10^18 - assert_ok!(SubtensorModule::sudo_set_voting_power_ema_alpha( - RuntimeOrigin::root(), - f.netuid, - new_alpha - )); - - assert_eq!( - SubtensorModule::get_voting_power_ema_alpha(f.netuid), - new_alpha - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_set_voting_power_ema_alpha_fails_above_one --exact --nocapture -#[test] -fn test_set_voting_power_ema_alpha_fails_above_one() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Try to set alpha > 1.0 (> 10^18) - let invalid_alpha: u64 = MAX_VOTING_POWER_EMA_ALPHA + 1; - assert_noop!( - SubtensorModule::sudo_set_voting_power_ema_alpha( - RuntimeOrigin::root(), - f.netuid, - invalid_alpha - ), - Error::::InvalidVotingPowerEmaAlpha - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_set_voting_power_ema_alpha_non_root_fails --exact --nocapture -#[test] -fn test_set_voting_power_ema_alpha_non_root_fails() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - - // Non-root cannot set alpha - assert_noop!( - SubtensorModule::sudo_set_voting_power_ema_alpha( - RuntimeOrigin::signed(f.coldkey), - f.netuid, - 500_000_000_000_000_000 - ), - sp_runtime::DispatchError::BadOrigin - ); - }); -} - -// ============================================ -// === Test EMA Calculation === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_ema_calculation --exact --nocapture -#[test] -fn test_voting_power_ema_calculation() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Initially voting power is 0 - assert_eq!(f.get_voting_power(), 0); - - // Run epoch to update voting power - f.run_epochs(1); - - // Voting power should now be > 0 (but less than full stake due to EMA starting from 0) - let voting_power_after_first_epoch = f.get_voting_power(); - assert!(voting_power_after_first_epoch > 0); - - // Run more epochs - voting power should increase towards stake - f.run_epochs(10); - - let voting_power_after_many_epochs = f.get_voting_power(); - assert!(voting_power_after_many_epochs > voting_power_after_first_epoch); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_cleared_when_deregistered --exact --nocapture -#[test] -fn test_voting_power_cleared_when_deregistered() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Run epochs to build up voting power - f.run_epochs(10); - - let voting_power_before = f.get_voting_power(); - assert!(voting_power_before > 0, "Voting power should be built up"); - - // Deregister the hotkey (simulate by removing from IsNetworkMember) - IsNetworkMember::::remove(f.hotkey, f.netuid); - - // Run epoch - voting power should be cleared for deregistered hotkey - f.run_epochs(1); - - // Should be removed from storage immediately when deregistered - assert_eq!(f.get_voting_power(), 0); - assert!( - !VotingPower::::contains_key(f.netuid, f.hotkey), - "Entry should be removed when hotkey is deregistered" - ); - }); -} - -// ============================================ -// === Test Validators Only === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_only_validators_get_voting_power --exact --nocapture -#[test] -fn test_only_validators_get_voting_power() { - new_test_ext(1).execute_with(|| { - let validator_hotkey = U256::from(1); - let miner_hotkey = U256::from(2); - let coldkey = U256::from(3); - - let netuid = add_dynamic_network(&validator_hotkey, &coldkey); - - mock::setup_reserves( - netuid, - (DEFAULT_STAKE_AMOUNT * 100).into(), - (DEFAULT_STAKE_AMOUNT * 100).into(), - ); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, DEFAULT_STAKE_AMOUNT * 20); - - // Register miner - register_ok_neuron(netuid, miner_hotkey, coldkey, 0); - - // Enable voting power tracking - assert_ok!(SubtensorModule::enable_voting_power_tracking( - RuntimeOrigin::signed(coldkey), - netuid - )); - - // Add stake to both - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - validator_hotkey, - netuid, - DEFAULT_STAKE_AMOUNT.into() - )); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(coldkey), - miner_hotkey, - netuid, - DEFAULT_STAKE_AMOUNT.into() - )); - - // Set validator permit: uid 0 (validator) has permit, uid 1 (miner) does not - ValidatorPermit::::insert(netuid, vec![true, false]); - - // Run epoch - let epoch_output = build_mock_epoch_output(netuid); - SubtensorModule::update_voting_power_for_subnet(netuid, &epoch_output); - - // Only validator should have voting power - assert!(SubtensorModule::get_voting_power(netuid, &validator_hotkey) > 0); - assert_eq!(SubtensorModule::get_voting_power(netuid, &miner_hotkey), 0); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_miner_voting_power_removed_when_loses_vpermit --exact --nocapture -#[test] -fn test_miner_voting_power_removed_when_loses_vpermit() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Run epochs to build voting power - f.run_epochs(10); - - let voting_power_before = f.get_voting_power(); - assert!(voting_power_before > 0); - - // Remove validator permit (now they're a miner) - f.set_validator_permit(false); - - // Run epoch - voting power should be removed - f.run_epochs(1); - - assert_eq!(f.get_voting_power(), 0); - }); -} - -// ============================================ -// === Test Hotkey Swap === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_transfers_on_hotkey_swap --exact --nocapture -#[test] -fn test_voting_power_transfers_on_hotkey_swap() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - let new_hotkey = U256::from(99); - let voting_power_value = 5_000_000_000_000_u64; - - // Set some voting power for the old hotkey - VotingPower::::insert(f.netuid, f.hotkey, voting_power_value); - - // Verify old hotkey has voting power - assert_eq!(f.get_voting_power(), voting_power_value); - assert_eq!(SubtensorModule::get_voting_power(f.netuid, &new_hotkey), 0); - - // Perform hotkey swap for this subnet - SubtensorModule::swap_voting_power_for_hotkey(&f.hotkey, &new_hotkey, f.netuid); - - // Old hotkey should have 0, new hotkey should have the voting power - assert_eq!(f.get_voting_power(), 0); - assert_eq!( - SubtensorModule::get_voting_power(f.netuid, &new_hotkey), - voting_power_value - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_swap_adds_to_existing --exact --nocapture -#[test] -fn test_voting_power_swap_adds_to_existing() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - let new_hotkey = U256::from(99); - let old_voting_power = 5_000_000_000_000_u64; - let new_existing_voting_power = 2_000_000_000_000_u64; - - // Set voting power for both hotkeys - VotingPower::::insert(f.netuid, f.hotkey, old_voting_power); - VotingPower::::insert(f.netuid, new_hotkey, new_existing_voting_power); - - // Perform swap - SubtensorModule::swap_voting_power_for_hotkey(&f.hotkey, &new_hotkey, f.netuid); - - // New hotkey should have combined voting power - assert_eq!(f.get_voting_power(), 0); - assert_eq!( - SubtensorModule::get_voting_power(f.netuid, &new_hotkey), - old_voting_power + new_existing_voting_power - ); - }); -} - -// ============================================ -// === Test Threshold Logic === -// ============================================ -// Tests the rule: Only remove voting power entry if it decayed FROM above threshold TO below. -// New validators building up from 0 should NOT be removed even if below threshold. - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_not_removed_if_never_above_threshold --exact --nocapture -#[test] -fn test_voting_power_not_removed_if_never_above_threshold() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Get the threshold - let min_stake = SubtensorModule::get_stake_threshold(); - - // Set voting power directly to a value below threshold (simulating building up) - // This is below threshold but was never above it - let below_threshold = min_stake.saturating_sub(1); - VotingPower::::insert(f.netuid, f.hotkey, below_threshold); - - // Run epoch - f.run_epochs(1); - - // Key assertion: Entry should NOT be removed because previous_ema was below threshold - // The removal rule only triggers when previous_ema >= threshold and new_ema < threshold - let voting_power = f.get_voting_power(); - assert!( - voting_power > 0, - "Voting power should still exist - it was never above threshold" - ); - assert!( - VotingPower::::contains_key(f.netuid, f.hotkey), - "Entry should exist - it was never above threshold so shouldn't be removed" - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_not_removed_with_small_dip_below_threshold --exact --nocapture -#[test] -fn test_voting_power_not_removed_with_small_dip_below_threshold() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_for_staking(); - f.enable_tracking(); - f.set_validator_permit(true); - - let min_stake = SubtensorModule::get_stake_threshold(); - - // Set voting power above threshold (validator was established) - let above_threshold = min_stake + 100; - VotingPower::::insert(f.netuid, f.hotkey, above_threshold); - - // Simulate a small dip: new EMA drops to 95% of threshold (within 10% buffer) - // This is above the removal threshold (90%) so should NOT be removed - let small_dip = min_stake * 95 / 100; - VotingPower::::insert(f.netuid, f.hotkey, small_dip); - - // Manually trigger the removal check by setting previous to above threshold - // and running with stake that would produce EMA in the buffer zone - VotingPower::::insert(f.netuid, f.hotkey, above_threshold); - - // Build epoch output with stake that will produce EMA around 95% of threshold - let mut epoch_output = build_mock_epoch_output(f.netuid); - if let Some(terms) = epoch_output.get_mut(&f.hotkey) { - terms.stake = small_dip.into(); // Stake drops but stays in buffer zone - } - - SubtensorModule::update_voting_power_for_subnet(f.netuid, &epoch_output); - - // Should NOT be removed - dip is within hysteresis buffer - assert!( - VotingPower::::contains_key(f.netuid, f.hotkey), - "Entry should exist - small dip within 10% buffer should not trigger removal" - ); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_removed_with_significant_drop_below_threshold --exact --nocapture -#[test] -fn test_voting_power_removed_with_significant_drop_below_threshold() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.enable_tracking(); - - // Use explicit values since get_stake_threshold() may return 0 in tests - let min_stake: u64 = 1_000_000_000; - StakeThreshold::::put(min_stake); - - // Set voting power above threshold (validator was established) - VotingPower::::insert(f.netuid, f.hotkey, min_stake); - - // Set alpha to 100% so new_ema = current_stake directly (for testing removal) - VotingPowerEmaAlpha::::insert(f.netuid, MAX_VOTING_POWER_EMA_ALPHA); - - // Build epoch output manually with stake = 0 and validator permit = true - let mut epoch_output = BTreeMap::new(); - epoch_output.insert( - f.hotkey, - EpochTerms { - uid: 0, - new_validator_permit: true, - stake: 0.into(), // Complete unstake - ..Default::default() - }, - ); - - // With alpha = 1.0: new_ema = 1.0 * 0 + 0 * previous = 0 - // 0 < removal_threshold (90% of min_stake = 900M) AND previous (1B) >= min_stake (1B) - // Should trigger removal - SubtensorModule::update_voting_power_for_subnet(f.netuid, &epoch_output); - - assert!( - !VotingPower::::contains_key(f.netuid, f.hotkey), - "Entry should be removed - stake dropped to 0 with alpha=1.0" - ); - }); -} - -// ============================================ -// === Test Tracking Not Active === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_not_updated_when_disabled --exact --nocapture -#[test] -fn test_voting_power_not_updated_when_disabled() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_for_staking(); - // DON'T enable voting power tracking - f.add_stake(DEFAULT_STAKE_AMOUNT); - f.set_validator_permit(true); - - // Run epoch - f.run_epochs(1); - - // Voting power should still be 0 since tracking is disabled - assert_eq!(f.get_voting_power(), 0); - }); -} - -// ============================================ -// === Test Re-enable After Disable === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_reenable_voting_power_clears_disable_schedule --exact --nocapture -#[test] -fn test_reenable_voting_power_clears_disable_schedule() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.enable_tracking(); - - // Schedule disable - assert_ok!(SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(f.coldkey), - f.netuid - )); - - assert!(SubtensorModule::get_voting_power_disable_at_block(f.netuid) > 0); - - // Re-enable should clear the disable schedule - f.enable_tracking(); - - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - assert_eq!( - SubtensorModule::get_voting_power_disable_at_block(f.netuid), - 0 - ); - }); -} - -// ============================================ -// === Test Grace Period Finalization === -// ============================================ - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_finalized_after_grace_period --exact --nocapture -#[test] -fn test_voting_power_finalized_after_grace_period() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Build up voting power - f.run_epochs(10); - - let voting_power_before = f.get_voting_power(); - assert!(voting_power_before > 0); - - // Schedule disable - assert_ok!(SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(f.coldkey), - f.netuid - )); - - let disable_at = SubtensorModule::get_voting_power_disable_at_block(f.netuid); - - // Advance block past grace period (time travel!) - System::set_block_number(disable_at + 1); - - // Run epoch - should finalize disable - f.run_epochs(1); - - // Tracking should be disabled and all entries cleared - assert!(!SubtensorModule::get_voting_power_tracking_enabled( - f.netuid - )); - assert_eq!( - SubtensorModule::get_voting_power_disable_at_block(f.netuid), - 0 - ); - assert_eq!(f.get_voting_power(), 0); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_continues_during_grace_period --exact --nocapture -#[test] -fn test_voting_power_continues_during_grace_period() { - new_test_ext(1).execute_with(|| { - let f = VotingPowerTestFixture::new(); - f.setup_full(); - - // Schedule disable - assert_ok!(SubtensorModule::disable_voting_power_tracking( - RuntimeOrigin::signed(f.coldkey), - f.netuid - )); - - let disable_at = SubtensorModule::get_voting_power_disable_at_block(f.netuid); - - // Set block to middle of grace period (time travel!) - System::set_block_number(disable_at - 1000); - - // Run epoch - should still update voting power during grace period - f.run_epochs(1); - - // Tracking should still be enabled and voting power should exist - assert!(SubtensorModule::get_voting_power_tracking_enabled(f.netuid)); - assert!(f.get_voting_power() > 0); - }); -} diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 2c64b23d32..20ace5ee0d 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -34,7 +34,7 @@ use w3f_bls::EngineBLS; use super::mock; use super::mock::*; use crate::coinbase::reveal_commits::{LegacyWeightsTlockPayload, WeightsTlockPayload}; -use crate::extensions::SubtensorTransactionExtension; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; /*************************** pub fn set_weights() tests diff --git a/pallets/subtensor/src/extensions/subtensor.rs b/pallets/subtensor/src/transaction_extension.rs similarity index 70% rename from pallets/subtensor/src/extensions/subtensor.rs rename to pallets/subtensor/src/transaction_extension.rs index a24a54f3fd..cf1d410ea9 100644 --- a/pallets/subtensor/src/extensions/subtensor.rs +++ b/pallets/subtensor/src/transaction_extension.rs @@ -1,30 +1,24 @@ use crate::{ - BalancesCall, Call, CheckColdkeySwap, Config, CustomTransactionError, Error, Pallet, + BalancesCall, Call, ColdkeySwapScheduled, Config, CustomTransactionError, Error, Pallet, TransactionType, }; use codec::{Decode, DecodeWithMemTracking, Encode}; use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use frame_support::pallet_prelude::Weight; use frame_support::traits::IsSubType; use scale_info::TypeInfo; use sp_runtime::traits::{ AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, TransactionExtension, ValidateResult, }; -use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError}; -use sp_runtime::{ - impl_tx_ext_default, - transaction_validity::{TransactionSource, TransactionValidity, ValidTransaction}, +use sp_runtime::transaction_validity::{ + TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }; use sp_std::marker::PhantomData; use sp_std::vec::Vec; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{NetUid, NetUidStorageIndex}; -const ADD_STAKE_BURN_PRIORITY_BOOST: u64 = 100; - -type CallOf = ::RuntimeCall; -type OriginOf = ::RuntimeOrigin; - #[freeze_struct("2e02eb32e5cb25d3")] #[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] pub struct SubtensorTransactionExtension(pub PhantomData); @@ -37,7 +31,9 @@ impl sp_std::fmt::Debug for SubtensorTransac impl SubtensorTransactionExtension where - CallOf: Dispatchable + IsSubType>, + ::RuntimeCall: + Dispatchable, + ::RuntimeCall: IsSubType>, { pub fn new() -> Self { Self(Default::default()) @@ -56,29 +52,30 @@ where pub fn result_to_validity(result: Result<(), Error>, priority: u64) -> TransactionValidity { if let Err(err) = result { Err(match err { - Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow, - Error::::SubnetNotExists => CustomTransactionError::SubnetNotExists, - Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow, + Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow.into(), + Error::::SubnetNotExists => CustomTransactionError::SubnetNotExists.into(), + Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow.into(), Error::::HotKeyAccountNotExists => { - CustomTransactionError::HotkeyAccountDoesntExist + CustomTransactionError::HotkeyAccountDoesntExist.into() } Error::::NotEnoughStakeToWithdraw => { - CustomTransactionError::NotEnoughStakeToWithdraw + CustomTransactionError::NotEnoughStakeToWithdraw.into() + } + Error::::InsufficientLiquidity => { + CustomTransactionError::InsufficientLiquidity.into() } - Error::::InsufficientLiquidity => CustomTransactionError::InsufficientLiquidity, - Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh, - Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed, + Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh.into(), + Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed.into(), Error::::HotKeyNotRegisteredInNetwork => { - CustomTransactionError::HotKeyNotRegisteredInNetwork + CustomTransactionError::HotKeyNotRegisteredInNetwork.into() } - Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress, + Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress.into(), Error::::ServingRateLimitExceeded => { - CustomTransactionError::ServingRateLimitExceeded + CustomTransactionError::ServingRateLimitExceeded.into() } - Error::::InvalidPort => CustomTransactionError::InvalidPort, - _ => CustomTransactionError::BadRequest, - } - .into()) + Error::::InvalidPort => CustomTransactionError::InvalidPort.into(), + _ => CustomTransactionError::BadRequest.into(), + }) } else { Ok(ValidTransaction { priority, @@ -88,58 +85,56 @@ where } } -impl TransactionExtension> for SubtensorTransactionExtension +impl + TransactionExtension<::RuntimeCall> + for SubtensorTransactionExtension where - T: Config - + Send - + Sync - + TypeInfo - + pallet_balances::Config - + pallet_subtensor_proxy::Config - + pallet_shield::Config, - CallOf: Dispatchable - + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, - OriginOf: AsSystemOriginSigner + Clone, + ::RuntimeCall: + Dispatchable, + ::RuntimeOrigin: AsSystemOriginSigner + Clone, + ::RuntimeCall: IsSubType>, + ::RuntimeCall: IsSubType>, { const IDENTIFIER: &'static str = "SubtensorTransactionExtension"; type Implicit = (); - type Val = (); + type Val = Option; type Pre = (); + fn weight(&self, _call: &::RuntimeCall) -> Weight { + // TODO: benchmark transaction extension + Weight::zero() + } + fn validate( &self, - origin: OriginOf, - call: &CallOf, - _info: &DispatchInfoOf>, + origin: ::RuntimeOrigin, + call: &::RuntimeCall, + _info: &DispatchInfoOf<::RuntimeCall>, _len: usize, _self_implicit: Self::Implicit, _inherited_implication: &impl Implication, _source: TransactionSource, - ) -> ValidateResult> { + ) -> ValidateResult::RuntimeCall> { // Ensure the transaction is signed, else we just skip the extension. let Some(who) = origin.as_system_origin_signer() else { - return Ok((Default::default(), (), origin)); + return Ok((Default::default(), None, origin)); }; - // TODO: move into tx extension pipeline but require node upgrade - CheckColdkeySwap::::new().validate( - origin.clone(), - call, - _info, - _len, - _self_implicit, - _inherited_implication, - _source, - )?; - + // Verify ColdkeySwapScheduled map for coldkey + match call.is_sub_type() { + // Whitelist + Some(Call::schedule_swap_coldkey { .. }) => {} + _ => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + } + } match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -163,7 +158,7 @@ where match Pallet::::find_commit_block_via_hash(provided_hash) { Some(commit_block) => { if Pallet::::is_reveal_block_range(*netuid, commit_block) { - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } else { Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) } @@ -208,7 +203,7 @@ where if provided_hashes.len() == batch_reveal_block.len() { if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) { - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } else { Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) } @@ -224,7 +219,7 @@ where } Some(Call::set_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -238,7 +233,7 @@ where if *reveal_round < pallet_drand::LastStoredRound::::get() { return Err(CustomTransactionError::InvalidRevealRound.into()); } - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } @@ -254,7 +249,7 @@ where return Err(CustomTransactionError::RateLimitExceeded.into()); } - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } Some(Call::serve_axon { netuid, @@ -281,32 +276,41 @@ where ), 0u64, ) - .map(|validity| (validity, (), origin.clone())) + .map(|validity| (validity, Some(who.clone()), origin.clone())) } Some(Call::register_network { .. }) => { if !TransactionType::RegisterNetwork.passes_rate_limit::(who) { return Err(CustomTransactionError::RateLimitExceeded.into()); } - Ok((Default::default(), (), origin)) + Ok((Default::default(), Some(who.clone()), origin)) } Some(Call::associate_evm_key { netuid, .. }) => { - let uid = Pallet::::get_uid_for_net_and_hotkey(*netuid, who) - .map_err(|_| CustomTransactionError::UidNotFound)?; - Pallet::::ensure_evm_key_associate_rate_limit(*netuid, uid) - .map_err(|_| CustomTransactionError::EvmKeyAssociateRateLimitExceeded)?; - Ok((Default::default(), (), origin)) - } - Some(Call::add_stake_burn { netuid, .. }) => { - Pallet::::ensure_subnet_owner(origin.clone(), *netuid).map_err(|_| { - TransactionValidityError::Invalid(InvalidTransaction::BadSigner) - })?; - - Ok((Self::validity_ok(ADD_STAKE_BURN_PRIORITY_BOOST), (), origin)) + match Pallet::::get_uid_for_net_and_hotkey(*netuid, who) { + Ok(uid) => { + match Pallet::::ensure_evm_key_associate_rate_limit(*netuid, uid) { + Ok(_) => Ok((Default::default(), Some(who.clone()), origin)), + Err(_) => { + Err(CustomTransactionError::EvmKeyAssociateRateLimitExceeded.into()) + } + } + } + Err(_) => Err(CustomTransactionError::UidNotFound.into()), + } } - _ => Ok((Default::default(), (), origin)), + _ => Ok((Default::default(), Some(who.clone()), origin)), } } - impl_tx_ext_default!(CallOf; weight prepare); + // NOTE: Add later when we put in a pre and post dispatch step. + fn prepare( + self, + _val: Self::Val, + _origin: &::RuntimeOrigin, + _call: &::RuntimeCall, + _info: &DispatchInfoOf<::RuntimeCall>, + _len: usize, + ) -> Result { + Ok(()) + } } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 609e43cf63..10fc0535f0 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -808,14 +808,19 @@ impl Pallet { TransferToggle::::get(netuid) } - pub fn set_coldkey_swap_announcement_delay(duration: BlockNumberFor) { - ColdkeySwapAnnouncementDelay::::set(duration); - Self::deposit_event(Event::ColdkeySwapAnnouncementDelaySet(duration)); - } - - pub fn set_coldkey_swap_reannouncement_delay(duration: BlockNumberFor) { - ColdkeySwapReannouncementDelay::::set(duration); - Self::deposit_event(Event::ColdkeySwapReannouncementDelaySet(duration)); + /// Set the duration for coldkey swap + /// + /// # Arguments + /// + /// * `duration` - The blocks for coldkey swap execution. + /// + /// # Effects + /// + /// * Update the ColdkeySwapScheduleDuration storage. + /// * Emits a ColdkeySwapScheduleDurationSet evnet. + pub fn set_coldkey_swap_schedule_duration(duration: BlockNumberFor) { + ColdkeySwapScheduleDuration::::set(duration); + Self::deposit_event(Event::ColdkeySwapScheduleDurationSet(duration)); } /// Set the duration for dissolve network diff --git a/pallets/subtensor/src/utils/mod.rs b/pallets/subtensor/src/utils/mod.rs index a91875da59..3eb8439959 100644 --- a/pallets/subtensor/src/utils/mod.rs +++ b/pallets/subtensor/src/utils/mod.rs @@ -5,4 +5,3 @@ pub mod misc; pub mod rate_limiting; #[cfg(feature = "try-runtime")] pub mod try_state; -pub mod voting_power; diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index 0649b3b9e3..85f58cfc64 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -16,7 +16,6 @@ pub enum TransactionType { MechanismCountUpdate, MechanismEmission, MaxUidsTrimming, - AddStakeBurn, } impl TransactionType { @@ -45,7 +44,6 @@ impl TransactionType { (Tempo::::get(netuid) as u64).saturating_mul(epochs) } Self::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), - Self::AddStakeBurn => Tempo::::get(netuid) as u64, _ => self.rate_limit::(), } @@ -143,7 +141,6 @@ impl From for u16 { TransactionType::MechanismCountUpdate => 7, TransactionType::MechanismEmission => 8, TransactionType::MaxUidsTrimming => 9, - TransactionType::AddStakeBurn => 10, } } } @@ -161,7 +158,6 @@ impl From for TransactionType { 7 => TransactionType::MechanismCountUpdate, 8 => TransactionType::MechanismEmission, 9 => TransactionType::MaxUidsTrimming, - 10 => TransactionType::AddStakeBurn, _ => TransactionType::Unknown, } } diff --git a/pallets/subtensor/src/utils/voting_power.rs b/pallets/subtensor/src/utils/voting_power.rs deleted file mode 100644 index 55437f8885..0000000000 --- a/pallets/subtensor/src/utils/voting_power.rs +++ /dev/null @@ -1,280 +0,0 @@ -use super::*; -use crate::epoch::run_epoch::EpochTerms; -use alloc::collections::BTreeMap; -use subtensor_runtime_common::{AlphaCurrency, NetUid}; - -/// 14 days in blocks (assuming ~12 second blocks) -/// 14 * 24 * 60 * 60 / 12 = 100800 blocks -pub const VOTING_POWER_DISABLE_GRACE_PERIOD_BLOCKS: u64 = 100800; - -/// Maximum alpha value (1.0 represented as u64 with 18 decimals) -pub const MAX_VOTING_POWER_EMA_ALPHA: u64 = 1_000_000_000_000_000_000; - -impl Pallet { - // ======================== - // === Getters === - // ======================== - - /// Get voting power for a hotkey on a subnet. - /// Returns 0 if not found or tracking disabled. - pub fn get_voting_power(netuid: NetUid, hotkey: &T::AccountId) -> u64 { - VotingPower::::get(netuid, hotkey) - } - - /// Check if voting power tracking is enabled for a subnet. - pub fn get_voting_power_tracking_enabled(netuid: NetUid) -> bool { - VotingPowerTrackingEnabled::::get(netuid) - } - - /// Get the block at which voting power tracking will be disabled. - /// Returns 0 if not scheduled for disabling. - pub fn get_voting_power_disable_at_block(netuid: NetUid) -> u64 { - VotingPowerDisableAtBlock::::get(netuid) - } - - /// Get the EMA alpha value for voting power calculation on a subnet. - pub fn get_voting_power_ema_alpha(netuid: NetUid) -> u64 { - VotingPowerEmaAlpha::::get(netuid) - } - - // ======================== - // === Extrinsic Handlers === - // ======================== - - /// Enable voting power tracking for a subnet. - pub fn do_enable_voting_power_tracking(netuid: NetUid) -> DispatchResult { - // Enable tracking - VotingPowerTrackingEnabled::::insert(netuid, true); - - // Clear any scheduled disable - VotingPowerDisableAtBlock::::remove(netuid); - - // Emit event - Self::deposit_event(Event::VotingPowerTrackingEnabled { netuid }); - - log::info!("VotingPower tracking enabled for netuid {netuid:?}"); - - Ok(()) - } - - /// Schedule disabling of voting power tracking for a subnet. - /// Tracking will continue for 14 days, then automatically disable. - pub fn do_disable_voting_power_tracking(netuid: NetUid) -> DispatchResult { - // Check if tracking is enabled - ensure!( - Self::get_voting_power_tracking_enabled(netuid), - Error::::VotingPowerTrackingNotEnabled - ); - - // Calculate the block at which tracking will be disabled - let current_block = Self::get_current_block_as_u64(); - let disable_at_block = - current_block.saturating_add(VOTING_POWER_DISABLE_GRACE_PERIOD_BLOCKS); - - // Schedule disable - VotingPowerDisableAtBlock::::insert(netuid, disable_at_block); - - // Emit event - Self::deposit_event(Event::VotingPowerTrackingDisableScheduled { - netuid, - disable_at_block, - }); - - log::info!( - "VotingPower tracking scheduled to disable at block {disable_at_block:?} for netuid {netuid:?}" - ); - - Ok(()) - } - - /// Set the EMA alpha value for voting power calculation on a subnet. - pub fn do_set_voting_power_ema_alpha(netuid: NetUid, alpha: u64) -> DispatchResult { - // Validate alpha (must be <= 1.0, represented as 10^18) - ensure!( - alpha <= MAX_VOTING_POWER_EMA_ALPHA, - Error::::InvalidVotingPowerEmaAlpha - ); - - // Set the alpha - VotingPowerEmaAlpha::::insert(netuid, alpha); - - // Emit event - Self::deposit_event(Event::VotingPowerEmaAlphaSet { netuid, alpha }); - - log::info!("VotingPower EMA alpha set to {alpha:?} for netuid {netuid:?}"); - - Ok(()) - } - - // ======================== - // === Epoch Processing === - // ======================== - - /// Update voting power for all validators on a subnet using pre-calculated epoch terms. - pub fn update_voting_power_for_subnet( - netuid: NetUid, - epoch_output: &BTreeMap, - ) { - // Early exit if tracking not enabled - if !Self::get_voting_power_tracking_enabled(netuid) { - return; - } - - // Check if past grace period and should finalize disable - let disable_at = Self::get_voting_power_disable_at_block(netuid); - if disable_at > 0 { - let current_block = Self::get_current_block_as_u64(); - if current_block >= disable_at { - Self::finalize_voting_power_disable(netuid); - return; - } - // Still in grace period - continue updating - } - - // Get the EMA alpha value for this subnet - let alpha = Self::get_voting_power_ema_alpha(netuid); - - // Get minimum stake threshold for validator permit - let min_stake = Self::get_stake_threshold(); - - // Iterate over epoch output using pre-calculated values - for (hotkey, terms) in epoch_output.iter() { - // Only validators (with vpermit) get voting power, not miners - if terms.new_validator_permit { - // Use the subnet-specific stake from epoch calculation - Self::update_voting_power_for_hotkey(netuid, hotkey, terms.stake, alpha, min_stake); - } else { - // Miner without vpermit - remove any existing voting power - VotingPower::::remove(netuid, hotkey); - } - } - - // Remove voting power for any hotkeys that are no longer registered on this subnet - Self::clear_voting_power_for_deregistered_hotkeys(netuid); - - log::trace!("VotingPower updated for validators on netuid {netuid:?}"); - } - - /// Clear voting power for hotkeys that are no longer registered on the subnet. - fn clear_voting_power_for_deregistered_hotkeys(netuid: NetUid) { - // Collect hotkeys to remove (can't mutate while iterating) - let hotkeys_to_remove: Vec = VotingPower::::iter_prefix(netuid) - .filter_map(|(hotkey, _)| { - // If the hotkey is not a network member, it's deregistered - if !IsNetworkMember::::get(&hotkey, netuid) { - Some(hotkey) - } else { - None - } - }) - .collect(); - - // Remove voting power for deregistered hotkeys - for hotkey in hotkeys_to_remove { - VotingPower::::remove(netuid, &hotkey); - log::trace!( - "VotingPower removed for deregistered hotkey {hotkey:?} on netuid {netuid:?}" - ); - } - } - - /// Update voting power EMA for a single hotkey using subnet-specific stake. - fn update_voting_power_for_hotkey( - netuid: NetUid, - hotkey: &T::AccountId, - current_stake: AlphaCurrency, - alpha: u64, - min_stake: u64, - ) { - // Get previous EMA value - let previous_ema = VotingPower::::get(netuid, hotkey); - - // Calculate new EMA value - // new_ema = alpha * current_stake + (1 - alpha) * previous_ema - // All values use 18 decimal precision for alpha (alpha is in range [0, 10^18]) - let new_ema = Self::calculate_voting_power_ema(current_stake.to_u64(), previous_ema, alpha); - - // Only remove if they previously had voting power ABOVE threshold and decayed below. - // This allows new validators to build up voting power from 0 without being removed. - if new_ema < min_stake && previous_ema >= min_stake { - // Was above threshold, now decayed below - remove - VotingPower::::remove(netuid, hotkey); - log::trace!( - "VotingPower removed for hotkey {hotkey:?} on netuid {netuid:?} (decayed below removal threshold: {new_ema:?} < {min_stake:?})" - ); - } else if new_ema > 0 { - // Update voting power (building up or maintaining) - VotingPower::::insert(netuid, hotkey, new_ema); - log::trace!( - "VotingPower updated for hotkey {hotkey:?} on netuid {netuid:?}: {previous_ema:?} -> {new_ema:?}" - ); - } - // If new_ema == 0 do nothing - } - - /// Calculate EMA for voting power. - /// new_ema = alpha * current_stake + (1 - alpha) * previous_ema - /// Alpha is in 18 decimal precision (10^18 = 1.0) - fn calculate_voting_power_ema(current_stake: u64, previous_ema: u64, alpha: u64) -> u64 { - // Use u128 for intermediate calculations to avoid overflow - let alpha_128 = alpha as u128; - let one_minus_alpha = (MAX_VOTING_POWER_EMA_ALPHA as u128).saturating_sub(alpha_128); - let current_128 = current_stake as u128; - let previous_128 = previous_ema as u128; - - // new_ema = (alpha * current_stake + (1 - alpha) * previous_ema) / 10^18 - let numerator = alpha_128 - .saturating_mul(current_128) - .saturating_add(one_minus_alpha.saturating_mul(previous_128)); - - let result = numerator - .checked_div(MAX_VOTING_POWER_EMA_ALPHA as u128) - .unwrap_or(0); - - // Safely convert back to u64, saturating at u64::MAX - result.min(u64::MAX as u128) as u64 - } - - /// Finalize the disabling of voting power tracking. - /// Clears all VotingPower entries for the subnet. - fn finalize_voting_power_disable(netuid: NetUid) { - // Clear all VotingPower entries for this subnet - let _ = VotingPower::::clear_prefix(netuid, u32::MAX, None); - - // Disable tracking - VotingPowerTrackingEnabled::::insert(netuid, false); - - // Clear disable schedule - VotingPowerDisableAtBlock::::remove(netuid); - - // Emit event - Self::deposit_event(Event::VotingPowerTrackingDisabled { netuid }); - - log::info!("VotingPower tracking disabled and entries cleared for netuid {netuid:?}"); - } - - // ======================== - // === Hotkey Swap === - // ======================== - - /// Transfer voting power from old hotkey to new hotkey during swap. - pub fn swap_voting_power_for_hotkey( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid: NetUid, - ) { - // Get voting power from old hotkey - let voting_power = VotingPower::::take(netuid, old_hotkey); - - // Transfer to new hotkey if non-zero - if voting_power > 0 { - // Add to any existing voting power on new hotkey (in case new hotkey already has some) - let existing = VotingPower::::get(netuid, new_hotkey); - VotingPower::::insert(netuid, new_hotkey, voting_power.saturating_add(existing)); - - log::trace!( - "VotingPower transferred from {old_hotkey:?} to {new_hotkey:?} on netuid {netuid:?}: {voting_power:?}" - ); - } - } -} diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 6e6ad7101a..19af1303c1 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::U64F64; +use substrate_fixed::types::U96F32; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; @@ -38,16 +38,19 @@ pub trait SwapHandler { Self: SwapEngine; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; - fn current_alpha_price(netuid: NetUid) -> U64F64; + fn current_alpha_price(netuid: NetUid) -> U96F32; + fn get_protocol_tao(netuid: NetUid) -> TaoCurrency; fn max_price() -> C; fn min_price() -> C; fn adjust_protocol_liquidity( netuid: NetUid, tao_delta: TaoCurrency, alpha_delta: AlphaCurrency, - ) -> (TaoCurrency, AlphaCurrency); + ); + fn is_user_liquidity_enabled(netuid: NetUid) -> bool; + fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult; + fn toggle_user_liquidity(netuid: NetUid, enabled: bool); fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; - fn init_swap(netuid: NetUid, maybe_price: Option); } pub trait DefaultPriceLimit @@ -58,8 +61,7 @@ where fn default_price_limit() -> C; } -/// Externally used swap result (for RPC) -#[freeze_struct("58ff42da64adce1a")] +#[freeze_struct("d3d0b124fe5a97c8")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SwapResult where @@ -69,7 +71,6 @@ where pub amount_paid_in: PaidIn, pub amount_paid_out: PaidOut, pub fee_paid: PaidIn, - pub fee_to_block_author: PaidIn, } impl SwapResult diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index fc3f2acefe..1576283fd5 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, current_price: U64F64, limit_price: U64F64) -> bool; + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool; } #[derive(Clone, Default)] @@ -45,8 +45,8 @@ where self.amount } - fn is_beyond_price_limit(&self, current_price: U64F64, limit_price: U64F64) -> bool { - current_price < limit_price + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { + alpha_sqrt_price < limit_sqrt_price } } @@ -81,7 +81,7 @@ where self.amount } - fn is_beyond_price_limit(&self, current_price: U64F64, limit_price: U64F64) -> bool { - current_price > limit_price + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { + alpha_sqrt_price > limit_sqrt_price } } diff --git a/pallets/swap/Cargo.toml b/pallets/swap/Cargo.toml index 45389874a1..7de8e49c1d 100644 --- a/pallets/swap/Cargo.toml +++ b/pallets/swap/Cargo.toml @@ -12,7 +12,6 @@ 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 @@ -29,8 +28,6 @@ subtensor-swap-interface.workspace = true [dev-dependencies] sp-tracing.workspace = true -rand = { version = "0.8", default-features = false } -rayon = "1.10" [lints] workspace = true @@ -45,9 +42,7 @@ std = [ "frame-system/std", "log/std", "pallet-subtensor-swap-runtime-api/std", - "rand/std", "safe-math/std", - "safe-bigmath/std", "scale-info/std", "serde/std", "sp-arithmetic/std", diff --git a/pallets/swap/rpc/src/lib.rs b/pallets/swap/rpc/src/lib.rs index a58334dea8..b83a083ad5 100644 --- a/pallets/swap/rpc/src/lib.rs +++ b/pallets/swap/rpc/src/lib.rs @@ -13,14 +13,12 @@ use sp_blockchain::HeaderBackend; use sp_runtime::traits::Block as BlockT; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -pub use pallet_subtensor_swap_runtime_api::{SubnetPrice, SwapRuntimeApi}; +pub use pallet_subtensor_swap_runtime_api::SwapRuntimeApi; #[rpc(client, server)] pub trait SwapRpcApi { #[method(name = "swap_currentAlphaPrice")] fn current_alpha_price(&self, netuid: NetUid, at: Option) -> RpcResult; - #[method(name = "swap_currentAlphaPriceAll")] - fn current_alpha_price_all(&self, at: Option) -> RpcResult>; #[method(name = "swap_simSwapTaoForAlpha")] fn sim_swap_tao_for_alpha( &self, @@ -94,18 +92,6 @@ where }) } - fn current_alpha_price_all( - &self, - at: Option<::Hash>, - ) -> RpcResult> { - let api = self.client.runtime_api(); - let at = at.unwrap_or_else(|| self.client.info().best_hash); - - api.current_alpha_price_all(at).map_err(|e| { - Error::RuntimeError(format!("Unable to get all current alpha prices: {e:?}")).into() - }) - } - fn sim_swap_tao_for_alpha( &self, netuid: NetUid, diff --git a/pallets/swap/runtime-api/Cargo.toml b/pallets/swap/runtime-api/Cargo.toml index 50f92d19e2..042875fdd0 100644 --- a/pallets/swap/runtime-api/Cargo.toml +++ b/pallets/swap/runtime-api/Cargo.toml @@ -7,7 +7,6 @@ edition.workspace = true [dependencies] codec = { workspace = true, features = ["derive"] } frame-support.workspace = true -serde.workspace = true scale-info.workspace = true sp-api.workspace = true sp-std.workspace = true @@ -21,7 +20,6 @@ std = [ "codec/std", "frame-support/std", "scale-info/std", - "serde/std", "sp-api/std", "sp-std/std", "subtensor-runtime-common/std", diff --git a/pallets/swap/runtime-api/src/lib.rs b/pallets/swap/runtime-api/src/lib.rs index ce61a93dc6..01d2ccf23e 100644 --- a/pallets/swap/runtime-api/src/lib.rs +++ b/pallets/swap/runtime-api/src/lib.rs @@ -1,33 +1,21 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::pallet_prelude::*; -use scale_info::prelude::vec::Vec; -use serde::{Deserialize, Serialize}; use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -#[freeze_struct("ee2ba1ec4ee58ae6")] +#[freeze_struct("3a4fd213b5de5eb6")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SimSwapResult { pub tao_amount: TaoCurrency, pub alpha_amount: AlphaCurrency, pub tao_fee: TaoCurrency, pub alpha_fee: AlphaCurrency, - pub tao_slippage: TaoCurrency, - pub alpha_slippage: AlphaCurrency, -} - -#[freeze_struct("d7bbb761fc2b2eac")] -#[derive(Decode, Deserialize, Encode, PartialEq, Eq, Clone, Debug, Serialize, TypeInfo)] -pub struct SubnetPrice { - pub netuid: NetUid, - pub price: u64, } sp_api::decl_runtime_apis! { pub trait SwapRuntimeApi { fn current_alpha_price(netuid: NetUid) -> u64; - fn current_alpha_price_all() -> Vec; fn sim_swap_tao_for_alpha(netuid: NetUid, tao: TaoCurrency) -> SimSwapResult; fn sim_swap_alpha_for_tao(netuid: NetUid, alpha: AlphaCurrency) -> SimSwapResult; } diff --git a/pallets/swap/src/benchmarking.rs b/pallets/swap/src/benchmarking.rs index c4a6afa87e..a17ac59141 100644 --- a/pallets/swap/src/benchmarking.rs +++ b/pallets/swap/src/benchmarking.rs @@ -2,16 +2,22 @@ #![allow(clippy::unwrap_used)] #![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::{Call, Config, Pallet}; - -#[allow(dead_code)] -fn init_swap(netuid: NetUid) { - let _ = Pallet::::maybe_initialize_palswap(netuid, None); -} +use crate::{ + pallet::{ + AlphaSqrtPrice, Call, Config, CurrentLiquidity, CurrentTick, Pallet, Positions, + SwapV3Initialized, + }, + position::{Position, PositionId}, + tick::TickIndex, +}; #[benchmarks(where T: Config)] mod benchmarks { @@ -26,5 +32,117 @@ mod benchmarks { set_fee_rate(RawOrigin::Root, netuid, rate); } + // TODO: Revise when user liquidity is available + // #[benchmark] + // 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()); + // } + + // 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, + // ); + // } + + #[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()); + } + + let caller: T::AccountId = whitelisted_caller(); + let hotkey: T::AccountId = account("hotkey", 0, 0); + let id = PositionId::from(1u128); + + Positions::::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), + _phantom: PhantomData, + }, + ); + + #[extrinsic_call] + remove_liquidity(RawOrigin::Signed(caller), hotkey, netuid.into(), id.into()); + } + + #[benchmark] + 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()); + } + + let caller: T::AccountId = whitelisted_caller(); + let hotkey: T::AccountId = account("hotkey", 0, 0); + let id = PositionId::from(1u128); + + Positions::::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), + _phantom: PhantomData, + }, + ); + + #[extrinsic_call] + modify_position( + RawOrigin::Signed(caller), + hotkey, + netuid.into(), + id.into(), + -5000, + ); + } + + // #[benchmark] + // fn toggle_user_liquidity() { + // let netuid = NetUid::from(101); + + // assert!(!EnabledUserLiquidity::::get(netuid)); + + // #[extrinsic_call] + // toggle_user_liquidity(RawOrigin::Root, netuid.into(), true); + + // assert!(EnabledUserLiquidity::::get(netuid)); + // } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index b51c3351dc..6257df852b 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -1,6 +1,10 @@ #![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::*; @@ -10,3 +14,5 @@ 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 3c5e424442..aacdf90835 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -14,18 +14,14 @@ use sp_runtime::{ BuildStorage, Vec, traits::{BlakeTwo256, IdentityLookup}, }; -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}; + construct_runtime!( pub enum Test { System: frame_system = 0, @@ -86,38 +82,16 @@ impl system::Config for Test { parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const MaxFeeRate: u16 = 10000; // 15.26% + pub const MaxPositions: u32 = 100; pub const MinimumLiquidity: u64 = 1_000; 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, @@ -133,22 +107,8 @@ 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(), @@ -163,7 +123,22 @@ impl CurrencyReserve for AlphaReserve { pub type GetAlphaForTao = subtensor_swap_interface::GetAlphaForTao; pub type GetTaoForAlpha = subtensor_swap_interface::GetTaoForAlpha; -#[allow(dead_code)] +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) + } +} + pub(crate) trait TestExt { fn approx_expected_swap_output( sqrt_current_price: f64, @@ -302,6 +277,7 @@ impl crate::pallet::Config for Test { type BalanceOps = MockBalanceOps; type ProtocolId = SwapProtocolId; type MaxFeeRate = MaxFeeRate; + type MaxPositions = MaxPositions; type MinimumLiquidity = MinimumLiquidity; type MinimumReserve = MinimumReserves; type WeightInfo = (); @@ -316,6 +292,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext = sp_io::TestExternalities::new(storage); ext.execute_with(|| { System::set_block_number(1); + + for netuid in 0u16..=100 { + // enable V3 for this range of netuids + EnabledUserLiquidity::::set(NetUid::from(netuid), true); + } + EnabledUserLiquidity::::set(NetUid::from(WRAPPING_FEES_NETUID), true); }); ext } diff --git a/pallets/swap/src/pallet/balancer.rs b/pallets/swap/src/pallet/balancer.rs deleted file mode 100644 index 1e1386bd41..0000000000 --- a/pallets/swap/src/pallet/balancer.rs +++ /dev/null @@ -1,1095 +0,0 @@ -// Balancer swap -// -// Unlike uniswap v2 or v3, it allows adding liquidity disproportionally to price. This is -// achieved by introducing the weights w1 and w2 so that w1 + w2 = 1. In these formulas x -// means base currency (alpha) and y means quote currency (tao). The w1 weight in the code -// below is referred as weight_base, and w2 as weight_quote. Because of the w1 + w2 = 1 -// constraint, only weight_quote is stored, and weight_base is always calculated. -// -// The formulas used for pool operation are following: -// -// Price: p = (w1*y) / (w2*x) -// -// Reserve deltas / (or -1 * payouts) in swaps are computed by: -// -// if ∆x is given (sell) ∆y = y * ((x / (x+∆x))^(w1/w2) - 1) -// if ∆y is given (buy) ∆x = x * ((y / (y+∆y))^(w2/w1) - 1) -// -// When swaps are executing the orders with slippage control, we need to know what amount -// we can swap before the price reaches the limit value of p': -// -// If p' < p (sell): ∆x = x * ((p / p')^w2 - 1) -// If p' < p (buy): ∆y = y * ((p' / p)^w1 - 1) -// -// In order to initialize weights with existing reserve values and price: -// -// w1 = px / (px + y) -// w2 = y / (px + y) -// -// Weights are adjusted when some amounts are added to the reserves. This prevents price -// from changing. -// -// new_w1 = p * (x + ∆x) / (p * (x + ∆x) + y + ∆y) -// new_w2 = (y + ∆y) / (p * (x + ∆x) + y + ∆y) -// -// Weights are limited to stay within [0.1, 0.9] range to avoid precision issues in exponentiation. -// Practically, these limitations will not be achieved, but if they are, the swap will not allow injection -// that will push the weights out of this interval because we prefer chain and swap stability over success -// of a single injection. Currently, we only allow the protocol to inject disproportionally to price, and -// the amount of disproportion will not cause weigths to get far from 0.5. -// - -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 sp_std::ops::Neg; -use substrate_fixed::types::U64F64; -use subtensor_macros::freeze_struct; - -/// Balancer implements all high complexity math for swap operations such as: -/// - Swapping x for y, which includes limit orders -/// - Adding and removing liquidity (including unbalanced) -/// -/// Notation used in this file: -/// - x: Base reserve (alplha reserve) -/// - y: Quote reserve (tao reserve) -/// - ∆x: Alpha paid in/out -/// - ∆y: Tao paid in/out -/// - w1: Base weight (a.k.a weight_base) -/// - w2: Quote weight (a.k.a weight_quote) -#[freeze_struct("33a4fb0774da77c7")] -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct Balancer { - quote: Perquintill, -} - -/// Accuracy matches to 18 decimal digits used to represent weights -pub const ACCURACY: u64 = 1_000_000_000_000_000_000_u64; -/// Lower imit of weights is 0.01 -pub const MIN_WEIGHT: Perquintill = Perquintill::from_parts(ACCURACY / 100); -/// 1.0 in Perquintill -pub const ONE: Perquintill = Perquintill::from_parts(ACCURACY); - -#[derive(Debug)] -pub enum BalancerError { - /// The provided weight value is out of range - InvalidValue, -} - -impl Default for Balancer { - /// The default value of weights is 0.5 for pool initialization - fn default() -> Self { - Self { - quote: Perquintill::from_rational(1u128, 2u128), - } - } -} - -impl Balancer { - /// Creates a new instance of balancer with a given quote weight - pub fn new(quote: Perquintill) -> Result { - if Self::check_constraints(quote) { - Ok(Balancer { quote }) - } else { - Err(BalancerError::InvalidValue) - } - } - - /// Constraints limit balancer weights within certain range of values: - /// - Both weights are above minimum - /// - Sum of weights is equal to 1.0 - fn check_constraints(quote: Perquintill) -> bool { - let base = ONE.saturating_sub(quote); - (base >= MIN_WEIGHT) && (quote >= MIN_WEIGHT) - } - - /// We store quote weight as Perquintill - pub fn get_quote_weight(&self) -> Perquintill { - self.quote - } - - /// Base weight is calculated as 1.0 - quote_weight - pub fn get_base_weight(&self) -> Perquintill { - ONE.saturating_sub(self.quote) - } - - /// Sets quote currency weight in the balancer. - /// Because sum of weights is always 1.0, there is no need to - /// store base currency weight - pub fn set_quote_weight(&mut self, new_value: Perquintill) -> Result<(), BalancerError> { - if Self::check_constraints(new_value) { - self.quote = new_value; - Ok(()) - } else { - Err(BalancerError::InvalidValue) - } - } - - /// If base_quote is true, calculate (x / (x + ∆x))^(weight_base / weight_quote), - /// otherwise, calculate (x / (x + ∆x))^(weight_quote / weight_base) - /// - /// Here we use SafeInt from bigmath crate for high-precision exponentiation, - /// which exposes the function pow_ratio_scaled. - /// - /// Note: ∆x may be negative - fn exp_scaled(&self, x: u64, dx: i128, base_quote: bool) -> U64F64 { - let x_plus_dx = if dx >= 0 { - x.saturating_add(dx as u64) - } else { - x.saturating_sub(dx.neg() as u64) - }; - - if x_plus_dx == 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 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); - log::debug!("x = {:?}", x); - log::debug!("dx = {:?}", dx); - log::debug!("x_safe = {:?}", x_safe); - log::debug!("denominator = {:?}", denominator); - log::debug!("w1_safe = {:?}", w1_safe); - log::debug!("w2_safe = {:?}", w2_safe); - log::debug!("precision = {:?}", precision); - log::debug!("perquintill_scale = {:?}", perquintill_scale); - - let maybe_result_safe_int = if base_quote { - 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 - && let Some(result_u64) = result_safe_int.to_u64() - { - return U64F64::saturating_from_num(result_u64) - .safe_div(U64F64::saturating_from_num(ACCURACY)); - } - U64F64::saturating_from_num(0) - } - - /// Calculates exponent of (x / (x + ∆x)) ^ (w_base/w_quote) - /// This method is used in sell swaps - /// (∆x is given by user, ∆y is paid out by the pool) - pub fn exp_base_quote(&self, x: u64, dx: u64) -> U64F64 { - self.exp_scaled(x, dx as i128, true) - } - - /// Calculates exponent of (y / (y + ∆y)) ^ (w_quote/w_base) - /// This method is used in buy swaps - /// (∆y is given by user, ∆x is paid out by the pool) - pub fn exp_quote_base(&self, y: u64, dy: u64) -> U64F64 { - self.exp_scaled(y, dy as i128, false) - } - - /// Calculates price as (w1/w2) * (y/x), where - /// - w1 is base weight - /// - w2 is quote weight - /// - x is base reserve - /// - y is quote reserve - 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).saturating_mul(U256::from(parts)); - let den = U256::from(acc); - - // Add 0.5 before integer division to achieve rounding to the nearest - // integer - let zero = U256::from(0); - let res = num - .saturating_add(den.checked_div(U256::from(2u8)).unwrap_or(zero)) - .checked_div(den) - .unwrap_or(zero); - res.min(U256::from(u128::MAX)) - .try_into() - .unwrap_or_default() - } - - /// When liquidity is added to balancer swap, it may be added with arbitrary proportion, - /// not necessarily in the proportion of price, like with uniswap v2 or v3. In order to - /// stay within balancer pool invariant, the weights need to be updated. Invariant: - /// - /// L = x ^ weight_base * y ^ weight_quote - /// - /// Note that weights must remain within the proper range (both be above MIN_WEIGHT), - /// so only reasonably small disproportions of updates are appropriate. - pub fn update_weights_for_added_liquidity( - &mut self, - tao_reserve: u64, - alpha_reserve: u64, - tao_delta: u64, - alpha_delta: u64, - ) -> Result<(), BalancerError> { - // 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 - /// This method is needed for limit orders. - /// - /// Formula is: - /// ∆y = y * ((price_new / price)^weight_base - 1) - /// price_new >= price - 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(w1_fixed), - &SafeInt::from(ACCURACY), - 1024, - &SafeInt::from(scale), - ); - - if let Some(exp_result_safe_int) = maybe_exp_result { - let reserve_fixed = U64F64::saturating_from_num(reserve); - let one = U64F64::saturating_from_num(1); - let scale_fixed = U64F64::saturating_from_num(scale); - let exp_result_fixed = if let Some(exp_result_u64) = exp_result_safe_int.to_u64() { - U64F64::saturating_from_num(exp_result_u64) - } else if u64::MAX < exp_result_safe_int { - U64F64::saturating_from_num(u64::MAX) - } else { - U64F64::saturating_from_num(0) - }; - reserve_fixed - .saturating_mul(exp_result_fixed.safe_div(scale_fixed).saturating_sub(one)) - .saturating_to_num::() - } else { - 0u64 - } - } - - /// Calculates base delta needed to reach the price down when selling - /// This method is needed for limit orders. - /// - /// Formula is: - /// ∆x = x * ((price / price_new)^weight_quote - 1) - /// price_new <= price - 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), - 1024, - &SafeInt::from(scale), - ); - - if let Some(exp_result_safe_int) = maybe_exp_result { - let one = U64F64::saturating_from_num(1); - let scale_fixed = U64F64::saturating_from_num(scale); - let reserve_fixed = U64F64::saturating_from_num(reserve); - let exp_result_fixed = if let Some(exp_result_u64) = exp_result_safe_int.to_u64() { - U64F64::saturating_from_num(exp_result_u64) - } else if u64::MAX < exp_result_safe_int { - U64F64::saturating_from_num(u64::MAX) - } else { - U64F64::saturating_from_num(0) - }; - reserve_fixed - .saturating_mul(exp_result_fixed.safe_div(scale_fixed).saturating_sub(one)) - .saturating_to_num::() - } else { - 0u64 - } - } - - /// Calculates amount of Alpha that needs to be sold to get a given amount of TAO - pub fn get_base_needed_for_quote( - &self, - tao_reserve: u64, - alpha_reserve: u64, - delta_tao: u64, - ) -> u64 { - let e = self.exp_scaled(tao_reserve, (delta_tao as i128).neg(), false); - let one = U64F64::from_num(1); - let alpha_reserve_fixed = U64F64::from_num(alpha_reserve); - // e > 1 in this case - alpha_reserve_fixed - .saturating_mul(e.saturating_sub(one)) - .saturating_to_num::() - } -} - -// cargo test --package pallet-subtensor-swap --lib -- pallet::balancer::tests --nocapture -#[cfg(test)] -#[allow(clippy::expect_used, clippy::unwrap_used)] -#[cfg(feature = "std")] -mod tests { - use crate::pallet::Balancer; - use crate::pallet::balancer::*; - use approx::assert_abs_diff_eq; - use sp_arithmetic::Perquintill; - - // Helper: convert Perquintill to f64 for comparison - fn perquintill_to_f64(p: Perquintill) -> f64 { - let parts = p.deconstruct() as f64; - parts / ACCURACY as f64 - } - - // Helper: convert U64F64 to f64 for comparison - fn f(v: U64F64) -> f64 { - v.to_num::() - } - - #[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 bal = Balancer::new(w_quote).unwrap(); - let e1 = bal.exp_base_quote(x, dx); - let e2 = bal.exp_quote_base(x, dx); - let one = U64F64::from_num(1); - let y_fixed = U64F64::from_num(y); - let dy1 = y_fixed * (one - e1); - let dy2 = y_fixed * (one - e2); - - let w1 = perquintill_to_f64(bal.get_base_weight()); - let w2 = perquintill_to_f64(bal.get_quote_weight()); - let e1_expected = (x as f64 / (x as f64 + dx as f64)).powf(w1 / w2); - let dy1_expected = y as f64 * (1. - e1_expected); - let e2_expected = (x as f64 / (x as f64 + dx as f64)).powf(w2 / w1); - let dy2_expected = y as f64 * (1. - e2_expected); - - // Start tolerance with 0.001 rao - let mut eps1 = 0.001; - let mut eps2 = 0.001; - - // If swapping more than 100k tao/alpha, relax tolerance to 1.0 rao - if dy1_expected > 100_000_000_000_000_f64 { - eps1 = 1.0; - } - if dy2_expected > 100_000_000_000_000_f64 { - eps2 = 1.0; - } - assert_abs_diff_eq!(f(dy1), dy1_expected, epsilon = eps1); - assert_abs_diff_eq!(f(dy2), dy2_expected, epsilon = eps2); - }) - }); - } - - /// 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 bal1 = Balancer::new(w_quote1).unwrap(); - let bal2 = Balancer::new(w_quote2).unwrap(); - - let exp1 = bal1.exp_base_quote(x1, dx1); - let exp2 = bal2.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_usize) { - let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); - let bal = Balancer::new(w_quote).unwrap(); - let e = bal.exp_base_quote(x, dx); - - let one = U64F64::from_num(1); - let dy = U64F64::from_num(y) * (one - e); - - let progress = (num as f64 - start as f64) / (stop as f64 - start as f64); - if progress - last_progress >= 0.0001 { - // Replace with println for real-time progress - log::debug!("progress = {:?}%", progress * 100.); - log::debug!("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 bal = Balancer::new(w_quote).unwrap(); - let e = bal.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(bal.get_base_weight()); - let w2 = perquintill_to_f64(bal.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 eps = (dy_expected / 1_000_000.).clamp(1.0, 1000.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; - // Replace with println for real-time progress - log::debug!("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 bal = Balancer::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 = bal.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 bal = Balancer::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 = bal.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 bal = Balancer::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 = bal.calculate_quote_delta_in(current_price, target_price, tao_reserve); - let dy_expected = 0u64; - - assert_eq!(dy, dy_expected); - } - - #[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 bal = Balancer::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 = bal.calculate_base_delta_in(current_price, target_price, alpha_reserve); - let dx_expected = 0u64; - - assert_eq!(dx, dx_expected); - } - - #[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 bal = Balancer::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 = bal.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 = bal.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. - ); - } - - #[test] - fn test_mul_round_zero_and_one() { - let v = 1_000_000u128; - - // p = 0 -> always 0 - assert_eq!(Balancer::mul_perquintill_round(Perquintill::zero(), v), 0); - - // p = 1 -> identity - assert_eq!(Balancer::mul_perquintill_round(Perquintill::one(), v), v); - } - - #[test] - fn test_mul_round_half_behaviour() { - // p = 1/2 - let p = Perquintill::from_rational(1u128, 2u128); - - // Check rounding around .5 boundaries - // value * 1/2, rounded to nearest - assert_eq!(Balancer::mul_perquintill_round(p, 0), 0); // 0.0 -> 0 - assert_eq!(Balancer::mul_perquintill_round(p, 1), 1); // 0.5 -> 1 (round up) - assert_eq!(Balancer::mul_perquintill_round(p, 2), 1); // 1.0 -> 1 - assert_eq!(Balancer::mul_perquintill_round(p, 3), 2); // 1.5 -> 2 - assert_eq!(Balancer::mul_perquintill_round(p, 4), 2); // 2.0 -> 2 - assert_eq!(Balancer::mul_perquintill_round(p, 5), 3); // 2.5 -> 3 - assert_eq!(Balancer::mul_perquintill_round(p, 1023), 512); // 511.5 -> 512 - assert_eq!(Balancer::mul_perquintill_round(p, 1025), 513); // 512.5 -> 513 - } - - #[test] - fn test_mul_round_third_behaviour() { - // p = 1/3 - let p = Perquintill::from_rational(1u128, 3u128); - - // value * 1/3, rounded to nearest - assert_eq!(Balancer::mul_perquintill_round(p, 3), 1); // 1.0 -> 1 - assert_eq!(Balancer::mul_perquintill_round(p, 4), 1); // 1.333... -> 1 - assert_eq!(Balancer::mul_perquintill_round(p, 5), 2); // 1.666... -> 2 - assert_eq!(Balancer::mul_perquintill_round(p, 6), 2); // 2.0 -> 2 - } - - #[test] - fn test_mul_round_large_values_simple_rational() { - // p = 7/10 (exact in perquintill: 0.7) - let p = Perquintill::from_rational(7u128, 10u128); - let v: u128 = 1_000_000_000_000_000_000; - - let res = Balancer::mul_perquintill_round(p, v); - - // Expected = round(0.7 * v) with pure integer math: - // round(v * 7 / 10) = (v*7 + 10/2) / 10 - let expected = (v.saturating_mul(7) + 10 / 2) / 10; - - assert_eq!(res, expected); - } - - #[test] - fn test_mul_round_max_value_with_one() { - let v = u128::MAX; - let p = ONE; - - // For p = 1, result must be exactly value, and must not overflow - let res = Balancer::mul_perquintill_round(p, v); - assert_eq!(res, v); - } - - #[test] - fn test_price_with_equal_weights_is_y_over_x() { - // quote = 0.5, base = 0.5 -> w1 / w2 = 1, so price = y/x - let quote = Perquintill::from_rational(1u128, 2u128); - let bal = Balancer::new(quote).unwrap(); - - let x = 2u64; - let y = 5u64; - - let price = bal.calculate_price(x, y); - let price_f = f(price); - - let expected_f = (y as f64) / (x as f64); - assert_abs_diff_eq!(price_f, expected_f, epsilon = 1e-12); - } - - #[test] - fn test_price_scales_with_weight_ratio_two_to_one() { - // Assume base = 1 - quote. - // quote = 1/3 -> base = 2/3, so w1 / w2 = 2. - // Then price = 2 * (y/x). - let quote = Perquintill::from_rational(1u128, 3u128); - let bal = Balancer::new(quote).unwrap(); - - let x = 4u64; - let y = 10u64; - - let price_f = f(bal.calculate_price(x, y)); - let expected_f = 2.0 * (y as f64 / x as f64); - - assert_abs_diff_eq!(price_f, expected_f, epsilon = 1e-10); - } - - #[test] - fn test_price_is_zero_when_y_is_zero() { - // If y = 0, y/x = 0 so price must be 0 regardless of weights (for x > 0). - let quote = Perquintill::from_rational(3u128, 10u128); // 0.3 - let bal = Balancer::new(quote).unwrap(); - - let x = 10u64; - let y = 0u64; - - let price_f = f(bal.calculate_price(x, y)); - assert_abs_diff_eq!(price_f, 0.0, epsilon = 0.0); - } - - #[test] - fn test_price_invariant_when_scaling_x_and_y_with_equal_weights() { - // For equal weights, price(x, y) == price(kx, ky). - let quote = Perquintill::from_rational(1u128, 2u128); // 0.5 - let bal = Balancer::new(quote).unwrap(); - - let x1 = 3u64; - let y1 = 7u64; - let k = 10u64; - let x2 = x1 * k; - let y2 = y1 * k; - - let p1 = f(bal.calculate_price(x1, y1)); - let p2 = f(bal.calculate_price(x2, y2)); - - assert_abs_diff_eq!(p1, p2, epsilon = 1e-12); - } - - #[test] - fn test_price_matches_formula_for_general_quote() { - // General check: price = (w1 / w2) * (y/x), - // where w1 = base_weight, w2 = quote_weight. - // Here we assume get_base_weight = 1 - quote. - let quote = Perquintill::from_rational(2u128, 5u128); // 0.4 - let bal = Balancer::new(quote).unwrap(); - - let x = 9u64; - let y = 25u64; - - let price_f = f(bal.calculate_price(x, y)); - - let base = Perquintill::one() - quote; - let w1 = base.deconstruct() as f64; - let w2 = quote.deconstruct() as f64; - - let expected_f = (w1 / w2) * (y as f64 / x as f64); - assert_abs_diff_eq!(price_f, expected_f, epsilon = 1e-9); - } - - #[test] - fn test_price_high_values_non_equal_weights() { - // Non-equal weights, high x and y (up to 21e15) - let quote = Perquintill::from_rational(3u128, 10u128); // 0.3 - let bal = Balancer::new(quote).unwrap(); - - let x: u64 = 21_000_000_000_000_000; - let y: u64 = 15_000_000_000_000_000; - - let price = bal.calculate_price(x, y); - let price_f = f(price); - - // Expected: (w1 / w2) * (y / x), using Balancer's actual weights - let w1 = bal.get_base_weight().deconstruct() as f64; - let w2 = bal.get_quote_weight().deconstruct() as f64; - let expected_f = (w1 / w2) * (y as f64 / x as f64); - - assert_abs_diff_eq!(price_f, expected_f, epsilon = 1e-9); - } - - // cargo test --package pallet-subtensor-swap --lib -- pallet::balancer::tests::test_exp_scaled --exact --nocapture - #[test] - fn test_exp_scaled() { - [ - // base_weight_numerator, base_weight_denominator, reserve, d_reserve, base_quote - (5_u64, 10_u64, 100000_u64, 100_u64, true, 0.999000999000999), - (1_u64, 4_u64, 500000_u64, 5000_u64, true, 0.970590147927644), - (3_u64, 4_u64, 200000_u64, 2000_u64, false, 0.970590147927644), - ( - 9_u64, - 10_u64, - 13513642_u64, - 1673_u64, - false, - 0.998886481979889, - ), - ( - 773_u64, - 1000_u64, - 7_000_000_000_u64, - 10_000_u64, - true, - 0.999999580484586, - ), - ] - .into_iter() - .map(|v| { - ( - Perquintill::from_rational(v.0, v.1), - v.2, - v.3, - v.4, - U64F64::from_num(v.5), - ) - }) - .for_each(|(quote_weight, reserve, d_reserve, base_quote, expected)| { - let balancer = Balancer::new(quote_weight).unwrap(); - let result = balancer.exp_scaled(reserve, d_reserve as i128, base_quote); - assert_abs_diff_eq!( - result.to_num::(), - expected.to_num::(), - epsilon = 0.000000001 - ); - }); - } - - // cargo test --package pallet-subtensor-swap --lib -- pallet::balancer::tests::test_base_needed_for_quote --exact --nocapture - #[test] - fn test_base_needed_for_quote() { - let num = 250_000_000_000_u128; // w1 = 0.75 - let w_quote = Perquintill::from_rational(num, 1_000_000_000_000_u128); - let bal = Balancer::new(w_quote).unwrap(); - - let tao_reserve: u64 = 1_000_000_000; - let alpha_reserve: u64 = 1_000_000_000; - let tao_delta: u64 = 1_123_432; // typical fee range - - let dx = bal.get_base_needed_for_quote(tao_reserve, alpha_reserve, tao_delta); - - // ∆x = x•[(y/(y+∆y))^(w2/w1) - 1] - let dx_expected = tao_reserve as f64 - * ((tao_reserve as f64 / ((tao_reserve - tao_delta) as f64)).powf(0.25 / 0.75) - 1.0); - - assert_eq!(dx, dx_expected as u64,); - } -} diff --git a/pallets/swap/src/pallet/hooks.rs b/pallets/swap/src/pallet/hooks.rs deleted file mode 100644 index 90989d5f52..0000000000 --- a/pallets/swap/src/pallet/hooks.rs +++ /dev/null @@ -1,30 +0,0 @@ -use frame_support::pallet_macros::pallet_section; - -#[pallet_section] -mod hooks { - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(block_number: BlockNumberFor) -> Weight { - Weight::from_parts(0, 0) - } - - fn on_finalize(_block_number: BlockNumberFor) {} - - fn on_runtime_upgrade() -> Weight { - // --- Migrate storage - let mut weight = Weight::from_parts(0, 0); - - weight = weight - // Cleanup uniswap v3 and migrate to balancer - .saturating_add( - migrations::migrate_swapv3_to_balancer::migrate_swapv3_to_balancer::(), - ); - weight - } - - #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { - Ok(()) - } - } -} diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 54efabb19a..6ec02879bf 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1,136 +1,215 @@ +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, - Perquintill, -}; -use sp_runtime::{DispatchResult, traits::AccountIdConversion}; -use substrate_fixed::types::U64F64; +use sp_arithmetic::helpers_128bit; +use sp_runtime::{DispatchResult, Vec, traits::AccountIdConversion}; +use substrate_fixed::types::{I64F64, U64F64, U96F32}; 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}; -use crate::{pallet::Balancer, pallet::balancer::BalancerError}; +use super::swap_step::{BasicSwapStep, SwapStep, SwapStepAction}; +use crate::{ + SqrtPrice, + position::{Position, PositionId}, + tick::{ActiveTickIndexManager, Tick, TickIndex}, +}; + +const MAX_SWAP_ITERATIONS: u16 = 1000; + +#[derive(Debug, PartialEq)] +pub struct UpdateLiquidityResult { + pub tao: TaoCurrency, + pub alpha: AlphaCurrency, + pub fee_tao: TaoCurrency, + pub fee_alpha: AlphaCurrency, + pub removed: bool, + pub tick_low: TickIndex, + pub tick_high: TickIndex, +} + +#[derive(Debug, PartialEq)] +pub struct RemoveLiquidityResult { + pub tao: TaoCurrency, + 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) -> U64F64 { + pub fn current_price(netuid: NetUid) -> U96F32 { match T::SubnetInfo::mechanism(netuid.into()) { 1 => { - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - if !alpha_reserve.is_zero() { - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let balancer = SwapBalancer::::get(netuid); - balancer.calculate_price(alpha_reserve.into(), tao_reserve.into()) + if SwapV3Initialized::::get(netuid) { + let sqrt_price = AlphaSqrtPrice::::get(netuid); + U96F32::saturating_from_num(sqrt_price.saturating_mul(sqrt_price)) } else { - U64F64::saturating_from_num(0) + 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) + } } } - _ => U64F64::saturating_from_num(1), + _ => U96F32::saturating_from_num(1), } } - // initializes pal-swap (balancer) for a subnet if needed - pub fn maybe_initialize_palswap( - netuid: NetUid, - maybe_price: Option, - ) -> Result<(), Error> { - if PalSwapInitialized::::get(netuid) { + // initializes V3 swap for a subnet if needed + pub fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { + if SwapV3Initialized::::get(netuid) { return Ok(()); } - // Query reserves + // 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()); - // Create balancer based on price - let balancer = Balancer::new(if let Some(price) = maybe_price { - // Price is given, calculate weights: - // w_quote = y / (px + y) - let px_high = (price.saturating_to_num::() as u128) - .saturating_mul(u64::from(alpha_reserve) as u128); - let px_low = U64F64::saturating_from_num(alpha_reserve) - .saturating_mul(price.frac()) - .saturating_to_num::(); - let px_plus_y = px_high - .saturating_add(px_low) - .saturating_add(u64::from(tao_reserve) as u128); - - // If price is given and both reserves are zero, the swap doesn't initialize - if px_plus_y == 0u128 { - return Err(Error::::ReservesOutOfBalance); - } - Perquintill::from_rational(u64::from(tao_reserve) as u128, px_plus_y) - } else { - // No price = insert 0.5 into SwapBalancer - Perquintill::from_rational(1_u64, 2_u64) - }) - .map_err(|err| match err { - BalancerError::InvalidValue => Error::::ReservesOutOfBalance, - })?; - SwapBalancer::::insert(netuid, balancer.clone()); + // 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); - PalSwapInitialized::::insert(netuid, true); + let current_sqrt_price = price.checked_sqrt(epsilon).unwrap_or(U64F64::from_num(0)); + AlphaSqrtPrice::::set(netuid, current_sqrt_price); + + // Set current tick + let current_tick = TickIndex::from_sqrt_price_bounded(current_sqrt_price); + CurrentTick::::set(netuid, current_tick); + + // Set initial (protocol owned) liquidity and positions + // Protocol liquidity makes one position from TickIndex::MIN to TickIndex::MAX + // 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 (position, _, _) = Self::add_liquidity_not_insert( + netuid, + &protocol_account_id, + TickIndex::MIN, + TickIndex::MAX, + liquidity, + )?; + + 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 - /// Returns actually added Tao and Alpha, which includes fees pub(super) fn adjust_protocol_liquidity( netuid: NetUid, tao_delta: TaoCurrency, alpha_delta: AlphaCurrency, - ) -> (TaoCurrency, AlphaCurrency) { - // Collect fees - let tao_fees = FeesTao::::get(netuid); - let alpha_fees = FeesAlpha::::get(netuid); - FeesTao::::insert(netuid, TaoCurrency::ZERO); - FeesAlpha::::insert(netuid, AlphaCurrency::ZERO); - let actual_tao_delta = tao_delta.saturating_add(tao_fees); - let actual_alpha_delta = alpha_delta.saturating_add(alpha_fees); - - // Get reserves - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let mut balancer = SwapBalancer::::get(netuid); - - // Update weights and log errors if they go out of range - if balancer - .update_weights_for_added_liquidity( - u64::from(tao_reserve), - u64::from(alpha_reserve), - u64::from(actual_tao_delta), - u64::from(actual_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, - actual_tao_delta, - actual_alpha_delta - ); - // Return fees back into fee storage and return zeroes - FeesTao::::insert(netuid, tao_fees); - FeesAlpha::::insert(netuid, alpha_fees); - (TaoCurrency::ZERO, AlphaCurrency::ZERO) - } else { - SwapBalancer::::insert(netuid, balancer); - (actual_tao_delta, actual_alpha_delta) + ) { + // 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); + } } } @@ -161,7 +240,7 @@ impl Pallet { pub(crate) fn do_swap( netuid: NetUid, order: Order, - limit_price: U64F64, + limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, ) -> Result, DispatchError> @@ -172,7 +251,7 @@ impl Pallet { transactional::with_transaction(|| { let reserve = Order::ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::(netuid, order, limit_price, drop_fees) + let result = Self::swap_inner::(netuid, order, limit_sqrt_price, drop_fees) .map_err(Into::into); if simulate || result.is_err() { @@ -198,7 +277,7 @@ impl Pallet { fn swap_inner( netuid: NetUid, order: Order, - limit_price: U64F64, + limit_sqrt_price: SqrtPrice, drop_fees: bool, ) -> Result, Error> where @@ -210,38 +289,70 @@ impl Pallet { Error::::ReservesTooLow ); - Self::maybe_initialize_palswap(netuid, None)?; + Self::maybe_initialize_v3(netuid)?; // Because user specifies the limit price, check that it is in fact beoynd the current one ensure!( - order.is_beyond_price_limit(Self::current_price(netuid), limit_price), + order.is_beyond_price_limit(AlphaSqrtPrice::::get(netuid), limit_sqrt_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 ========"); - let amount_to_swap = order.amount(); - log::trace!("Amount to swap: {amount_to_swap}"); + log::trace!("Amount Remaining: {amount_remaining}"); - // Create and execute a swap step - let mut swap_step = BasicSwapStep::::new( - netuid, - amount_to_swap, - limit_price, - drop_fees, - ); + // 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); - let swap_result = swap_step.execute()?; + if swap_step.action() == SwapStepAction::Stop { + amount_remaining = Order::PaidIn::ZERO; + } + + // 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); + + ensure!( + iteration_counter <= MAX_SWAP_ITERATIONS, + Error::::TooManySwapSteps + ); + } - log::trace!("Delta out: {}", swap_result.delta_out); - log::trace!("Fees: {}", swap_result.fee_paid); - log::trace!("Fees for block author: {}", swap_result.fee_to_block_author); + log::trace!("\nAmount Paid Out: {amount_paid_out}"); log::trace!("======== End Swap ========"); Ok(SwapResult { - amount_paid_in: swap_result.delta_in, - amount_paid_out: swap_result.delta_out, - fee_paid: swap_result.fee_paid, - fee_to_block_author: swap_result.fee_to_block_author, + amount_paid_in: in_acc, + amount_paid_out, + fee_paid: fee_acc, }) } @@ -270,12 +381,425 @@ impl Pallet { } } - pub(crate) fn min_price_inner() -> C { - u64::from(1_000_u64).into() + 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(crate) fn max_price_inner() -> C { - u64::from(1_000_000_000_000_000_u64).into() + 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 + /// amount of liquidity to be added can be determined using + /// [`get_tao_based_liquidity`] and [`get_alpha_based_liquidity`], which compute the required + /// liquidity based on TAO and Alpha balances for the current price tick. + /// + /// ### Behavior: + /// - If the `protocol` flag is **not set** (`false`), the function will attempt to + /// **withdraw balances** from the account using `state_ops.withdraw_balances()`. + /// - If the `protocol` flag is **set** (`true`), the liquidity is added without modifying balances. + /// - If swap V3 was not initialized before, updates the value in storage. + /// + /// ### Parameters: + /// - `coldkey_account_id`: A reference to the account coldkey that is providing liquidity. + /// - `hotkey_account_id`: A reference to the account hotkey that is providing liquidity. + /// - `tick_low`: The lower bound of the price tick range. + /// - `tick_high`: The upper bound of the price tick range. + /// - `liquidity`: The amount of liquidity to be added. + /// + /// ### Returns: + /// - `Ok((u64, u64))`: (tao, alpha) amounts at new position + /// - `Err(SwapError)`: If the operation fails due to insufficient balance, invalid tick range, + /// or other swap-related errors. + /// + /// ### Errors: + /// - [`SwapError::InsufficientBalance`] if the account does not have enough balance. + /// - [`SwapError::InvalidTickRange`] if `tick_low` is greater than or equal to `tick_high`. + /// - 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, + ) -> 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)) + } + + /// Remove liquidity and credit balances back to (coldkey_account_id, hotkey_account_id) stake. + /// Removing is allowed even when user liquidity is enabled. + /// + /// 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, + ) -> 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, + }) + } + + pub fn do_modify_position( + netuid: NetUid, + 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, + }) + } + + /// 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); + } + }); + } + } + + /// 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); + } + } + + /// 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() } /// Returns the protocol account ID @@ -286,22 +810,207 @@ 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" + ); + + Ok(()) + } + /// 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 protocol liquidity, burning proceeds. - let burned_tao = T::TaoReserve::reserve(netuid.into()); - let burned_alpha = T::AlphaReserve::reserve(netuid.into()); + // 1) Force-close only protocol positions, burning proceeds. + let mut burned_tao = TaoCurrency::ZERO; + let mut burned_alpha = AlphaCurrency::ZERO; - 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; + } + } + } - FeesTao::::remove(netuid); - FeesAlpha::::remove(netuid); - PalSwapInitialized::::remove(netuid); + // 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); FeeRate::::remove(netuid); - SwapBalancer::::remove(netuid); + EnabledUserLiquidity::::remove(netuid); log::debug!( "clear_protocol_liquidity: netuid={netuid:?}, protocol_burned: τ={burned_tao:?}, α={burned_alpha:?}; state cleared" @@ -337,13 +1046,15 @@ where drop_fees: bool, should_rollback: bool, ) -> Result, DispatchError> { - let limit_price = U64F64::saturating_from_num(price_limit.to_u64()) - .safe_div(U64F64::saturating_from_num(1_000_000_000_u64)); + 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)?; Self::do_swap::( NetUid::from(netuid), order, - limit_price, + limit_sqrt_price, drop_fees, should_rollback, ) @@ -390,7 +1101,6 @@ impl SwapHandler for Pallet { amount_paid_in: actual_amount, amount_paid_out: actual_amount.to_u64().into(), fee_paid: 0.into(), - fee_to_block_author: 0.into(), }) } } @@ -400,10 +1110,28 @@ impl SwapHandler for Pallet { Self::calculate_fee_amount(netuid, amount, false) } - fn current_alpha_price(netuid: NetUid) -> U64F64 { + fn current_alpha_price(netuid: NetUid) -> U96F32 { 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())) + .collect::>(); + + if let Some(position) = positions.get_mut(0) { + let current_sqrt_price = AlphaSqrtPrice::::get(netuid); + // Adjust liquidity + let maybe_token_amounts = position.to_token_amounts(current_sqrt_price); + if let Ok((tao, _)) = maybe_token_amounts { + return tao.into(); + } + } + + TaoCurrency::ZERO + } + fn min_price() -> C { Self::min_price_inner() } @@ -416,14 +1144,20 @@ impl SwapHandler for Pallet { netuid: NetUid, tao_delta: TaoCurrency, alpha_delta: AlphaCurrency, - ) -> (TaoCurrency, AlphaCurrency) { - Self::adjust_protocol_liquidity(netuid, tao_delta, alpha_delta) + ) { + Self::adjust_protocol_liquidity(netuid, tao_delta, alpha_delta); } + fn is_user_liquidity_enabled(netuid: NetUid) -> bool { + EnabledUserLiquidity::::get(netuid) + } + fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { + Self::do_dissolve_all_liquidity_providers(netuid) + } + fn toggle_user_liquidity(netuid: NetUid, enabled: bool) { + EnabledUserLiquidity::::insert(netuid, enabled) + } fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult { Self::do_clear_protocol_liquidity(netuid) } - fn init_swap(netuid: NetUid, maybe_price: Option) { - Self::maybe_initialize_palswap(netuid, maybe_price).unwrap_or_default(); - } } diff --git a/pallets/swap/src/pallet/migrations/migrate_swapv3_to_balancer.rs b/pallets/swap/src/pallet/migrations/migrate_swapv3_to_balancer.rs deleted file mode 100644 index 147849e5a6..0000000000 --- a/pallets/swap/src/pallet/migrations/migrate_swapv3_to_balancer.rs +++ /dev/null @@ -1,81 +0,0 @@ -use super::*; -use crate::HasMigrationRun; -use frame_support::{storage_alias, traits::Get, weights::Weight}; -use scale_info::prelude::string::String; -use substrate_fixed::types::U64F64; - -pub mod deprecated_swap_maps { - use super::*; - - #[storage_alias] - pub type AlphaSqrtPrice = - StorageMap, Twox64Concat, NetUid, U64F64, ValueQuery>; - - /// TAO reservoir for scraps of protocol claimed fees. - #[storage_alias] - pub type ScrapReservoirTao = - StorageMap, Twox64Concat, NetUid, TaoCurrency, ValueQuery>; - - /// Alpha reservoir for scraps of protocol claimed fees. - #[storage_alias] - pub type ScrapReservoirAlpha = - StorageMap, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>; -} - -pub fn migrate_swapv3_to_balancer() -> Weight { - let migration_name = BoundedVec::truncate_from(b"migrate_swapv3_to_balancer".to_vec()); - let mut weight = T::DbWeight::get().reads(1); - - if HasMigrationRun::::get(&migration_name) { - log::info!( - "Migration '{:?}' has already run. Skipping.", - String::from_utf8_lossy(&migration_name) - ); - return weight; - } - - log::info!( - "Running migration '{}'", - String::from_utf8_lossy(&migration_name), - ); - - // ------------------------------ - // Step 1: Initialize swaps with price before price removal - // ------------------------------ - for (netuid, price_sqrt) in deprecated_swap_maps::AlphaSqrtPrice::::iter() { - let price = price_sqrt.saturating_mul(price_sqrt); - crate::Pallet::::maybe_initialize_palswap(netuid, Some(price)).unwrap_or_default(); - } - - // ------------------------------ - // Step 2: Clear Map entries - // ------------------------------ - remove_prefix::("Swap", "AlphaSqrtPrice", &mut weight); - remove_prefix::("Swap", "CurrentTick", &mut weight); - remove_prefix::("Swap", "EnabledUserLiquidity", &mut weight); - remove_prefix::("Swap", "FeeGlobalTao", &mut weight); - remove_prefix::("Swap", "FeeGlobalAlpha", &mut weight); - remove_prefix::("Swap", "LastPositionId", &mut weight); - // Scrap reservoirs can be just cleaned because they are already included in reserves - remove_prefix::("Swap", "ScrapReservoirTao", &mut weight); - remove_prefix::("Swap", "ScrapReservoirAlpha", &mut weight); - remove_prefix::("Swap", "Ticks", &mut weight); - remove_prefix::("Swap", "TickIndexBitmapWords", &mut weight); - remove_prefix::("Swap", "SwapV3Initialized", &mut weight); - remove_prefix::("Swap", "CurrentLiquidity", &mut weight); - remove_prefix::("Swap", "Positions", &mut weight); - - // ------------------------------ - // Step 3: Mark Migration as Completed - // ------------------------------ - - HasMigrationRun::::insert(&migration_name, true); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - - log::info!( - "Migration '{:?}' completed successfully.", - String::from_utf8_lossy(&migration_name) - ); - - weight -} diff --git a/pallets/swap/src/pallet/migrations/mod.rs b/pallets/swap/src/pallet/migrations/mod.rs deleted file mode 100644 index d34626f05e..0000000000 --- a/pallets/swap/src/pallet/migrations/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use super::*; -use frame_support::pallet_prelude::Weight; -use sp_io::KillStorageResult; -use sp_io::hashing::twox_128; -use sp_io::storage::clear_prefix; -use sp_std::vec::Vec; - -pub mod migrate_swapv3_to_balancer; - -pub(crate) fn remove_prefix(module: &str, old_map: &str, weight: &mut Weight) { - let mut prefix = Vec::new(); - prefix.extend_from_slice(&twox_128(module.as_bytes())); - prefix.extend_from_slice(&twox_128(old_map.as_bytes())); - - let removal_results = clear_prefix(&prefix, Some(u32::MAX)); - let removed_entries_count = match removal_results { - KillStorageResult::AllRemoved(removed) => removed as u64, - KillStorageResult::SomeRemaining(removed) => { - log::info!("Failed To Remove Some Items During migration"); - removed as u64 - } - }; - - *weight = (*weight).saturating_add(T::DbWeight::get().writes(removed_entries_count)); -} diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 9fc4aca769..b55df77fee 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -1,33 +1,32 @@ use core::num::NonZeroU64; +use core::ops::Neg; use frame_support::{PalletId, pallet_prelude::*, traits::Get}; use frame_system::pallet_prelude::*; -use sp_arithmetic::Perbill; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, +}; + +use crate::{ + position::{Position, PositionId}, + tick::{LayerLevel, Tick, TickIndex}, + weights::WeightInfo, }; -use crate::{pallet::balancer::Balancer, weights::WeightInfo}; pub use pallet::*; -use subtensor_macros::freeze_struct; -mod balancer; -mod hooks; mod impls; -pub mod migrations; mod swap_step; #[cfg(test)] mod tests; -// Define a maximum length for the migration key -type MigrationKeyMaxLen = ConstU32<128>; - #[allow(clippy::module_inception)] #[frame_support::pallet] #[allow(clippy::expect_used)] mod pallet { use super::*; - use frame_system::ensure_root; + use frame_system::{ensure_root, ensure_signed}; #[pallet::pallet] pub struct Pallet(_); @@ -57,6 +56,10 @@ mod pallet { #[pallet::constant] type MaxFeeRate: Get; + /// The maximum number of positions a user can have + #[pallet::constant] + type MaxPositions: Get; + /// Minimum liquidity that is safe for rounding and integer math. #[pallet::constant] type MinimumLiquidity: Get; @@ -75,52 +78,168 @@ mod pallet { 33 // ~0.05 % } - /// Fee split between pool and block builder. - /// Pool receives the portion returned by this function - #[pallet::type_value] - pub fn DefaultFeeSplit() -> Perbill { - Perbill::zero() - } - /// The fee rate applied to swaps per subnet, normalized value between 0 and u16::MAX #[pallet::storage] pub type FeeRate = StorageMap<_, Twox64Concat, NetUid, u16, ValueQuery, DefaultFeeRate>; - //////////////////////////////////////////////////// - // Balancer (PalSwap) maps and variables + // Global accrued fees in tao per subnet + #[pallet::storage] + pub type FeeGlobalTao = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; - /// Default reserve weight - #[pallet::type_value] - pub fn DefaultBalancer() -> Balancer { - Balancer::default() - } - /// u64-normalized reserve weight + // 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 to determine whether swap V3 was initialized for a specific subnet. #[pallet::storage] - pub type SwapBalancer = - StorageMap<_, Twox64Concat, NetUid, Balancer, ValueQuery, DefaultBalancer>; + pub type SwapV3Initialized = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; - /// Storage to determine whether balancer swap was initialized for a specific subnet. + /// Storage for the square root price of Alpha token for each subnet. #[pallet::storage] - pub type PalSwapInitialized = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; + pub type AlphaSqrtPrice = StorageMap<_, Twox64Concat, NetUid, U64F64, ValueQuery>; - /// Total fees in TAO per subnet due to be paid to users / protocol + /// Storage for the current price tick. #[pallet::storage] - pub type FeesTao = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>; + pub type CurrentTick = StorageMap<_, Twox64Concat, NetUid, TickIndex, ValueQuery>; - /// Total fees in Alpha per subnet due to be paid to users / protocol + /// Storage for the current liquidity amount for each subnet. #[pallet::storage] - pub type FeesAlpha = StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>; + pub type CurrentLiquidity = StorageMap<_, Twox64Concat, NetUid, u64, ValueQuery>; - /// --- Storage for migration run status + /// 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 + /// operations. Once set to `true` for a subnet, it cannot be changed back to `false`. #[pallet::storage] - pub type HasMigrationRun = - StorageMap<_, Identity, BoundedVec, bool, ValueQuery>; + pub type EnabledUserLiquidity = StorageMap<_, Twox64Concat, NetUid, bool, ValueQuery>; + + /// 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< + _, + ( + NMapKey, // Subnet ID + NMapKey, // Account ID + NMapKey, // Position ID + ), + Position, + OptionQuery, + >; + + /// Position ID counter. + #[pallet::storage] + pub type LastPositionId = StorageValue<_, u128, ValueQuery>; + + /// 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>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event emitted when the fee rate has been updated for a subnet FeeRateSet { netuid: NetUid, rate: u16 }, + + /// Event emitted when user liquidity operations are enabled for a subnet. + /// First enable even indicates a switch from V2 to V3 swap. + UserLiquidityToggled { netuid: NetUid, enable: bool }, + + /// Event emitted when a liquidity position is added to a subnet's liquidity pool. + LiquidityAdded { + /// The coldkey account that owns the position + coldkey: T::AccountId, + /// The hotkey account where Alpha comes from + hotkey: T::AccountId, + /// The subnet identifier + netuid: NetUid, + /// Unique identifier for the liquidity position + position_id: PositionId, + /// The amount of liquidity added to the position + liquidity: u64, + /// The amount of TAO tokens committed to the position + 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 { + /// The coldkey account that owns the position + coldkey: T::AccountId, + /// The hotkey account where Alpha goes to + hotkey: T::AccountId, + /// The subnet identifier + netuid: NetUid, + /// Unique identifier for the liquidity position + position_id: PositionId, + /// The amount of liquidity removed from the position + liquidity: u64, + /// The amount of TAO tokens returned to the user + tao: TaoCurrency, + /// The amount of Alpha tokens returned to the user + alpha: AlphaCurrency, + /// The amount of TAO fees earned from the position + 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 { + /// The coldkey account that owns the position + coldkey: T::AccountId, + /// The hotkey account where Alpha comes from or goes to + hotkey: T::AccountId, + /// The subnet identifier + netuid: NetUid, + /// Unique identifier for the liquidity position + position_id: PositionId, + /// The amount of liquidity added to or removed from the position + liquidity: i64, + /// The amount of TAO tokens returned to the user + tao: i64, + /// The amount of Alpha tokens returned to the user + alpha: i64, + /// The amount of TAO fees earned from the position + 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, + }, } #[pallet::error] @@ -141,9 +260,18 @@ mod pallet { /// The caller does not have enough balance for the operation. InsufficientBalance, + /// Attempted to remove liquidity that does not exist. + LiquidityNotFound, + /// The provided tick range is invalid. InvalidTickRange, + /// Maximum user positions exceeded + MaxPositionsExceeded, + + /// Too many swap steps + TooManySwapSteps, + /// Provided liquidity parameter is invalid (likely too small) InvalidLiquidityValue, @@ -153,14 +281,11 @@ mod pallet { /// The subnet does not exist. MechanismDoesNotExist, + /// User liquidity operations are disabled for this subnet + UserLiquidityDisabled, + /// The subnet does not have subtoken enabled SubtokenDisabled, - - /// Swap reserves are too imbalanced - ReservesOutOfBalance, - - /// The extrinsic is deprecated - Deprecated, } #[pallet::call] @@ -191,103 +316,315 @@ mod pallet { Ok(()) } - /// DEPRECATED + /// Enable user liquidity operations for a specific subnet. This switches the + /// subnet from V2 to V3 swap mode. Thereafter, adding new user liquidity can be disabled + /// by toggling this flag to false, but the swap mode will remain V3 because of existing + /// user liquidity until all users withdraw their liquidity. + /// + /// Only sudo or subnet owner can enable user liquidity. + /// Only sudo can disable user liquidity. #[pallet::call_index(4)] - #[pallet::weight(Weight::from_parts(15_000_000, 0))] - #[deprecated(note = "Deprecated, user liquidity is permanently disabled")] + #[pallet::weight(::WeightInfo::toggle_user_liquidity())] pub fn toggle_user_liquidity( - _origin: OriginFor, - _netuid: NetUid, - _enable: bool, + origin: OriginFor, + netuid: NetUid, + enable: bool, ) -> DispatchResult { - Err(Error::::Deprecated.into()) + if ensure_root(origin.clone()).is_err() { + let account_id: T::AccountId = ensure_signed(origin)?; + // Only enabling is allowed to subnet owner + ensure!( + T::SubnetInfo::is_owner(&account_id, netuid.into()) && enable, + DispatchError::BadOrigin + ); + } + + ensure!( + T::SubnetInfo::exists(netuid.into()), + Error::::MechanismDoesNotExist + ); + + // EnabledUserLiquidity::::insert(netuid, enable); + + // Self::deposit_event(Event::UserLiquidityToggled { netuid, enable }); + + Ok(()) } - /// DEPRECATED + /// Add liquidity to a specific price range for a subnet. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - tick_low: Lower bound of the price range + /// - tick_high: Upper bound of the price range + /// - liquidity: Amount of liquidity to add + /// + /// Emits `Event::LiquidityAdded` on success #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(15_000_000, 0))] - #[deprecated(note = "Deprecated, user liquidity is permanently disabled")] + #[pallet::weight(::WeightInfo::add_liquidity())] pub fn add_liquidity( - _origin: OriginFor, + origin: OriginFor, _hotkey: T::AccountId, _netuid: NetUid, _tick_low: TickIndex, _tick_high: TickIndex, _liquidity: u64, ) -> DispatchResult { - Err(Error::::Deprecated.into()) + ensure_signed(origin)?; + + // Extrinsic should have no effect. This fix may have to be reverted later, + // so leaving the code in for now. + + // // Ensure that the subnet exists. + // ensure!( + // T::SubnetInfo::exists(netuid.into()), + // Error::::MechanismDoesNotExist + // ); + + // ensure!( + // T::SubnetInfo::is_subtoken_enabled(netuid.into()), + // Error::::SubtokenDisabled + // ); + + // let (position_id, tao, alpha) = Self::do_add_liquidity( + // netuid.into(), + // &coldkey, + // &hotkey, + // tick_low, + // tick_high, + // liquidity, + // )?; + // let alpha = AlphaCurrency::from(alpha); + // let tao = TaoCurrency::from(tao); + + // // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly + // let tao_provided = T::BalanceOps::decrease_balance(&coldkey, tao)?; + // ensure!(tao_provided == tao, Error::::InsufficientBalance); + + // let alpha_provided = + // T::BalanceOps::decrease_stake(&coldkey, &hotkey, netuid.into(), alpha)?; + // ensure!(alpha_provided == alpha, Error::::InsufficientBalance); + + // // Add provided liquidity to user-provided reserves + // T::TaoReserve::increase_provided(netuid.into(), tao_provided); + // T::AlphaReserve::increase_provided(netuid.into(), alpha_provided); + + // // Emit an event + // Self::deposit_event(Event::LiquidityAdded { + // coldkey, + // hotkey, + // netuid, + // position_id, + // liquidity, + // tao, + // alpha, + // tick_low, + // tick_high, + // }); + + // Ok(()) + + Err(Error::::UserLiquidityDisabled.into()) } - /// DEPRECATED + /// Remove liquidity from a specific position. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - position_id: ID of the position to remove + /// + /// Emits `Event::LiquidityRemoved` on success #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(15_000_000, 0))] - #[deprecated(note = "Deprecated, user liquidity is permanently disabled")] + #[pallet::weight(::WeightInfo::remove_liquidity())] pub fn remove_liquidity( - _origin: OriginFor, - _hotkey: T::AccountId, - _netuid: NetUid, - _position_id: PositionId, + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + position_id: PositionId, ) -> DispatchResult { - Err(Error::::Deprecated.into()) + let coldkey = ensure_signed(origin)?; + + // Ensure that the subnet exists. + ensure!( + T::SubnetInfo::exists(netuid.into()), + Error::::MechanismDoesNotExist + ); + + // Remove liquidity + let result = Self::do_remove_liquidity(netuid, &coldkey, position_id)?; + + // Credit the returned tao and alpha to the account + T::BalanceOps::increase_balance(&coldkey, result.tao.saturating_add(result.fee_tao)); + T::BalanceOps::increase_stake( + &coldkey, + &hotkey, + netuid.into(), + result.alpha.saturating_add(result.fee_alpha), + )?; + + // Remove withdrawn liquidity from user-provided reserves + T::TaoReserve::decrease_provided(netuid.into(), result.tao); + T::AlphaReserve::decrease_provided(netuid.into(), result.alpha); + + // Emit an event + Self::deposit_event(Event::LiquidityRemoved { + coldkey, + hotkey, + netuid: netuid.into(), + position_id, + liquidity: result.liquidity, + tao: result.tao, + 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(()) } - /// DEPRECATED + /// Modify a liquidity position. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - position_id: ID of the position to remove + /// - liquidity_delta: Liquidity to add (if positive) or remove (if negative) + /// + /// Emits `Event::LiquidityRemoved` on success #[pallet::call_index(3)] - #[pallet::weight(Weight::from_parts(15_000_000, 0))] - #[deprecated(note = "Deprecated, user liquidity is permanently disabled")] + #[pallet::weight(::WeightInfo::modify_position())] pub fn modify_position( - _origin: OriginFor, - _hotkey: T::AccountId, - _netuid: NetUid, - _position_id: PositionId, - _liquidity_delta: i64, + origin: OriginFor, + hotkey: T::AccountId, + netuid: NetUid, + position_id: PositionId, + liquidity_delta: i64, ) -> DispatchResult { - Err(Error::::Deprecated.into()) + let coldkey = ensure_signed(origin)?; + + // Ensure that the subnet exists. + ensure!( + T::SubnetInfo::exists(netuid.into()), + Error::::MechanismDoesNotExist + ); + + ensure!( + T::SubnetInfo::is_subtoken_enabled(netuid.into()), + Error::::SubtokenDisabled + ); + + // Add or remove liquidity + let result = + Self::do_modify_position(netuid, &coldkey, &hotkey, position_id, liquidity_delta)?; + + if liquidity_delta > 0 { + // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly + let tao_provided = T::BalanceOps::decrease_balance(&coldkey, result.tao)?; + ensure!(tao_provided == result.tao, Error::::InsufficientBalance); + + let alpha_provided = + T::BalanceOps::decrease_stake(&coldkey, &hotkey, netuid.into(), result.alpha)?; + ensure!( + alpha_provided == result.alpha, + Error::::InsufficientBalance + ); + + // Emit an event + Self::deposit_event(Event::LiquidityModified { + coldkey: coldkey.clone(), + hotkey: hotkey.clone(), + netuid, + position_id, + liquidity: liquidity_delta, + tao: result.tao.to_u64() as i64, + 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 + T::BalanceOps::increase_balance(&coldkey, result.tao); + T::BalanceOps::increase_stake(&coldkey, &hotkey, netuid.into(), result.alpha)?; + + // Emit an event + if result.removed { + Self::deposit_event(Event::LiquidityRemoved { + coldkey: coldkey.clone(), + hotkey: hotkey.clone(), + netuid, + position_id, + liquidity: liquidity_delta.unsigned_abs(), + tao: result.tao, + 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 { + coldkey: coldkey.clone(), + hotkey: hotkey.clone(), + netuid, + position_id, + liquidity: liquidity_delta, + tao: (result.tao.to_u64() as i64).neg(), + 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, + }); + } + } + + // Credit accrued fees to user account (no matter if liquidity is added or removed) + if result.fee_tao > TaoCurrency::ZERO { + T::BalanceOps::increase_balance(&coldkey, result.fee_tao); + } + if !result.fee_alpha.is_zero() { + T::BalanceOps::increase_stake( + &coldkey, + &hotkey.clone(), + netuid.into(), + result.fee_alpha, + )?; + } + + Ok(()) } - /// DEPRECATED + /// Disable user liquidity in all subnets. + /// + /// Emits `Event::UserLiquidityToggled` on success #[pallet::call_index(5)] - #[pallet::weight(Weight::from_parts(15_000_000, 0))] - #[deprecated(note = "Deprecated, user liquidity is permanently disabled")] - pub fn disable_lp(_origin: OriginFor) -> DispatchResult { - Err(Error::::Deprecated.into()) + #[pallet::weight(::WeightInfo::modify_position())] + pub fn disable_lp(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + + for netuid in 1..=128 { + let netuid = NetUid::from(netuid as u16); + if EnabledUserLiquidity::::get(netuid) { + EnabledUserLiquidity::::insert(netuid, false); + Self::deposit_event(Event::UserLiquidityToggled { + netuid, + enable: false, + }); + } + + // Remove provided liquidity unconditionally because the network may have + // user liquidity previously disabled + // Ignore result to avoid early stopping + let _ = Self::do_dissolve_all_liquidity_providers(netuid); + } + + Ok(()) } } } - -/// Struct representing a tick index, DEPRECATED -#[freeze_struct("7c280c2b3bbbb33e")] -#[derive( - Debug, - Default, - Clone, - Copy, - Decode, - Encode, - DecodeWithMemTracking, - TypeInfo, - MaxEncodedLen, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, -)] -pub struct TickIndex(i32); - -/// Struct representing a liquidity position ID, DEPRECATED -#[freeze_struct("e695cd6455c3f0cb")] -#[derive( - Clone, - Copy, - Decode, - DecodeWithMemTracking, - Default, - Encode, - Eq, - MaxEncodedLen, - PartialEq, - RuntimeDebug, - TypeInfo, -)] -pub struct PositionId(u128); diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index a161238300..6791835b1a 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -1,12 +1,14 @@ use core::marker::PhantomData; -use frame_support::ensure; use safe_math::*; -use sp_core::Get; -use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; +use substrate_fixed::types::{I64F64, U64F64}; +use subtensor_runtime_common::{AlphaCurrency, Currency, 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 @@ -18,16 +20,22 @@ where // Input parameters netuid: NetUid, drop_fees: bool, - requested_delta_in: PaidIn, - limit_price: U64F64, - // Intermediate calculations - target_price: U64F64, - current_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, // Result values + action: SwapStepAction, delta_in: PaidIn, - final_price: U64F64, + final_price: SqrtPrice, fee: PaidIn, _phantom: PhantomData<(T, PaidIn, PaidOut)>, @@ -44,25 +52,36 @@ where pub(crate) fn new( netuid: NetUid, amount_remaining: PaidIn, - limit_price: U64F64, + limit_sqrt_price: SqrtPrice, 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 requested_delta_in = amount_remaining.saturating_sub(fee); + let possible_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); Self { netuid, drop_fees, - requested_delta_in, - limit_price, - target_price, - current_price, + target_sqrt_price, + limit_sqrt_price, + current_sqrt_price, + edge_sqrt_price, + edge_tick, + possible_delta_in, + current_liquidity, + action: SwapStepAction::Stop, delta_in: PaidIn::ZERO, - final_price: target_price, + final_price: target_sqrt_price, fee, _phantom: PhantomData, } @@ -79,25 +98,64 @@ where let mut recalculate_fee = false; // Calculate the stopping price: The price at which we either reach the limit price, - // 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 { + // 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 - self.final_price = self.limit_price; - self.delta_in = Self::delta_in(self.netuid, self.current_price, self.limit_price); + // 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; + } 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; recalculate_fee = true; } - log::trace!("\tCurrent Price : {}", self.current_price); - log::trace!("\tTarget Price : {}", self.target_price); - log::trace!("\tLimit Price : {}", self.limit_price); + 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!("\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. + // recalculate it in case if we hit the limit price or the edge price. if recalculate_fee { let u16_max = U64F64::saturating_from_num(u16::MAX); let fee_rate = if self.drop_fees { @@ -111,131 +169,325 @@ where .saturating_to_num::() .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(); + } } /// Process a single step of a swap fn process_swap(&self) -> Result, Error> { - // Convert amounts, actual swap happens here + // Hold the fees + Self::add_fees( + self.netuid, + Pallet::::current_liquidity_safe(self.netuid), + self.fee, + ); let delta_out = Self::convert_deltas(self.netuid, self.delta_in); - log::trace!("\tDelta Out : {delta_out}"); - let mut fee_to_block_author = 0.into(); - if self.delta_in > 0.into() { - ensure!(delta_out > 0.into(), Error::::ReservesTooLow); - - // Split fees according to DefaultFeeSplit between liquidity pool and - // validators. In case we want just to forward 100% of fees to the block - // author, it can be done this way: - // ``` - // fee_to_block_author = self.fee; - // ``` - let fee_split = DefaultFeeSplit::get(); - let lp_fee = fee_split.mul_floor(self.fee.to_u64()).into(); - Self::add_fees(self.netuid, lp_fee); - fee_to_block_author = self.fee.saturating_sub(lp_fee); + // 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); + + // Update current tick + let new_current_tick = TickIndex::from_sqrt_price_bounded(self.final_price); + CurrentTick::::set(self.netuid, new_current_tick); + Ok(SwapStepResult { + amount_to_take: self.delta_in.saturating_add(self.fee), fee_paid: self.fee, delta_in: self.delta_in, delta_out, - fee_to_block_author, }) } + + pub(crate) fn action(&self) -> SwapStepAction { + self.action + } } impl SwapStep for BasicSwapStep { - fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> TaoCurrency { - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let balancer = SwapBalancer::::get(netuid); - TaoCurrency::from(balancer.calculate_quote_delta_in( - price_curr, - price_target, - tao_reserve.into(), - )) + 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 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 balancer = SwapBalancer::::get(netuid); - let dy = delta_in; - let dx = Self::convert_deltas(netuid, dy); - balancer.calculate_price( - u64::from(alpha_reserve.saturating_sub(dx)), - u64::from(tao_reserve.saturating_add(dy)), + fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex { + ActiveTickIndexManager::::find_closest_higher( + netuid, + current_tick.next().unwrap_or(TickIndex::MAX), ) + .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(price1: &U64F64, price2: &U64F64) -> bool { - price1 <= price2 + fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { + sq_price1 <= sq_price2 } - fn add_fees(netuid: NetUid, fee: TaoCurrency) { - FeesTao::::mutate(netuid, |total| *total = total.saturating_add(fee)) + 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 convert_deltas(netuid: NetUid, delta_in: TaoCurrency) -> AlphaCurrency { - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let balancer = SwapBalancer::::get(netuid); - let e = balancer.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::(), - ) + // 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(()) } } impl SwapStep for BasicSwapStep { - fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> AlphaCurrency { - let alpha_reserve = T::AlphaReserve::reserve(netuid); - let balancer = SwapBalancer::::get(netuid); - AlphaCurrency::from(balancer.calculate_base_delta_in( - price_curr, - price_target, - alpha_reserve.into(), - )) + 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 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 balancer = SwapBalancer::::get(netuid); - let dx = delta_in; - let dy = Self::convert_deltas(netuid, dx); - balancer.calculate_price( - u64::from(alpha_reserve.saturating_add(dx)), - u64::from(tao_reserve.saturating_sub(dy)), + 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 price_is_closer(price1: &U64F64, price2: &U64F64) -> bool { - price1 >= price2 + 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_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { + sq_price1 >= sq_price2 } - fn add_fees(netuid: NetUid, fee: AlphaCurrency) { - FeesAlpha::::mutate(netuid, |total| *total = total.saturating_add(fee)) + fn action_on_edge_sqrt_price() -> SwapStepAction { + SwapStepAction::Stop + } + + 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 convert_deltas(netuid: NetUid, delta_in: AlphaCurrency) -> TaoCurrency { - let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); - let tao_reserve = T::TaoReserve::reserve(netuid.into()); - let balancer = SwapBalancer::::get(netuid); - let e = balancer.exp_base_quote(alpha_reserve.into(), delta_in.into()); - 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::(), - ) + // 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(()) } } @@ -246,25 +498,49 @@ where PaidOut: Currency, { /// Get the input amount needed to reach the target price - fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> PaidIn; + fn delta_in( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + sqrt_price_target: SqrtPrice, + ) -> PaidIn; - /// Get the target price based on the input amount - fn price_target(netuid: NetUid, delta_in: PaidIn) -> U64F64; + /// 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; - /// Returns True if price1 is closer to the current price than price2 + /// 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 /// in terms of order direction. - /// For buying: price1 <= price2 - /// For selling: price1 >= price2 - fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool; + /// 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; /// Add fees to the global fee counters - fn add_fees(netuid: NetUid, fee: PaidIn); + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: PaidIn); /// Convert input amount (delta_in) to output amount (delta_out) /// - /// This is the core method of the swap that tells how much output token is given for an - /// amount of input token + /// 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. 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)] @@ -273,8 +549,14 @@ 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, - pub(crate) fee_to_block_author: PaidIn, +} + +#[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 912c89bbe8..cd18e665cd 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -6,30 +6,76 @@ )] use approx::assert_abs_diff_eq; -use frame_support::{assert_noop, assert_ok}; -use sp_arithmetic::Perquintill; +use frame_support::{assert_err, assert_noop, assert_ok}; +use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; -use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{Currency, NetUid}; +use substrate_fixed::types::U96F32; +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); -// Run all tests: -// cargo test --package pallet-subtensor-swap --lib -- pallet::tests --nocapture + // 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); -#[allow(dead_code)] -fn get_min_price() -> U64F64 { - U64F64::from_num(Pallet::::min_price_inner::()) - / U64F64::from_num(1_000_000_000) + ( + current_price_low.to_num::(), + current_price_high.to_num::() + 0.000000001, + ) } -#[allow(dead_code)] -fn get_max_price() -> U64F64 { - U64F64::from_num(Pallet::::max_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 + } + } + } } mod dispatchables { @@ -60,405 +106,641 @@ mod dispatchables { }); } - fn perquintill_to_f64(p: Perquintill) -> f64 { - let parts = p.deconstruct() as f64; - parts / 1_000_000_000_000_000_000_f64 - } + // #[test] + // fn test_toggle_user_liquidity() { + // new_test_ext().execute_with(|| { + // let netuid = NetUid::from(101); + + // assert!(!EnabledUserLiquidity::::get(netuid)); + + // assert_ok!(Swap::toggle_user_liquidity( + // RuntimeOrigin::root(), + // netuid.into(), + // true + // )); + + // assert!(EnabledUserLiquidity::::get(netuid)); + + // assert_noop!( + // Swap::toggle_user_liquidity(RuntimeOrigin::signed(666), netuid.into(), true), + // DispatchError::BadOrigin + // ); + + // assert_ok!(Swap::toggle_user_liquidity( + // RuntimeOrigin::signed(1), + // netuid.into(), + // true + // )); + + // assert_noop!( + // Swap::toggle_user_liquidity( + // RuntimeOrigin::root(), + // NON_EXISTENT_NETUID.into(), + // true + // ), + // Error::::MechanismDoesNotExist + // ); + // }); + // } +} - /// 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); +#[test] +fn test_swap_initialization() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); - let tao_delta = TaoCurrency::from(tao_delta); - let alpha_delta = AlphaCurrency::from(alpha_delta); + // Get reserves from the mock provider + let tao = TaoReserve::reserve(netuid.into()); + let alpha = AlphaReserve::reserve(netuid.into()); - // 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); + assert_ok!(Pallet::::maybe_initialize_v3(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); + assert!(SwapV3Initialized::::get(netuid)); - // 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. - ); + // Verify current price is set + let sqrt_price = AlphaSqrtPrice::::get(netuid); + let expected_sqrt_price = U64F64::from_num(0.5_f64); + assert_abs_diff_eq!( + sqrt_price.to_num::(), + expected_sqrt_price.to_num::(), + epsilon = 0.000000001 + ); - // 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 = SwapBalancer::::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 - ); - }); - }); - } + // 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); + }); +} - /// 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 - /// Balancer::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 +// 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) [ - (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), + // 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() - .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); - } + .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, + ); - // 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 + // 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) + }, + ); + }); +} - // Now do one-time big injection with another netuid and compare weights +#[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 netuid2 = NetUid::from(2); + let limit = MaxPositions::get() as usize; - // Initialize same large reserves - TaoReserve::set_mock_reserve(netuid2, initial_tao_reserve); - AlphaReserve::set_mock_reserve(netuid2, initial_alpha_reserve); + for _ in 0..limit { + Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + TickIndex::MIN, + TickIndex::MAX, + liquidity, + ) + .unwrap(); + } - // 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); + let test_result = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + TickIndex::MIN, + TickIndex::MAX, + liquidity, + ); - // Compare reserve weights for netuid 1 and 2 - let res_weights1 = SwapBalancer::::get(netuid1); - let res_weights2 = SwapBalancer::::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 - ); - }); - }); - } + assert_err!(test_result, Error::::MaxPositionsExceeded); + }); +} - /// 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 +#[test] +fn test_add_liquidity_out_of_bounds() { + new_test_ext().execute_with(|| { [ - (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), + // 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() - .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 = SwapBalancer::::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); - } - }); + .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, + ); }); - } + }); +} - /// Collects the fees and adds them to protocol liquidity - /// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::dispatchables::test_adjust_protocol_liquidity_collects_fees --exact --nocapture - #[test] - fn test_adjust_protocol_liquidity_collects_fees() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); +#[test] +fn test_add_liquidity_over_balance() { + new_test_ext().execute_with(|| { + let coldkey_account_id = 3; + let hotkey_account_id = 1002; - let tao_delta = TaoCurrency::ZERO; - let alpha_delta = AlphaCurrency::ZERO; - - // Initialize reserves and price - // 0.1 price - let tao = TaoCurrency::from(1_000_000_000_u64); - let alpha = AlphaCurrency::from(10_000_000_000_u64); - TaoReserve::set_mock_reserve(netuid, tao); - AlphaReserve::set_mock_reserve(netuid, alpha); - - // Insert fees - let tao_fees = TaoCurrency::from(1_000); - let alpha_fees = AlphaCurrency::from(1_000); - FeesTao::::insert(netuid, tao_fees); - FeesAlpha::::insert(netuid, alpha_fees); - - // Adjust reserves - let (actual_tao_delta, actual_alpha_delta) = - 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 returned reserve deltas are correct (include fees) - assert_eq!(actual_tao_delta, tao_fees); - assert_eq!(actual_alpha_delta, alpha_fees); - - // Check that fees got reset - assert_eq!(FeesTao::::get(netuid), TaoCurrency::ZERO); - assert_eq!(FeesAlpha::::get(netuid), AlphaCurrency::ZERO); + [ + // 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, + ); }); - } + }); } +// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_remove_liquidity_basic --exact --show-output #[test] -fn test_swap_initialization() { +fn test_remove_liquidity_basic() { new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); + 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); - // Setup reserves - let tao = TaoCurrency::from(1_000_000_000u64); - let alpha = AlphaCurrency::from(4_000_000_000u64); - TaoReserve::set_mock_reserve(netuid, tao); - AlphaReserve::set_mock_reserve(netuid, alpha); + let (current_price_low, current_price_high) = get_ticked_prices_around_current_price(); - assert_ok!(Pallet::::maybe_initialize_palswap(netuid, None)); - assert!(PalSwapInitialized::::get(netuid)); + // 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(); - // Verify current price is set - let price = Pallet::::current_price(netuid); - let expected_price = U64F64::from_num(0.25_f64); - assert_abs_diff_eq!( - price.to_num::(), - expected_price.to_num::(), - epsilon = 0.000000001 - ); + // 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); - // Verify that swap reserve weight is initialized - let reserve_weight = SwapBalancer::::get(netuid); - assert_eq!( - reserve_weight.get_quote_weight(), - Perquintill::from_rational(1_u64, 2_u64), - ); + // 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); + }); }); } #[test] -fn test_swap_initialization_with_price() { +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); - // Setup reserves, tao / alpha = 0.25 - let tao = TaoCurrency::from(1_000_000_000u64); - let alpha = AlphaCurrency::from(4_000_000_000u64); - TaoReserve::set_mock_reserve(netuid, tao); - AlphaReserve::set_mock_reserve(netuid, alpha); + // Calculate ticks (assuming tick math is tested separately) + let tick_low = price_to_tick(min_price); + let tick_high = price_to_tick(max_price); - // Initialize with 0.2 price - assert_ok!(Pallet::::maybe_initialize_palswap( + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Add liquidity + assert_ok!(Pallet::::do_add_liquidity( netuid, - Some(U64F64::from(1u16) / U64F64::from(5u16)) + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, )); - assert!(PalSwapInitialized::::get(netuid)); - // Verify current price is set to 0.2 - let price = Pallet::::current_price(netuid); - let expected_price = U64F64::from_num(0.2_f64); - assert_abs_diff_eq!( - price.to_num::(), - expected_price.to_num::(), - epsilon = 0.000000001 + 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_swap_basic --exact --nocapture +// 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 + ); + + // Current liquidity is reduced with modify_position + assert!(CurrentLiquidity::::get(netuid) < liquidity_before); + + // 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); + + // 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(); + + 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, + ); + + // 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 #[test] fn test_swap_basic() { new_test_ext().execute_with(|| { @@ -466,284 +748,858 @@ 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, { - let swap_amount = order.amount().to_u64(); + // Consumed liquidity ticks + let tick_low = TickIndex::MIN; + let tick_high = TickIndex::MAX; + let liquidity = order.amount().to_u64(); // Setup swap - // 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); - assert_ok!(Pallet::::maybe_initialize_palswap(netuid, None)); - - // Get current price - let current_price_before = Pallet::::current_price(netuid); + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - // Get reserves - let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); - let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); + // 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); - // 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)) - }; + // Get current price + let current_price = Pallet::::current_price(netuid); // Swap - let limit_price_fixed = U64F64::from_num(limit_price); + let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); let swap_result = - Pallet::::do_swap(netuid, order.clone(), limit_price_fixed, false, false) + Pallet::::do_swap(netuid, order.clone(), sqrt_limit_price, false, false) .unwrap(); assert_abs_diff_eq!( swap_result.amount_paid_out.to_u64(), - expected_output_amount as u64, - epsilon = 1 + output_amount, + epsilon = output_amount / 100 ); assert_abs_diff_eq!( swap_result.paid_in_reserve_delta() as u64, - (swap_amount - expected_fee), - epsilon = 1 + liquidity, + epsilon = liquidity / 10 ); assert_abs_diff_eq!( swap_result.paid_out_reserve_delta() as i64, - -(expected_output_amount as i64), - epsilon = 1 + -(output_amount as i64), + epsilon = output_amount as i64 / 10 ); - // 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, - ), - ); - } + // 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 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 + + // 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 that price movement is in correct direction - let current_price_after = Pallet::::current_price(netuid); assert_eq!( - current_price_after >= current_price_before, - price_should_grow + 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); + + // 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); } // 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( + 1.into(), + GetAlphaForTao::with_amount(1_000), + 1000.0, + 3990, + true, + ); perform_test( 2.into(), - GetTaoForAlpha::with_amount(123_456), + GetTaoForAlpha::with_amount(1_000), 0.0001, + 250, false, ); perform_test( 3.into(), - GetAlphaForTao::with_amount(1_000_000_000), - 1000.0, - true, - ); - perform_test( - 3.into(), - GetAlphaForTao::with_amount(10_000_000_000), + GetAlphaForTao::with_amount(500_000_000), 1000.0, + 2_000_000_000, true, ); }); } -// cargo test --package pallet-subtensor-swap --lib -- pallet::impls::tests::test_swap_precision_edge_case --exact --show-output +// 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 #[test] -fn test_swap_precision_edge_case() { - // Test case: tao_reserve, alpha_reserve, swap_amount - [ - (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(|(tao_reserve, alpha_reserve, swap_amount)| { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); - let order = GetTaoForAlpha::with_amount(swap_amount); - - // Very low reserves - TaoReserve::set_mock_reserve(netuid, TaoCurrency::from(tao_reserve)); - AlphaReserve::set_mock_reserve(netuid, AlphaCurrency::from(alpha_reserve)); +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::(); + }); - // Minimum possible limit price - let limit_price: U64F64 = get_min_price(); - println!("limit_price = {:?}", limit_price); + 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 + ); - // Swap - let swap_result = - Pallet::::do_swap(netuid, order, limit_price, false, true).unwrap(); + // 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. + ); - assert!(swap_result.amount_paid_out > TaoCurrency::ZERO); - }); - }); -} + ////////////////////////////////////////////// + // Swap -#[test] -fn test_convert_deltas() { - new_test_ext().execute_with(|| { - 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), - (1500, 1000, 0.50000001, 1), - (1500, 1000, 0.50000001, 10000), - (1500, 1000, 0.50000001, 1000000), - (1500, 1000, 0.50000001, u64::MAX), - (1, 1000000, 0.50000001, 1), - (1, 1000000, 0.50000001, 10000), - (1, 1000000, 0.50000001, 1000000), - (1, 1000000, 0.50000001, u64::MAX), - (1000000, 1, 0.50000001, 1), - (1000000, 1, 0.50000001, 10000), - (1000000, 1, 0.50000001, 1000000), - (1000000, 1, 0.50000001, u64::MAX), - (1500, 1000, 0.49999999, 1), - (1500, 1000, 0.49999999, 10000), - (1500, 1000, 0.49999999, 1000000), - (1500, 1000, 0.49999999, u64::MAX), - (1, 1000000, 0.49999999, 1), - (1, 1000000, 0.49999999, 10000), - (1, 1000000, 0.49999999, 1000000), - (1, 1000000, 0.49999999, u64::MAX), - (1000000, 1, 0.49999999, 1), - (1000000, 1, 0.49999999, 10000), - (1000000, 1, 0.49999999, 1000000), - (1000000, 1, 0.49999999, 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), - ] { - // Initialize reserves and weights - let netuid = NetUid::from(1); - TaoReserve::set_mock_reserve(netuid, TaoCurrency::from(tao)); - AlphaReserve::set_mock_reserve(netuid, AlphaCurrency::from(alpha)); - assert_ok!(Pallet::::maybe_initialize_palswap(netuid, None)); - - let w_accuracy = 1_000_000_000_f64; - let w_quote_pt = - Perquintill::from_rational((w_quote * w_accuracy) as u128, w_accuracy as u128); - let bal = Balancer::new(w_quote_pt).unwrap(); - SwapBalancer::::insert(netuid, bal); - - // 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)); + // Calculate the expected output amount for the cornercase of one step + let order_liquidity = order_liquidity_fraction * position_liquidity as f64; - 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 - ); - } - }); -} + let output_amount = >::approx_expected_swap_output( + sqrt_current_price, + liquidity_before as f64, + order_liquidity, + ); -#[test] -fn test_rollback_works() { - new_test_ext().execute_with(|| { - let netuid = NetUid::from(1); + // 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. + ); - assert_eq!( + 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) + [ + // 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), + ] + .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); + + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + let current_price = SqrtPrice::from_num(0.500_000_512_192_122_7); + let tick = TickIndex::try_from_sqrt_price(current_price).unwrap(); + + let round_trip_price = TickIndex::try_to_sqrt_price(&tick).unwrap(); + assert!(round_trip_price <= current_price); + + let roundtrip_tick = TickIndex::try_from_sqrt_price(round_trip_price).unwrap(); + assert!(tick == roundtrip_tick); + }); +} + +#[test] +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, + ), + ] { + { + AlphaSqrtPrice::::insert(netuid, sqrt_price); + + 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() + ); + } + } + }); +} + +// #[test] +// fn test_user_liquidity_disabled() { +// new_test_ext().execute_with(|| { +// // Use a netuid above 100 since our mock enables liquidity for 0-100 +// let netuid = NetUid::from(101); +// let tick_low = TickIndex::new_unchecked(-1000); +// let tick_high = TickIndex::new_unchecked(1000); +// let position_id = PositionId::from(1); +// let liquidity = 1_000_000_000; +// let liquidity_delta = 500_000_000; + +// assert!(!EnabledUserLiquidity::::get(netuid)); + +// assert_noop!( +// Swap::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// liquidity +// ), +// Error::::UserLiquidityDisabled +// ); + +// assert_noop!( +// Swap::do_remove_liquidity(netuid, &OK_COLDKEY_ACCOUNT_ID, position_id), +// Error::::LiquidityNotFound +// ); + +// assert_noop!( +// Swap::modify_position( +// RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), +// OK_HOTKEY_ACCOUNT_ID, +// netuid, +// position_id, +// liquidity_delta +// ), +// Error::::UserLiquidityDisabled +// ); + +// assert_ok!(Swap::toggle_user_liquidity( +// RuntimeOrigin::root(), +// netuid, +// true +// )); + +// let position_id = Swap::do_add_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// tick_low, +// tick_high, +// liquidity, +// ) +// .unwrap() +// .0; + +// assert_ok!(Swap::do_modify_position( +// netuid.into(), +// &OK_COLDKEY_ACCOUNT_ID, +// &OK_HOTKEY_ACCOUNT_ID, +// position_id, +// liquidity_delta, +// )); + +// assert_ok!(Swap::do_remove_liquidity( +// netuid, +// &OK_COLDKEY_ACCOUNT_ID, +// position_id, +// )); +// }); +// } + +/// 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)); + + // 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); + + // Add liquidity + assert_ok!(Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, + )); + + // 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 + }; + + assert_eq!(CurrentLiquidity::::get(netuid), expected_liquidity) + }); + }); +} + +#[test] +fn test_rollback_works() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + assert_eq!( Pallet::::do_swap( netuid, GetAlphaForTao::with_amount(1_000_000), @@ -764,7 +1620,80 @@ fn test_rollback_works() { }) } -#[allow(dead_code)] +/// 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); + }); +} + fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { if t < a { a @@ -775,40 +1704,955 @@ fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { } } -#[allow(dead_code)] fn print_current_price(netuid: NetUid) { - let current_price = Pallet::::current_price(netuid); + let current_sqrt_price = AlphaSqrtPrice::::get(netuid).to_num::(); + let current_price = current_sqrt_price * current_sqrt_price; log::trace!("Current price: {current_price:.6}"); } -/// Simple palswap path: PalSwap is initialized. -/// Function must still clear any residual storages and succeed. +/// 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_liquidate_pal_simple_ok_and_clears() { +fn test_wrapping_fees() { new_test_ext().execute_with(|| { - let netuid = NetUid::from(202); + 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(); - // Insert map values - FeeRate::::insert(netuid, 1_000); - 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 bal = Balancer::new(w_quote_pt).unwrap(); - SwapBalancer::::insert(netuid, bal); + print_current_price(netuid); - // Sanity: PalSwap is not initialized - assert!(PalSwapInitialized::::get(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(); - // ACT + 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(), + ); + + 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); + }); +} + +/// 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)); + + // 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); + } +} + +// TODO: Revise when user liquidity is available +// #[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_noop!( +// Pallet::::add_liquidity( +// RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), +// OK_HOTKEY_ACCOUNT_ID, +// netuid, +// tick_low, +// tick_high, +// liquidity, +// ), +// Error::::SubtokenDisabled +// ); + +// assert_noop!( +// Pallet::::modify_position( +// RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), +// OK_HOTKEY_ACCOUNT_ID, +// netuid, +// PositionId::from(0), +// liquidity as i64, +// ), +// Error::::SubtokenDisabled +// ); +// }); +// } + +#[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). +// #[test] +// fn test_liquidate_v3_with_user_liquidity_disabled() { +// new_test_ext().execute_with(|| { +// let netuid = NetUid::from(101); + +// assert_ok!(Pallet::::maybe_initialize_v3(netuid)); +// assert!(SwapV3Initialized::::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 +// )); + +// // Users-only dissolve, then clear protocol liquidity/state. +// assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); +// assert_ok!(Pallet::::do_clear_protocol_liquidity(netuid)); + +// // ASSERT: positions & ticks gone, state reset +// assert_eq!( +// Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), +// 0 +// ); +// 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)); +// assert!(!AlphaSqrtPrice::::contains_key(netuid)); +// assert!(!CurrentTick::::contains_key(netuid)); +// assert!(!CurrentLiquidity::::contains_key(netuid)); +// assert!(!FeeGlobalTao::::contains_key(netuid)); +// assert!(!FeeGlobalAlpha::::contains_key(netuid)); + +// // `EnabledUserLiquidity` is removed by protocol clear stage. +// assert!(!EnabledUserLiquidity::::contains_key(netuid)); +// }); +// } + +/// Non‑V3 path: V3 not initialized (no positions); function must still clear any residual storages and succeed. +#[test] +fn test_liquidate_non_v3_uninitialized_ok_and_clears() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(202); + + // Sanity: V3 is not initialized + assert!(!SwapV3Initialized::::get(netuid)); + assert!( + Positions::::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!( + 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() + ); + // 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!(!EnabledUserLiquidity::::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)); + + // 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)); + }); + + // Non‑V3 flavor + new_test_ext().execute_with(|| { + let netuid = NetUid::from(8); + + // 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)); + + 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)); + }); +} + +#[test] +fn liquidate_v3_refunds_user_funds_and_clears_state() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + // 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)" + ); + + // Clear protocol liquidity and V3 state now. + assert_ok!(Pallet::::do_clear_protocol_liquidity(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)); + }); +} + +#[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_v3(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); + + // --- 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" + ); + + // 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)); + }); +} + +#[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" + ); + + 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" + ); + }); +} + +#[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_eq!( + after_total, before_total, + "owner’s α must be conserved across hot ledgers + (owner,owner)" + ); + }); +} + +#[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)" + ); + + 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)); + + 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" + ); + + 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!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none(), + "active tick bitmap words must be cleared" + ); + assert!(!FeeRate::::contains_key(netuid)); - assert!(!FeesTao::::contains_key(netuid)); - assert!(!FeesAlpha::::contains_key(netuid)); - assert!(!PalSwapInitialized::::contains_key(netuid)); - assert!(!SwapBalancer::::contains_key(netuid)); + assert!(!EnabledUserLiquidity::::contains_key(netuid)); }); } @@ -816,36 +2660,84 @@ fn test_liquidate_pal_simple_ok_and_clears() { fn test_clear_protocol_liquidity_green_path() { new_test_ext().execute_with(|| { // --- Arrange --- - let netuid = NetUid::from(1); + let netuid = NetUid::from(55); - // Initialize swap state - assert_ok!(Pallet::::maybe_initialize_palswap(netuid, None)); + // 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!( - PalSwapInitialized::::get(netuid), - "Swap must be initialized" + SwapV3Initialized::::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!(!FeesTao::::contains_key(netuid)); - assert!(!FeesAlpha::::contains_key(netuid)); + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); - // Flags - assert!(!PalSwapInitialized::::contains_key(netuid)); + // Price / tick / liquidity / flags + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!SwapV3Initialized::::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!(!PalSwapInitialized::::contains_key(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)); }); } -#[allow(dead_code)] fn as_tuple( (t_used, a_used, t_rem, a_rem): (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency), ) -> (u64, u64, u64, u64) { @@ -857,43 +2749,160 @@ fn as_tuple( ) } -// cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_migrate_swapv3_to_balancer --exact --nocapture #[test] -fn test_migrate_swapv3_to_balancer() { - use crate::migrations::migrate_swapv3_to_balancer::deprecated_swap_maps; - use substrate_fixed::types::U64F64; +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)); +} - new_test_ext().execute_with(|| { - let migration = - crate::migrations::migrate_swapv3_to_balancer::migrate_swapv3_to_balancer::; - let netuid = NetUid::from(1); +#[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)); +} - // Insert deprecated maps values - deprecated_swap_maps::AlphaSqrtPrice::::insert(netuid, U64F64::from_num(1.23)); - deprecated_swap_maps::ScrapReservoirTao::::insert(netuid, TaoCurrency::from(9876)); - deprecated_swap_maps::ScrapReservoirAlpha::::insert( - netuid, - AlphaCurrency::from(9876), - ); +#[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)); +} - // Insert reserves that do not match the 1.23 price - TaoReserve::set_mock_reserve(netuid, TaoCurrency::from(1_000_000_000)); - AlphaReserve::set_mock_reserve(netuid, AlphaCurrency::from(4_000_000_000)); +#[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)); +} - // Run migration - migration(); +#[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 that values are removed from state - assert!(!deprecated_swap_maps::AlphaSqrtPrice::::contains_key( - netuid - )); - assert!(!deprecated_swap_maps::ScrapReservoirAlpha::::contains_key(netuid)); +#[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 that subnet price is still 1.23^2 - assert_abs_diff_eq!( - Swap::current_price(netuid).to_num::(), - 1.23 * 1.23, - epsilon = 0.1 +#[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 new file mode 100644 index 0000000000..5a57928a93 --- /dev/null +++ b/pallets/swap/src/position.rs @@ -0,0 +1,198 @@ +use core::marker::PhantomData; + +use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; +use frame_support::pallet_prelude::*; +use safe_math::*; +use substrate_fixed::types::{I64F64, 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; + +/// 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")] +#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)] +#[scale_info(skip_type_params(T))] +pub struct Position { + /// Unique ID of the 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, + /// Phantom marker for generic Config type + pub _phantom: PhantomData, +} + +impl Position { + pub fn new( + id: PositionId, + netuid: NetUid, + tick_low: TickIndex, + tick_high: TickIndex, + liquidity: u64, + ) -> Self { + let mut position = Position { + id, + netuid, + tick_low, + tick_high, + 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 + } + + /// Converts position to token amounts + /// + /// 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::(), + ) + }) + } + + /// 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)) + } +} + +#[freeze_struct("8501fa251c9d74c")] +#[derive( + Clone, + Copy, + Decode, + DecodeWithMemTracking, + Default, + Encode, + Eq, + MaxEncodedLen, + PartialEq, + RuntimeDebug, + TypeInfo, +)] +pub struct PositionId(u128); + +impl PositionId { + /// Create a new position ID + pub fn new() -> Self { + let new = LastPositionId::::get().saturating_add(1); + LastPositionId::::put(new); + + Self(new) + } +} + +impl From for PositionId { + fn from(value: u128) -> Self { + Self(value) + } +} + +impl From for u128 { + fn from(value: PositionId) -> Self { + value.0 + } +} diff --git a/pallets/swap/src/tick.rs b/pallets/swap/src/tick.rs new file mode 100644 index 0000000000..d3493fde45 --- /dev/null +++ b/pallets/swap/src/tick.rs @@ -0,0 +1,2198 @@ +//! 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/swap/src/weights.rs b/pallets/swap/src/weights.rs index 210bf1dc6d..2bbbb8dbdf 100644 --- a/pallets/swap/src/weights.rs +++ b/pallets/swap/src/weights.rs @@ -15,6 +15,10 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_subtensor_swap. pub trait WeightInfo { fn set_fee_rate() -> Weight; + fn add_liquidity() -> Weight; + fn remove_liquidity() -> Weight; + fn modify_position() -> Weight; + fn toggle_user_liquidity() -> Weight; } /// Default weights for pallet_subtensor_swap. @@ -26,6 +30,34 @@ impl WeightInfo for DefaultWeight { .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + + fn add_liquidity() -> Weight { + // Conservative weight estimate for add_liquidity + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + + fn remove_liquidity() -> Weight { + // Conservative weight estimate for remove_liquidity + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + + fn modify_position() -> Weight { + // Conservative weight estimate for modify_position + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)) + } + + fn toggle_user_liquidity() -> Weight { + // Conservative weight estimate: one read and one write + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } // For backwards compatibility and tests @@ -35,4 +67,28 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } + + fn add_liquidity() -> Weight { + Weight::from_parts(50_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(5)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + + fn remove_liquidity() -> Weight { + Weight::from_parts(50_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + + fn modify_position() -> Weight { + Weight::from_parts(50_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(4)) + } + + fn toggle_user_liquidity() -> Weight { + Weight::from_parts(10_000_000, 0) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) + } } diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index 21e72e8853..fc2a16a409 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -30,8 +30,8 @@ use subtensor_swap_interface::SwapHandler; use core::marker::PhantomData; use smallvec::smallvec; use sp_std::vec::Vec; -use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AuthorshipInfo, Balance, NetUid}; +use substrate_fixed::types::U96F32; +use subtensor_runtime_common::{Balance, Currency, NetUid}; // Tests #[cfg(test)] @@ -47,7 +47,7 @@ impl WeightToFeePolynomial for LinearWeightToFee { fn polynomial() -> WeightToFeeCoefficients { let coefficient = WeightToFeeCoefficient { coeff_integer: 0, - coeff_frac: Perbill::from_parts(500_000), // 0.5 unit per weight + coeff_frac: Perbill::from_parts(50_000), // 0.05 unit per weight negative: false, degree: 1, }; @@ -95,7 +95,6 @@ where T: frame_system::Config, T: pallet_subtensor::Config, T: pallet_balances::Config, - T: AuthorshipInfo>, { fn on_nonzero_unbalanced( imbalance: FungibleImbalance< @@ -104,18 +103,11 @@ where IncreaseIssuance, pallet_balances::Pallet>, >, ) { - if let Some(author) = T::author() { - // Pay block author instead of burning. - // One of these is the right call depending on your exact fungible API: - // let _ = pallet_balances::Pallet::::resolve(&author, imbalance); - // or: let _ = pallet_balances::Pallet::::deposit(&author, imbalance.peek(), Precision::BestEffort); - // - // Prefer "resolve" (moves the actual imbalance) if available: - let _ = as Balanced<_>>::resolve(&author, imbalance); - } else { - // Fallback: if no author, burn (or just drop). - drop(imbalance); - } + let ti_before = pallet_subtensor::TotalIssuance::::get(); + pallet_subtensor::TotalIssuance::::put( + ti_before.saturating_sub(imbalance.peek().into()), + ); + drop(imbalance); } } @@ -153,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 = U64F64::saturating_from_num( + let alpha_balance = U96F32::saturating_from_num( pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( hotkey, coldkey, *netuid, ), @@ -176,13 +168,13 @@ where alpha_vec.iter().for_each(|(hotkey, netuid)| { // Divide tao_amount evenly among all alpha entries - let alpha_balance = U64F64::saturating_from_num( + let alpha_balance = U96F32::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 = U64F64::saturating_from_num(tao_per_entry) + let alpha_fee = U96F32::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/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index e6452a0bcd..3bad4a275f 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -21,7 +21,7 @@ use sp_runtime::{ }; use sp_std::cmp::Ordering; use sp_weights::Weight; -pub use subtensor_runtime_common::{AlphaCurrency, AuthorshipInfo, Currency, NetUid, TaoCurrency}; +pub use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use subtensor_swap_interface::{Order, SwapHandler}; use crate::SubtensorTxFeeHandler; @@ -29,7 +29,10 @@ use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; pub const TAO: u64 = 1_000_000_000; -type Block = frame_system::mocking::MockBlock; +pub type Block = sp_runtime::generic::Block< + sp_runtime::generic::Header, + UncheckedExtrinsic, +>; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( @@ -136,22 +139,6 @@ impl pallet_transaction_payment::Config for Test { type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; } -pub struct MockAuthorshipProvider; - -pub const MOCK_BLOCK_BUILDER: u64 = 12345u64; - -impl AuthorshipInfo for MockAuthorshipProvider { - fn author() -> Option { - Some(U256::from(MOCK_BLOCK_BUILDER)) - } -} - -impl AuthorshipInfo for Test { - fn author() -> Option { - Some(U256::from(MOCK_BLOCK_BUILDER)) - } -} - parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -209,14 +196,17 @@ parameter_types! { pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. + // pub const InitialSubnetLimit: u16 = 10; // (DEPRECATED) pub const InitialNetworkRateLimit: u64 = 0; pub const InitialKeySwapCost: u64 = 1_000_000_000; pub const InitialAlphaHigh: u16 = 58982; // Represents 0.9 as per the production default pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: u64 = 50; - pub const InitialColdkeySwapReannouncementDelay: u64 = 10; + // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) + // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -285,8 +275,8 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type Yuma3On = InitialYuma3On; type Preimages = (); - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; @@ -300,7 +290,6 @@ impl pallet_subtensor::Config for Test { type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; type CommitmentsInterface = CommitmentsI; type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = MockAuthorshipProvider; } parameter_types! { @@ -401,6 +390,7 @@ impl pallet_balances::Config for Test { parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const SwapMaxFeeRate: u16 = 10000; // 15.26% + pub const SwapMaxPositions: u32 = 100; pub const SwapMinimumLiquidity: u64 = 1_000; pub const SwapMinimumReserve: NonZeroU64 = NonZeroU64::new(1_000_000).unwrap(); } @@ -412,6 +402,7 @@ impl pallet_subtensor_swap::Config for Test { type TaoReserve = pallet_subtensor::TaoCurrencyReserve; type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; type MinimumReserve = SwapMinimumReserve; type WeightInfo = (); @@ -531,12 +522,7 @@ where pallet_transaction_payment::ChargeTransactionPayment::::from(0), ); - Some(UncheckedExtrinsic::new_signed( - call, - nonce.into(), - (), - extra, - )) + Some(UncheckedExtrinsic::new_signed(call, nonce, (), extra)) } } @@ -643,38 +629,6 @@ pub(crate) fn swap_alpha_to_tao(netuid: NetUid, alpha: AlphaCurrency) -> (u64, u swap_alpha_to_tao_ext(netuid, alpha, false) } -pub(crate) fn swap_tao_to_alpha_ext( - netuid: NetUid, - tao: TaoCurrency, - drop_fees: bool, -) -> (u64, u64) { - if netuid.is_root() { - return (tao.into(), 0); - } - - let order = GetAlphaForTao::::with_amount(tao); - let result = ::SwapInterface::swap( - netuid.into(), - order, - ::SwapInterface::max_price(), - drop_fees, - true, - ); - - assert_ok!(&result); - - let result = result.unwrap(); - - // we don't want to have silent 0 comparisons in tests - assert!(!result.amount_paid_out.is_zero()); - - (result.amount_paid_out.to_u64(), result.fee_paid.to_u64()) -} - -pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (u64, u64) { - swap_tao_to_alpha_ext(netuid, tao, false) -} - #[allow(dead_code)] pub fn add_network(netuid: NetUid, tempo: u16) { SubtensorModule::init_new_network(netuid, tempo); diff --git a/pallets/transaction-fee/src/tests/mod.rs b/pallets/transaction-fee/src/tests/mod.rs index 80cfab5fd3..b6697e87f0 100644 --- a/pallets/transaction-fee/src/tests/mod.rs +++ b/pallets/transaction-fee/src/tests/mod.rs @@ -2,11 +2,12 @@ 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::*; @@ -443,9 +444,7 @@ fn test_remove_stake_edge_alpha() { assert_ok!(result); // Lower Alpha price to 0.0001 so that there is not enough alpha to cover tx fees - SubnetTAO::::insert(sn.subnets[0].netuid, TaoCurrency::from(1_000_000)); - SubnetAlphaIn::::insert(sn.subnets[0].netuid, AlphaCurrency::from(10_000_000_000)); - + 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(), @@ -1210,55 +1209,3 @@ fn test_recycle_alpha_fees_alpha() { assert!(actual_alpha_fee > 0.into()); }); } - -// cargo test --package subtensor-transaction-fee --lib -- tests::test_add_stake_fees_go_to_block_builder --exact --show-output -#[test] -fn test_add_stake_fees_go_to_block_builder() { - new_test_ext().execute_with(|| { - // Portion of swap fees that should go to the block builder - let block_builder_fee_portion = 3. / 5.; - - // Get the block builder balance - let block_builder = U256::from(MOCK_BLOCK_BUILDER); - let block_builder_balance_before = Balances::free_balance(block_builder); - - let stake_amount = TAO; - let sn = setup_subnets(1, 1); - - // Simulate add stake to get the expected TAO fee - let (_, swap_fee) = mock::swap_tao_to_alpha(sn.subnets[0].netuid, stake_amount.into()); - - SubtensorModule::add_balance_to_coldkey_account(&sn.coldkey, stake_amount * 10_u64); - remove_stake_rate_limit_for_tests(&sn.hotkeys[0], &sn.coldkey, sn.subnets[0].netuid); - - // Stake - let balance_before = Balances::free_balance(sn.coldkey); - let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::add_stake { - hotkey: sn.hotkeys[0], - netuid: sn.subnets[0].netuid, - amount_staked: stake_amount.into(), - }); - - // Dispatch the extrinsic with ChargeTransactionPayment extension - let info = call.get_dispatch_info(); - let ext = pallet_transaction_payment::ChargeTransactionPayment::::from(0); - assert_ok!(ext.dispatch_transaction( - RuntimeOrigin::signed(sn.coldkey).into(), - call, - &info, - 0, - 0, - )); - - let final_balance = Balances::free_balance(sn.coldkey); - let actual_tao_fee = balance_before - stake_amount - final_balance; - assert!(actual_tao_fee > 0); - - // Expect that block builder balance has increased by both the swap fee and the transaction fee - let expected_block_builder_swap_reward = swap_fee as f64 * block_builder_fee_portion; - let expected_tx_fee = 0.000136; // Use very low value for less test flakiness - let block_builder_balance_after = Balances::free_balance(block_builder); - let actual_reward = block_builder_balance_after - block_builder_balance_before; - assert!(actual_reward as f64 >= expected_block_builder_swap_reward + expected_tx_fee); - }); -} diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml index be1824cf91..400e1caa9f 100644 --- a/precompiles/Cargo.toml +++ b/precompiles/Cargo.toml @@ -38,7 +38,6 @@ pallet-subtensor-swap.workspace = true pallet-admin-utils.workspace = true subtensor-swap-interface.workspace = true pallet-crowdloan.workspace = true -pallet-shield.workspace = true [lints] workspace = true @@ -64,7 +63,6 @@ std = [ "pallet-subtensor-proxy/std", "pallet-subtensor-swap/std", "pallet-subtensor/std", - "pallet-shield/std", "precompile-utils/std", "scale-info/std", "sp-core/std", diff --git a/precompiles/src/alpha.rs b/precompiles/src/alpha.rs index 98737f1269..8dcea0e829 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::{U64F64, U96F32}; +use substrate_fixed::types::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(U64F64::from_num(1_000_000_000)); + let price = current_alpha_price.saturating_mul(U96F32::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: U64F64 = U64F64::from_num(0); + let mut sum_alpha_price: U96F32 = U96F32::from_num(0); for (netuid, _) in netuids { let price = as SwapHandler>::current_alpha_price( netuid.into(), ); - if price < U64F64::from_num(1) { + if price < U96F32::from_num(1) { sum_alpha_price = sum_alpha_price.saturating_add(price); } } - let price = sum_alpha_price.saturating_mul(U64F64::from_num(1_000_000_000)); + let price = sum_alpha_price.saturating_mul(U96F32::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/precompiles/src/balance_transfer.rs b/precompiles/src/balance_transfer.rs index d8d10970a3..c1cdab6ca5 100644 --- a/precompiles/src/balance_transfer.rs +++ b/precompiles/src/balance_transfer.rs @@ -10,7 +10,7 @@ use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, Uniqu use crate::{PrecompileExt, PrecompileHandleExt}; -pub struct BalanceTransferPrecompile(PhantomData); +pub(crate) struct BalanceTransferPrecompile(PhantomData); impl PrecompileExt for BalanceTransferPrecompile where @@ -18,8 +18,6 @@ where + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -28,9 +26,7 @@ where ::RuntimeCall: GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable, @@ -47,8 +43,6 @@ where + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -57,9 +51,7 @@ where ::RuntimeCall: GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable, diff --git a/precompiles/src/crowdloan.rs b/precompiles/src/crowdloan.rs index c9926dfe3b..f0971015d9 100644 --- a/precompiles/src/crowdloan.rs +++ b/precompiles/src/crowdloan.rs @@ -25,8 +25,6 @@ where + pallet_evm::Config + pallet_proxy::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -36,9 +34,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { const INDEX: u64 = 2057; @@ -53,8 +49,6 @@ where + pallet_evm::Config + pallet_proxy::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -64,9 +58,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::RuntimeCall: From> + GetDispatchInfo + Dispatchable, diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs index dbfe032cdf..1ea581bd35 100644 --- a/precompiles/src/ed25519.rs +++ b/precompiles/src/ed25519.rs @@ -8,7 +8,7 @@ use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; use crate::{PrecompileExt, parse_slice}; -pub struct Ed25519Verify(PhantomData); +pub(crate) struct Ed25519Verify(PhantomData); impl PrecompileExt for Ed25519Verify where diff --git a/precompiles/src/extensions.rs b/precompiles/src/extensions.rs index 51287a1b88..5d73a75f9b 100644 --- a/precompiles/src/extensions.rs +++ b/precompiles/src/extensions.rs @@ -10,7 +10,7 @@ use pallet_evm::{ AddressMapping, BalanceConverter, EvmBalance, ExitError, GasWeightMapping, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, }; -use pallet_subtensor::SubtensorTransactionExtension; +use pallet_subtensor::transaction_extension::SubtensorTransactionExtension; use precompile_utils::EvmResult; use scale_info::TypeInfo; use sp_core::{H160, U256, blake2_256}; @@ -58,8 +58,6 @@ pub(crate) trait PrecompileHandleExt: PrecompileHandle { + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + TypeInfo, @@ -67,9 +65,7 @@ pub(crate) trait PrecompileHandleExt: PrecompileHandle { ::RuntimeCall: GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::RuntimeOrigin: From> + AsSystemOriginSigner + Clone, { @@ -186,7 +182,7 @@ fn extension_error(err: TransactionValidityError) -> PrecompileFailure { impl PrecompileHandleExt for T where T: PrecompileHandle {} -pub trait PrecompileExt>: Precompile { +pub(crate) trait PrecompileExt>: Precompile { const INDEX: u64; // ss58 public key i.e., the contract sends funds it received to the destination address from diff --git a/precompiles/src/leasing.rs b/precompiles/src/leasing.rs index 732e687c6f..01a8db4354 100644 --- a/precompiles/src/leasing.rs +++ b/precompiles/src/leasing.rs @@ -26,8 +26,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_crowdloan::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -38,9 +36,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { const INDEX: u64 = 2058; @@ -54,8 +50,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_crowdloan::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -66,9 +60,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { #[precompile::public("getLease(uint32)")] diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index a824ac39d4..864119d89f 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -10,7 +10,6 @@ use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}, pallet_prelude::Decode, }; -use pallet_admin_utils::PrecompileEnum; use pallet_evm::{ AddressMapping, IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -25,24 +24,23 @@ use sp_core::{H160, U256, crypto::ByteArray}; use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup}; use subtensor_runtime_common::ProxyType; -use crate::extensions::*; +use pallet_admin_utils::PrecompileEnum; -pub use address_mapping::AddressMappingPrecompile; -pub use alpha::AlphaPrecompile; -pub use balance_transfer::BalanceTransferPrecompile; -pub use crowdloan::CrowdloanPrecompile; -pub use ed25519::Ed25519Verify; -pub use extensions::PrecompileExt; -pub use leasing::LeasingPrecompile; -pub use metagraph::MetagraphPrecompile; -pub use neuron::NeuronPrecompile; -pub use proxy::ProxyPrecompile; -pub use sr25519::Sr25519Verify; -pub use staking::{StakingPrecompile, StakingPrecompileV2}; -pub use storage_query::StorageQueryPrecompile; -pub use subnet::SubnetPrecompile; -pub use uid_lookup::UidLookupPrecompile; -pub use voting_power::VotingPowerPrecompile; +use crate::address_mapping::*; +use crate::alpha::*; +use crate::balance_transfer::*; +use crate::crowdloan::*; +use crate::ed25519::*; +use crate::extensions::*; +use crate::leasing::*; +use crate::metagraph::*; +use crate::neuron::*; +use crate::proxy::*; +use crate::sr25519::*; +use crate::staking::*; +use crate::storage_query::*; +use crate::subnet::*; +use crate::uid_lookup::*; mod address_mapping; mod alpha; @@ -59,7 +57,6 @@ mod staking; mod storage_query; mod subnet; mod uid_lookup; -mod voting_power; pub struct Precompiles(PhantomData); @@ -73,8 +70,6 @@ where + pallet_subtensor_swap::Config + pallet_proxy::Config + pallet_crowdloan::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -88,9 +83,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, ::Balance: TryFrom, <::Lookup as StaticLookup>::Source: From, @@ -110,8 +103,6 @@ where + pallet_subtensor_swap::Config + pallet_proxy::Config + pallet_crowdloan::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -125,9 +116,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, ::Balance: TryFrom, <::Lookup as StaticLookup>::Source: From, @@ -136,7 +125,7 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 27] { + pub fn used_addresses() -> [H160; 26] { [ hash(1), hash(2), @@ -162,7 +151,6 @@ where hash(AlphaPrecompile::::INDEX), hash(CrowdloanPrecompile::::INDEX), hash(LeasingPrecompile::::INDEX), - hash(VotingPowerPrecompile::::INDEX), hash(ProxyPrecompile::::INDEX), hash(AddressMappingPrecompile::::INDEX), ] @@ -178,8 +166,6 @@ where + pallet_subtensor_swap::Config + pallet_proxy::Config + pallet_crowdloan::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -194,8 +180,6 @@ where + Dispatchable + IsSubType> + IsSubType> - + IsSubType> - + IsSubType> + Decode, <::RuntimeCall as Dispatchable>::RuntimeOrigin: From>>, @@ -261,9 +245,6 @@ where a if a == hash(LeasingPrecompile::::INDEX) => { LeasingPrecompile::::try_execute::(handle, PrecompileEnum::Leasing) } - a if a == hash(VotingPowerPrecompile::::INDEX) => { - VotingPowerPrecompile::::try_execute::(handle, PrecompileEnum::VotingPower) - } a if a == hash(ProxyPrecompile::::INDEX) => { ProxyPrecompile::::try_execute::(handle, PrecompileEnum::Proxy) } diff --git a/precompiles/src/neuron.rs b/precompiles/src/neuron.rs index e2602c0246..0b998b3c07 100644 --- a/precompiles/src/neuron.rs +++ b/precompiles/src/neuron.rs @@ -19,8 +19,6 @@ where + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -30,9 +28,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { const INDEX: u64 = 2052; @@ -45,8 +41,6 @@ where + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -56,9 +50,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { #[precompile::public("setWeights(uint16,uint16[],uint16[],uint64)")] diff --git a/precompiles/src/proxy.rs b/precompiles/src/proxy.rs index 3312b67194..5139477f00 100644 --- a/precompiles/src/proxy.rs +++ b/precompiles/src/proxy.rs @@ -30,8 +30,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_proxy::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -43,9 +41,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, <::Lookup as StaticLookup>::Source: From, { @@ -60,8 +56,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_proxy::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -73,9 +67,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, <::Lookup as StaticLookup>::Source: From, { #[precompile::public("createPureProxy(uint8,uint32,uint16)")] diff --git a/precompiles/src/sr25519.rs b/precompiles/src/sr25519.rs index 054948d524..6c6245d8f0 100644 --- a/precompiles/src/sr25519.rs +++ b/precompiles/src/sr25519.rs @@ -9,7 +9,7 @@ use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure}; use crate::{PrecompileExt, parse_slice}; -pub struct Sr25519Verify(PhantomData); +pub(crate) struct Sr25519Verify(PhantomData); impl PrecompileExt for Sr25519Verify where diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index c276c32e60..9cc2a2aaa4 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -48,7 +48,7 @@ use crate::{PrecompileExt, PrecompileHandleExt}; // to stop supporting both precompiles. // // All the future extensions should happen in StakingPrecompileV2. -pub struct StakingPrecompileV2(PhantomData); +pub(crate) struct StakingPrecompileV2(PhantomData); impl PrecompileExt for StakingPrecompileV2 where @@ -57,8 +57,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_proxy::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -69,9 +67,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, <::Lookup as StaticLookup>::Source: From, { @@ -86,8 +82,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_proxy::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -98,9 +92,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, <::Lookup as StaticLookup>::Source: From, { @@ -450,7 +442,7 @@ where } // Deprecated, exists for backward compatibility. -pub struct StakingPrecompile(PhantomData); +pub(crate) struct StakingPrecompile(PhantomData); impl PrecompileExt for StakingPrecompile where @@ -459,8 +451,6 @@ where + pallet_subtensor::Config + pallet_proxy::Config + pallet_balances::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -472,9 +462,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, ::Balance: TryFrom, <::Lookup as StaticLookup>::Source: From, @@ -490,8 +478,6 @@ where + pallet_subtensor::Config + pallet_proxy::Config + pallet_balances::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -503,9 +489,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, ::Balance: TryFrom, <::Lookup as StaticLookup>::Source: From, diff --git a/precompiles/src/storage_query.rs b/precompiles/src/storage_query.rs index 796d1c8f04..3022fff36b 100644 --- a/precompiles/src/storage_query.rs +++ b/precompiles/src/storage_query.rs @@ -62,7 +62,7 @@ const AUTHORIZED_PREFIXES: [[u8; 16]; 10] = [ use crate::PrecompileExt; -pub struct StorageQueryPrecompile(PhantomData); +pub(crate) struct StorageQueryPrecompile(PhantomData); impl PrecompileExt for StorageQueryPrecompile where diff --git a/precompiles/src/subnet.rs b/precompiles/src/subnet.rs index 9d1e24cc1e..4df373c116 100644 --- a/precompiles/src/subnet.rs +++ b/precompiles/src/subnet.rs @@ -22,8 +22,6 @@ where + pallet_evm::Config + pallet_subtensor::Config + pallet_admin_utils::Config - + pallet_shield::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -34,9 +32,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { const INDEX: u64 = 2051; @@ -49,9 +45,7 @@ where + pallet_balances::Config + pallet_evm::Config + pallet_subtensor::Config - + pallet_shield::Config + pallet_admin_utils::Config - + pallet_subtensor_proxy::Config + Send + Sync + scale_info::TypeInfo, @@ -62,9 +56,7 @@ where + GetDispatchInfo + Dispatchable + IsSubType> - + IsSubType> - + IsSubType> - + IsSubType>, + + IsSubType>, ::AddressMapping: AddressMapping, { #[precompile::public("registerNetwork(bytes32)")] diff --git a/precompiles/src/uid_lookup.rs b/precompiles/src/uid_lookup.rs index b791b96786..be8c803b45 100644 --- a/precompiles/src/uid_lookup.rs +++ b/precompiles/src/uid_lookup.rs @@ -8,7 +8,7 @@ use sp_std::vec::Vec; use crate::PrecompileExt; -pub struct UidLookupPrecompile(PhantomData); +pub(crate) struct UidLookupPrecompile(PhantomData); impl PrecompileExt for UidLookupPrecompile where diff --git a/precompiles/src/voting_power.rs b/precompiles/src/voting_power.rs deleted file mode 100644 index 74e1731b6e..0000000000 --- a/precompiles/src/voting_power.rs +++ /dev/null @@ -1,131 +0,0 @@ -use core::marker::PhantomData; - -use fp_evm::PrecompileHandle; -use precompile_utils::EvmResult; -use sp_core::{ByteArray, H256, U256}; -use subtensor_runtime_common::NetUid; - -use crate::PrecompileExt; - -/// VotingPower precompile for smart contract access to validator voting power. -/// -/// This precompile allows smart contracts to query voting power for validators, -/// enabling on-chain governance decisions like slashing and spending. -pub struct VotingPowerPrecompile(PhantomData); - -impl PrecompileExt for VotingPowerPrecompile -where - R: frame_system::Config + pallet_subtensor::Config, - R::AccountId: From<[u8; 32]> + ByteArray, -{ - const INDEX: u64 = 2061; -} - -#[precompile_utils::precompile] -impl VotingPowerPrecompile -where - R: frame_system::Config + pallet_subtensor::Config, - R::AccountId: From<[u8; 32]>, -{ - /// Get voting power for a hotkey on a subnet. - /// - /// Returns the EMA of stake for the hotkey, which represents its voting power. - /// Returns 0 if: - /// - The hotkey has no voting power entry - /// - Voting power tracking is not enabled for the subnet - /// - The hotkey is not registered on the subnet - /// - /// # Arguments - /// * `netuid` - The subnet identifier (u16) - /// * `hotkey` - The hotkey account ID (bytes32) - /// - /// # Returns - /// * `u256` - The voting power value (in RAO, same precision as stake) - #[precompile::public("getVotingPower(uint16,bytes32)")] - #[precompile::view] - fn get_voting_power( - _: &mut impl PrecompileHandle, - netuid: u16, - hotkey: H256, - ) -> EvmResult { - let hotkey = R::AccountId::from(hotkey.0); - let voting_power = pallet_subtensor::VotingPower::::get(NetUid::from(netuid), &hotkey); - Ok(U256::from(voting_power)) - } - - /// Check if voting power tracking is enabled for a subnet. - /// - /// # Arguments - /// * `netuid` - The subnet identifier (u16) - /// - /// # Returns - /// * `bool` - True if voting power tracking is enabled - #[precompile::public("isVotingPowerTrackingEnabled(uint16)")] - #[precompile::view] - fn is_voting_power_tracking_enabled( - _: &mut impl PrecompileHandle, - netuid: u16, - ) -> EvmResult { - Ok(pallet_subtensor::VotingPowerTrackingEnabled::::get( - NetUid::from(netuid), - )) - } - - /// Get the block at which voting power tracking will be disabled. - /// - /// Returns 0 if not scheduled for disabling. - /// When non-zero, tracking continues until this block, then stops. - /// - /// # Arguments - /// * `netuid` - The subnet identifier (u16) - /// - /// # Returns - /// * `u64` - The block number at which tracking will be disabled (0 if not scheduled) - #[precompile::public("getVotingPowerDisableAtBlock(uint16)")] - #[precompile::view] - fn get_voting_power_disable_at_block( - _: &mut impl PrecompileHandle, - netuid: u16, - ) -> EvmResult { - Ok(pallet_subtensor::VotingPowerDisableAtBlock::::get( - NetUid::from(netuid), - )) - } - - /// Get the EMA alpha value for voting power calculation on a subnet. - /// - /// Alpha is stored with 18 decimal precision (1.0 = 10^18). - /// Higher alpha = faster response to stake changes. - /// - /// # Arguments - /// * `netuid` - The subnet identifier (u16) - /// - /// # Returns - /// * `u64` - The alpha value (with 18 decimal precision) - #[precompile::public("getVotingPowerEmaAlpha(uint16)")] - #[precompile::view] - fn get_voting_power_ema_alpha(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - Ok(pallet_subtensor::VotingPowerEmaAlpha::::get( - NetUid::from(netuid), - )) - } - - /// Get total voting power for a subnet. - /// - /// Returns the sum of all voting power for all validators on the subnet. - /// Useful for calculating voting thresholds (e.g., 51% quorum). - /// - /// # Arguments - /// * `netuid` - The subnet identifier (u16) - /// - /// # Returns - /// * `u256` - The total voting power across all validators - #[precompile::public("getTotalVotingPower(uint16)")] - #[precompile::view] - fn get_total_voting_power(_: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { - let total: u64 = pallet_subtensor::VotingPower::::iter_prefix(NetUid::from(netuid)) - .map(|(_, voting_power)| voting_power) - .fold(0u64, |acc, vp| acc.saturating_add(vp)); - Ok(U256::from(total)) - } -} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 95564977cf..b3aced2160 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -21,7 +21,6 @@ subtensor-custom-rpc-runtime-api.workspace = true smallvec.workspace = true log.workspace = true codec = { workspace = true, features = ["derive"] } -safe-math.workspace = true scale-info = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["alloc"] } pallet-aura = { workspace = true } @@ -159,7 +158,6 @@ ethereum.workspace = true frame-metadata.workspace = true sp-io.workspace = true sp-tracing.workspace = true -precompile-utils = { workspace = true, features = ["testing"] } [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true } @@ -200,7 +198,6 @@ std = [ "pallet-preimage/std", "pallet-commitments/std", "precompile-utils/std", - "safe-math/std", "sp-api/std", "sp-block-builder/std", "sp-core/std", diff --git a/runtime/src/base_call_filter.rs b/runtime/src/base_call_filter.rs deleted file mode 100644 index 8d76c422c1..0000000000 --- a/runtime/src/base_call_filter.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::RuntimeCall; -use crate::Vec; -use crate::pallet_proxy; -use crate::pallet_utility; -use frame_support::traits::Contains; -use sp_std::boxed::Box; -use sp_std::vec; -pub struct NoNestingCallFilter; - -impl Contains for NoNestingCallFilter { - fn contains(call: &RuntimeCall) -> bool { - let calls = match call { - RuntimeCall::Utility(inner) => { - let calls = match inner { - pallet_utility::Call::force_batch { calls } => calls, - pallet_utility::Call::batch { calls } => calls, - pallet_utility::Call::batch_all { calls } => calls, - _ => return true, - }; - - calls - .iter() - .map(|call| Box::new(call.clone())) - .collect::>() - } - RuntimeCall::Proxy(inner) => { - let call = match inner { - pallet_proxy::Call::proxy { call, .. } => call, - pallet_proxy::Call::proxy_announced { call, .. } => call, - _ => return true, - }; - - vec![call.clone()] - } - RuntimeCall::Multisig(inner) => { - let call = match inner { - pallet_multisig::Call::as_multi { call, .. } => call, - pallet_multisig::Call::as_multi_threshold_1 { call, .. } => call, - _ => return true, - }; - - vec![call.clone()] - } - RuntimeCall::Crowdloan(inner) => { - let call = match inner { - pallet_crowdloan::Call::create { - call: Some(call), .. - } => call, - _ => return true, - }; - - vec![call.clone()] - } - RuntimeCall::Scheduler(inner) => { - let call = match inner { - pallet_scheduler::Call::schedule { call, .. } => call, - pallet_scheduler::Call::schedule_after { call, .. } => call, - pallet_scheduler::Call::schedule_named { call, .. } => call, - pallet_scheduler::Call::schedule_named_after { call, .. } => call, - _ => return true, - }; - - vec![call.clone()] - } - _ => return true, - }; - - !calls.iter().any(|call| { - matches!(&**call, RuntimeCall::Utility(inner) if matches!(inner, pallet_utility::Call::force_batch { .. } | pallet_utility::Call::batch_all { .. } | pallet_utility::Call::batch { .. })) || - matches!(&**call, RuntimeCall::Proxy(inner) if matches!(inner, pallet_proxy::Call::proxy { .. } | pallet_proxy::Call::proxy_announced { .. })) || - matches!(&**call, RuntimeCall::Multisig(inner) if matches!(inner, pallet_multisig::Call::as_multi { .. } | pallet_multisig::Call::as_multi_threshold_1 { .. })) || - matches!(&**call, RuntimeCall::Crowdloan(inner) if matches!(inner, pallet_crowdloan::Call::create { .. } )) || - matches!(&**call, RuntimeCall::Scheduler(inner) if matches!(inner, pallet_scheduler::Call::schedule {..} | pallet_scheduler::Call::schedule_after { .. } | pallet_scheduler::Call::schedule_named {.. } | pallet_scheduler::Call::schedule_named_after { .. } )) || - matches!(&**call, RuntimeCall::Sudo(inner) if matches!(inner, pallet_sudo::Call::sudo {..} | pallet_sudo::Call::sudo_as { .. } | pallet_sudo::Call::sudo_unchecked_weight { .. } )) - }) - } -} - -pub struct SafeModeWhitelistedCalls; -impl Contains for SafeModeWhitelistedCalls { - fn contains(call: &RuntimeCall) -> bool { - matches!( - call, - RuntimeCall::Sudo(_) - | RuntimeCall::Multisig(_) - | RuntimeCall::System(_) - | RuntimeCall::SafeMode(_) - | RuntimeCall::Timestamp(_) - | RuntimeCall::SubtensorModule( - pallet_subtensor::Call::set_weights { .. } - | pallet_subtensor::Call::serve_axon { .. } - ) - | RuntimeCall::Commitments(pallet_commitments::Call::set_commitment { .. }) - ) - } -} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 460a84e049..919468c1ef 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -10,7 +10,6 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use core::num::NonZeroU64; -mod base_call_filter; pub mod check_nonce; mod migrations; pub mod sudo_wrapper; @@ -43,10 +42,9 @@ use pallet_subtensor::rpc_info::{ }; use pallet_subtensor::{CommitmentsInterface, ProxyInterface}; use pallet_subtensor_proxy as pallet_proxy; -use pallet_subtensor_swap_runtime_api::{SimSwapResult, SubnetPrice}; +use pallet_subtensor_swap_runtime_api::SimSwapResult; use pallet_subtensor_utility as pallet_utility; use runtime_common::prod_or_fast; -use safe_math::FixedExt; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_babe::BabeConfiguration; @@ -72,15 +70,11 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use substrate_fixed::types::U64F64; use subtensor_precompiles::Precompiles; -use subtensor_runtime_common::{AlphaCurrency, AuthorshipInfo, TaoCurrency, time::*, *}; +use subtensor_runtime_common::{AlphaCurrency, TaoCurrency, time::*, *}; use subtensor_swap_interface::{Order, SwapHandler}; // A few exports that help ease life for downstream crates. -use crate::base_call_filter::NoNestingCallFilter; -use crate::base_call_filter::SafeModeWhitelistedCalls; -use core::marker::PhantomData; pub use frame_support::{ StorageValue, construct_runtime, parameter_types, traits::{ @@ -100,12 +94,15 @@ pub use pallet_balances::Call as BalancesCall; use pallet_commitments::GetCommitments; pub use pallet_timestamp::Call as TimestampCall; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; -use scale_info::TypeInfo; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; use subtensor_transaction_fee::{SubtensorTxFeeHandler, TransactionFeeHandler}; +use core::marker::PhantomData; + +use scale_info::TypeInfo; + // Frontier use fp_rpc::TransactionStatus; use pallet_ethereum::{Call::transact, PostLogContent, Transaction as EthereumTransaction}; @@ -180,7 +177,8 @@ impl frame_system::offchain::CreateSignedTransaction pallet_transaction_payment::ChargeTransactionPayment::::from(0), ), SudoTransactionExtension::::new(), - pallet_subtensor::SubtensorTransactionExtension::::new(), + pallet_subtensor::transaction_extension::SubtensorTransactionExtension::::new( + ), pallet_drand::drand_priority::DrandPriority::::new(), frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); @@ -243,7 +241,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 380, + spec_version: 377, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -278,6 +276,28 @@ parameter_types! { pub const SS58Prefix: u8 = 42; } +pub struct NoNestingCallFilter; + +impl Contains for NoNestingCallFilter { + fn contains(call: &RuntimeCall) -> bool { + match call { + RuntimeCall::Utility(inner) => { + let calls = match inner { + pallet_utility::Call::force_batch { calls } => calls, + pallet_utility::Call::batch { calls } => calls, + pallet_utility::Call::batch_all { calls } => calls, + _ => &Vec::new(), + }; + + !calls.iter().any(|call| { + matches!(call, RuntimeCall::Utility(inner) if matches!(inner, pallet_utility::Call::force_batch { .. } | pallet_utility::Call::batch_all { .. } | pallet_utility::Call::batch { .. })) + }) + } + _ => true, + } + } +} + // Configure FRAME pallets to include in runtime. impl frame_system::Config for Runtime { @@ -411,6 +431,25 @@ parameter_types! { pub const DisallowPermissionlessRelease: Option = None; } +pub struct SafeModeWhitelistedCalls; +impl Contains for SafeModeWhitelistedCalls { + fn contains(call: &RuntimeCall) -> bool { + matches!( + call, + RuntimeCall::Sudo(_) + | RuntimeCall::Multisig(_) + | RuntimeCall::System(_) + | RuntimeCall::SafeMode(_) + | RuntimeCall::Timestamp(_) + | RuntimeCall::SubtensorModule( + pallet_subtensor::Call::set_weights { .. } + | pallet_subtensor::Call::serve_axon { .. } + ) + | RuntimeCall::Commitments(pallet_commitments::Call::set_commitment { .. }) + ) + } +} + impl pallet_safe_mode::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -452,34 +491,6 @@ impl pallet_balances::Config for Runtime { type DoneSlashHandler = (); } -// Implement AuthorshipInfo trait for Runtime to satisfy pallet transaction -// fee OnUnbalanced trait bounds -pub struct BlockAuthorFromAura(core::marker::PhantomData); - -impl> BlockAuthorFromAura { - pub fn get_block_author() -> Option { - let binding = frame_system::Pallet::::digest(); - let digest_logs = binding.logs(); - let author_index = F::find_author(digest_logs.iter().filter_map(|d| d.as_pre_runtime()))?; - let authority_id = pallet_aura::Authorities::::get() - .get(author_index as usize)? - .clone(); - Some(AccountId32::new(authority_id.to_raw_vec().try_into().ok()?)) - } -} - -impl AuthorshipInfo for Runtime { - fn author() -> Option { - BlockAuthorFromAura::::get_block_author() - } -} - -impl> AuthorshipInfo for BlockAuthorFromAura { - fn author() -> Option { - Self::get_block_author() - } -} - parameter_types! { pub const OperationalFeeMultiplier: u8 = 5; pub FeeMultiplier: Multiplier = Multiplier::one(); @@ -990,7 +1001,7 @@ parameter_types! { pub const SubtensorInitialRho: u16 = 10; pub const SubtensorInitialAlphaSigmoidSteepness: i16 = 1000; pub const SubtensorInitialKappa: u16 = 32_767; // 0.5 = 65535/2 - pub const SubtensorInitialMaxAllowedUids: u16 = 256; + pub const SubtensorInitialMaxAllowedUids: u16 = 4096; pub const SubtensorInitialIssuance: u64 = 0; pub const SubtensorInitialMinAllowedWeights: u16 = 1024; pub const SubtensorInitialEmissionValue: u16 = 0; @@ -1032,6 +1043,7 @@ parameter_types! { pub const SubtensorInitialMinAllowedUids: u16 = 64; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent + // pub const SubtensorInitialSubnetLimit: u16 = 12; // (DEPRECATED) pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 7200; pub const SubtensorInitialKeySwapCost: u64 = 100_000_000; // 0.1 TAO @@ -1039,8 +1051,9 @@ parameter_types! { pub const InitialAlphaLow: u16 = 45875; // Represents 0.7 as per the production default pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialYuma3On: bool = false; // Default value for Yuma3On - pub const InitialColdkeySwapAnnouncementDelay: BlockNumber = prod_or_fast!(5 * 24 * 60 * 60 / 12, 50); // 5 days - pub const InitialColdkeySwapReannouncementDelay: BlockNumber = prod_or_fast!(24 * 60 * 60 / 12, 10); // 1 day + // pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) + pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialColdkeySwapRescheduleDuration: BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. pub const InitialEmaPriceHalvingPeriod: u64 = 201_600_u64; // 4 weeks @@ -1111,8 +1124,8 @@ impl pallet_subtensor::Config for Runtime { type Yuma3On = InitialYuma3On; type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; - type InitialColdkeySwapAnnouncementDelay = InitialColdkeySwapAnnouncementDelay; - type InitialColdkeySwapReannouncementDelay = InitialColdkeySwapReannouncementDelay; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type InitialStartCallDelay = InitialStartCallDelay; @@ -1125,12 +1138,12 @@ impl pallet_subtensor::Config for Runtime { type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; type CommitmentsInterface = CommitmentsI; type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit; - type AuthorshipProvider = BlockAuthorFromAura; } parameter_types! { pub const SwapProtocolId: PalletId = PalletId(*b"ten/swap"); pub const SwapMaxFeeRate: u16 = 10000; // 15.26% + pub const SwapMaxPositions: u32 = 100; pub const SwapMinimumLiquidity: u64 = 1_000; pub const SwapMinimumReserve: NonZeroU64 = unsafe { NonZeroU64::new_unchecked(1_000_000) }; } @@ -1142,6 +1155,7 @@ impl pallet_subtensor_swap::Config for Runtime { type TaoReserve = pallet_subtensor::TaoCurrencyReserve; type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; + type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; type MinimumReserve = SwapMinimumReserve; // TODO: set measured weights when the pallet been benchmarked and the type is generated @@ -1604,7 +1618,7 @@ pub type TransactionExtensions = ( frame_system::CheckWeight, ChargeTransactionPaymentWrapper, SudoTransactionExtension, - pallet_subtensor::SubtensorTransactionExtension, + pallet_subtensor::transaction_extension::SubtensorTransactionExtension, pallet_drand::drand_priority::DrandPriority, frame_metadata_hash_extension::CheckMetadataHash, ); @@ -2486,28 +2500,15 @@ impl_runtime_apis! { impl pallet_subtensor_swap_runtime_api::SwapRuntimeApi for Runtime { fn current_alpha_price(netuid: NetUid) -> u64 { + use substrate_fixed::types::U96F32; + pallet_subtensor_swap::Pallet::::current_price(netuid.into()) - .saturating_mul(U64F64::from_num(1_000_000_000)) + .saturating_mul(U96F32::from_num(1_000_000_000)) .saturating_to_num() } - fn current_alpha_price_all() -> Vec { - pallet_subtensor::Pallet::::get_all_subnet_netuids() - .into_iter() - .map(|netuid| { - SubnetPrice { - netuid, - price: Self::current_alpha_price(netuid), - } - }) - .collect() - } - fn sim_swap_tao_for_alpha(netuid: NetUid, tao: TaoCurrency) -> SimSwapResult { - let price = pallet_subtensor_swap::Pallet::::current_price(netuid.into()); - let no_slippage_alpha = U64F64::saturating_from_num(u64::from(tao)).safe_div(price).saturating_to_num::(); let order = pallet_subtensor::GetAlphaForTao::::with_amount(tao); - // fee_to_block_author is included in sr.fee_paid, so it is absent in this calculation pallet_subtensor_swap::Pallet::::sim_swap( netuid.into(), order, @@ -2518,25 +2519,18 @@ impl_runtime_apis! { alpha_amount: 0.into(), tao_fee: 0.into(), alpha_fee: 0.into(), - tao_slippage: 0.into(), - alpha_slippage: 0.into(), }, |sr| SimSwapResult { tao_amount: sr.amount_paid_in.into(), alpha_amount: sr.amount_paid_out.into(), tao_fee: sr.fee_paid.into(), alpha_fee: 0.into(), - tao_slippage: 0.into(), - alpha_slippage: no_slippage_alpha.saturating_sub(sr.amount_paid_out.into()).into(), }, ) } fn sim_swap_alpha_for_tao(netuid: NetUid, alpha: AlphaCurrency) -> SimSwapResult { - let price = pallet_subtensor_swap::Pallet::::current_price(netuid.into()); - let no_slippage_tao = U64F64::saturating_from_num(u64::from(alpha)).saturating_mul(price).saturating_to_num::(); let order = pallet_subtensor::GetTaoForAlpha::::with_amount(alpha); - // fee_to_block_author is included in sr.fee_paid, so it is absent in this calculation pallet_subtensor_swap::Pallet::::sim_swap( netuid.into(), order, @@ -2547,16 +2541,12 @@ impl_runtime_apis! { alpha_amount: 0.into(), tao_fee: 0.into(), alpha_fee: 0.into(), - tao_slippage: 0.into(), - alpha_slippage: 0.into(), }, |sr| SimSwapResult { tao_amount: sr.amount_paid_out.into(), alpha_amount: sr.amount_paid_in.into(), tao_fee: 0.into(), alpha_fee: sr.fee_paid.into(), - tao_slippage: no_slippage_tao.saturating_sub(sr.amount_paid_out.into()).into(), - alpha_slippage: 0.into(), }, ) } diff --git a/runtime/tests/precompiles.rs b/runtime/tests/precompiles.rs deleted file mode 100644 index 5565aa8f7f..0000000000 --- a/runtime/tests/precompiles.rs +++ /dev/null @@ -1,219 +0,0 @@ -#![allow(clippy::unwrap_used)] -#![allow(clippy::expect_used)] - -use core::iter::IntoIterator; -use std::collections::BTreeSet; - -use fp_evm::{Context, ExitError, PrecompileFailure, PrecompileResult}; -use node_subtensor_runtime::{BuildStorage, Runtime, RuntimeGenesisConfig, System}; -use pallet_evm::{AddressMapping, BalanceConverter, PrecompileSet}; -use precompile_utils::testing::{MockHandle, PrecompileTesterExt}; -use sp_core::{H160, H256, U256}; -use sp_runtime::traits::Hash; -use subtensor_precompiles::{ - AddressMappingPrecompile, BalanceTransferPrecompile, PrecompileExt, Precompiles, -}; - -type AccountId = ::AccountId; - -fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig::default() - .build_storage() - .unwrap() - .into(); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -fn execute_precompile( - precompiles: &Precompiles, - precompile_address: H160, - caller: H160, - input: Vec, - apparent_value: U256, -) -> Option { - let mut handle = MockHandle::new( - precompile_address, - Context { - address: precompile_address, - caller, - apparent_value, - }, - ); - handle.input = input; - precompiles.execute(&mut handle) -} - -fn evm_apparent_value_from_substrate(amount: u64) -> U256 { - ::BalanceConverter::into_evm_balance(amount.into()) - .expect("runtime balance conversion should work for test amount") - .into() -} - -fn addr_from_index(index: u64) -> H160 { - H160::from_low_u64_be(index) -} - -#[test] -fn precompile_registry_addresses_are_unique() { - new_test_ext().execute_with(|| { - let addresses = Precompiles::::used_addresses(); - let unique: BTreeSet<_> = IntoIterator::into_iter(addresses).collect(); - assert_eq!(unique.len(), addresses.len()); - }); -} - -mod address_mapping { - use super::*; - - fn address_mapping_call_data(target: H160) -> Vec { - // Solidity selector for addressMapping(address). - let selector = sp_io::hashing::keccak_256(b"addressMapping(address)"); - let mut input = Vec::with_capacity(4 + 32); - // First 4 bytes of keccak256(function_signature): ABI function selector. - input.extend_from_slice(&selector[..4]); - // Left-pad the 20-byte address argument to a 32-byte ABI word. - input.extend_from_slice(&[0u8; 12]); - // The 20-byte address payload (right-aligned in the 32-byte ABI word). - input.extend_from_slice(target.as_bytes()); - input - } - - #[test] - fn address_mapping_precompile_returns_runtime_address_mapping() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - - let caller = addr_from_index(1); - let target_address = addr_from_index(0x1234); - let input = address_mapping_call_data(target_address); - - let mapped_account = - ::AddressMapping::into_account_id(target_address); - let expected_output: [u8; 32] = mapped_account.into(); - - let precompile_addr = addr_from_index(AddressMappingPrecompile::::INDEX); - precompiles - .prepare_test(caller, precompile_addr, input) - .with_static_call(true) - .execute_returns_raw(expected_output.to_vec()); - }); - } -} - -mod balance_transfer { - use super::*; - - fn balance_transfer_call_data(target: H256) -> Vec { - // Solidity selector for transfer(bytes32). - let selector = sp_io::hashing::keccak_256(b"transfer(bytes32)"); - let mut input = Vec::with_capacity(4 + 32); - input.extend_from_slice(&selector[..4]); - input.extend_from_slice(target.as_bytes()); - input - } - - #[test] - fn balance_transfer_precompile_transfers_balance() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); - let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); - let destination_raw = H256::repeat_byte(7); - let destination_account: AccountId = destination_raw.0.into(); - - let amount = 123_456; - pallet_subtensor::Pallet::::add_balance_to_coldkey_account( - &dispatch_account, - (amount * 2).into(), - ); - - let source_balance_before = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_before = - pallet_balances::Pallet::::free_balance(&destination_account); - - let result = execute_precompile( - &precompiles, - precompile_addr, - addr_from_index(1), - balance_transfer_call_data(destination_raw), - evm_apparent_value_from_substrate(amount), - ); - let precompile_result = - result.expect("expected precompile transfer call to be routed to a precompile"); - precompile_result.expect("expected successful precompile transfer dispatch"); - - let source_balance_after = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_after = - pallet_balances::Pallet::::free_balance(&destination_account); - - assert_eq!(source_balance_after, source_balance_before - amount); - assert_eq!( - destination_balance_after, - destination_balance_before + amount - ); - }); - } - - #[test] - fn balance_transfer_precompile_respects_subtensor_extension_policy() { - new_test_ext().execute_with(|| { - let precompiles = Precompiles::::new(); - let precompile_addr = addr_from_index(BalanceTransferPrecompile::::INDEX); - let dispatch_account: AccountId = BalanceTransferPrecompile::::account_id(); - let destination_raw = H256::repeat_byte(8); - let destination_account: AccountId = destination_raw.0.into(); - - let amount = 100; - pallet_subtensor::Pallet::::add_balance_to_coldkey_account( - &dispatch_account, - 1_000_000_u64.into(), - ); - - // Activate coldkey-swap guard for precompile dispatch account. - let replacement_coldkey = AccountId::from([9u8; 32]); - let replacement_hash = - ::Hashing::hash_of(&replacement_coldkey); - pallet_subtensor::ColdkeySwapAnnouncements::::insert( - &dispatch_account, - (System::block_number(), replacement_hash), - ); - - let source_balance_before = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_before = - pallet_balances::Pallet::::free_balance(&destination_account); - - let result = execute_precompile( - &precompiles, - precompile_addr, - addr_from_index(1), - balance_transfer_call_data(destination_raw), - evm_apparent_value_from_substrate(amount), - ); - let precompile_result = - result.expect("expected precompile transfer call to be routed to a precompile"); - let failure = precompile_result - .expect_err("expected transaction extension rejection on precompile dispatch"); - let message = match failure { - PrecompileFailure::Error { - exit_status: ExitError::Other(message), - } => message, - other => panic!("unexpected precompile failure: {other:?}"), - }; - assert!( - message.contains("transaction extension rejected"), - "unexpected precompile failure: {message}" - ); - - let source_balance_after = - pallet_balances::Pallet::::free_balance(&dispatch_account); - let destination_balance_after = - pallet_balances::Pallet::::free_balance(&destination_account); - assert_eq!(source_balance_after, source_balance_before); - assert_eq!(destination_balance_after, destination_balance_before); - }); - } -} diff --git a/scripts/update-deps-to-path.sh b/scripts/update-deps-to-path.sh deleted file mode 100755 index a1eab9b99c..0000000000 --- a/scripts/update-deps-to-path.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/bin/zsh -# update-deps-to-path.sh goal is to replace git dependencies with path dependencies -# in the target Cargo.toml file. -# -# The script will scan the source repos for packages and build a mapping of package name to path. -# It will then process the target Cargo.toml file line by line and replace git dependencies with path dependencies. -# -# The script will output the new Cargo.toml file to stdout. -# -# The script will exit with a non-zero status if the target Cargo.toml file is not found. -# Usage: ./scripts/update-deps-to-path.sh ./Cargo.toml ./polkadot-sdk-otf ../frontier-otf > Cargo.toml.new - -set -e - -TARGET_TOML="${1:?Usage: $0 [source-repo-2] ...}" -shift - -if [[ $# -eq 0 ]]; then - echo "Error: At least one source repo path required" >&2 - exit 1 -fi - -# Build package name -> path mapping from all source repos -typeset -A PKG_PATHS - -for SOURCE_PATH in "$@"; do - SOURCE_PATH="$(cd "$SOURCE_PATH" && pwd)" - echo "Scanning $SOURCE_PATH for packages..." >&2 - - for cargo_toml in $(find "$SOURCE_PATH" -name "Cargo.toml" -type f 2>/dev/null); do - pkg_name=$(yq -p toml -o yaml '.package.name // ""' "$cargo_toml" 2>/dev/null | tr -d '"') - - if [[ -n "$pkg_name" && "$pkg_name" != "null" ]]; then - pkg_dir="$(dirname "$cargo_toml")" - PKG_PATHS[$pkg_name]="$pkg_dir" - echo " Found: $pkg_name" >&2 - fi - done -done - -echo "Found ${#PKG_PATHS[@]} total packages" >&2 -echo "" >&2 - -# Process target Cargo.toml line by line -echo "Updating dependencies in $TARGET_TOML..." >&2 - -while IFS= read -r line; do - # Check if this line has a git dependency - if [[ "$line" =~ ^([a-zA-Z0-9_-]+|\"[^\"]+\")\ *=\ *\{.*git\ *=\ *\" ]]; then - # Extract package name (handle both quoted and unquoted) - dep_name=$(echo "$line" | sed -E 's/^"?([a-zA-Z0-9_-]+)"? *=.*/\1/') - - # Check for package alias - if [[ "$line" =~ package\ *=\ *\"([^\"]+)\" ]]; then - lookup_name="${match[1]}" - else - lookup_name="$dep_name" - fi - - # Check if we have this package - if [[ -n "${PKG_PATHS[$lookup_name]}" ]]; then - pkg_path="${PKG_PATHS[$lookup_name]}" - echo " $dep_name -> $pkg_path" >&2 - - # Extract features/default-features/package if present - extras="" - if [[ "$line" =~ default-features\ *=\ *false ]]; then - extras="$extras, default-features = false" - fi - if [[ "$line" =~ package\ *=\ *\"([^\"]+)\" ]]; then - extras="$extras, package = \"${match[1]}\"" - fi - if [[ "$line" =~ features\ *=\ *\[([^\]]*)\] ]]; then - extras="$extras, features = [${match[1]}]" - fi - - # Output new line with just path - echo "${dep_name} = { path = \"${pkg_path}\"${extras} }" - else - # Package not found in sources, keep original - echo "$line" - fi - else - # Not a git dependency, keep as-is - echo "$line" - fi -done < "$TARGET_TOML" - -echo "" >&2 -echo "Done!" >&2