From c179c8e23550c4da447d284c6d22458c173ee9a7 Mon Sep 17 00:00:00 2001 From: opendansor Date: Fri, 7 Jun 2024 13:25:16 -0700 Subject: [PATCH 001/269] Update localnet.sh to include a flag for fast_blocks for E2E testing --- scripts/localnet.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/localnet.sh b/scripts/localnet.sh index ab564871b..65ca5c0a9 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -6,9 +6,24 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" # The base directory of the subtensor project BASE_DIR="$SCRIPT_DIR/.." -: "${CHAIN:=local}" -: "${BUILD_BINARY:=1}" -: "${FEATURES:="pow-faucet runtime-benchmarks fast-blocks"}" +# get parameters +# Get the value of fast_blocks from the first argument +fast_blocks=${1:-"True"} + +# Check the value of fast_blocks +if [ "$fast_blocks" == "False" ]; then + # Block of code to execute if fast_blocks is False + echo "fast_blocks is Off" + : "${CHAIN:=local}" + : "${BUILD_BINARY:=1}" + : "${FEATURES:="pow-faucet runtime-benchmarks"}" +else + # Block of code to execute if fast_blocks is not False + echo "fast_blocks is On" + : "${CHAIN:=local}" + : "${BUILD_BINARY:=1}" + : "${FEATURES:="pow-faucet runtime-benchmarks fast-blocks"}" +fi SPEC_PATH="${SCRIPT_DIR}/specs/" FULL_PATH="$SPEC_PATH$CHAIN.json" From 9a179bb2ceea0d0f00f8d50473c68120c3324cc5 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Jul 2024 11:01:05 -0400 Subject: [PATCH 002/269] feature-gate hash --- runtime/build.rs | 11 ++++++++++- scripts/build.sh | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/runtime/build.rs b/runtime/build.rs index 8f021e838..4d65d12a7 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -1,5 +1,5 @@ fn main() { - #[cfg(feature = "std")] + #[cfg(all(feature = "std", not(feature = "metadata-hash")))] { substrate_wasm_builder::WasmBuilder::new() .with_current_project() @@ -7,4 +7,13 @@ fn main() { .import_memory() .build(); } + #[cfg(all(feature = "std", feature = "metadata-hash"))] + { + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .export_heap_base() + .import_memory() + .enable_metadata_hash("TAO", 9) + .build(); + } } diff --git a/scripts/build.sh b/scripts/build.sh index 548af664b..3f588a1cc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -1,2 +1,2 @@ -cargo build --profile production --features runtime-benchmarks +cargo build --profile production --features "runtime-benchmarks metadata-hash" From 5039e5bdb83e2f677e46773d9da3335a0298a98e Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Jul 2024 11:45:49 -0400 Subject: [PATCH 003/269] use 1.10.0-rc2 --- Cargo.toml | 139 +++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d9eff122..8bfb9d2b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,80 +39,81 @@ litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } subtensor-macros = { path = "support/macros" } -frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" , default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } -sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } substrate-fixed = { git = "https://github.com/encointer/substrate-fixed.git", tag = "v0.5.9" } -substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } -substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.10.0" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } frame-metadata = "16" [profile.release] From 322752b502eb428e99c23a09b63b47b21b5934a9 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Jul 2024 11:46:09 -0400 Subject: [PATCH 004/269] add feature --- runtime/Cargo.toml | 3 +++ runtime/src/lib.rs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index fcb02a24c..042d0337c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -41,6 +41,7 @@ pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-utility = { workspace = true } frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -111,6 +112,7 @@ std = [ "codec/std", "scale-info/std", "frame-executive/std", + "frame-metadata-hash-extension/std", "frame-support/std", "frame-system-rpc-runtime-api/std", "frame-system/std", @@ -204,3 +206,4 @@ try-runtime = [ "pallet-commitments/try-runtime", "pallet-registry/try-runtime" ] +metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index de8be6e61..3457ecb5f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -23,6 +23,7 @@ use pallet_commitments::CanCommit; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; +use frame_metadata_hash_extension::CheckMetadataHash; use pallet_registry::CanRegisterIdentity; use scale_info::TypeInfo; use smallvec::smallvec; @@ -1283,6 +1284,7 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, pallet_subtensor::SubtensorSignedExtension, pallet_commitments::CommitmentsSignedExtension, + frame_metadata_hash_extension::CheckMetadataHash, ); type Migrations = pallet_grandpa::migrations::MigrateV4ToV5; From 78a71f904e94380091edd4790dcc62219949d1e2 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Jul 2024 12:25:33 -0400 Subject: [PATCH 005/269] lock file update --- Cargo.lock | 484 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 276 insertions(+), 208 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 868e85b89..ceeb1098e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2308,7 +2308,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", ] @@ -2331,7 +2331,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-support-procedural", @@ -2347,16 +2347,16 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "static_assertions", ] [[package]] name = "frame-benchmarking-cli" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "Inflector", "array-bytes 6.2.3", @@ -2388,15 +2388,15 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", "thousands", ] @@ -2404,7 +2404,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "aquamarine 0.3.3", "frame-support", @@ -2416,8 +2416,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -2432,10 +2432,25 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-metadata-hash-extension" +version = "0.1.0" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +dependencies = [ + "array-bytes 6.2.3", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + [[package]] name = "frame-support" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "aquamarine 0.5.0", "array-bytes 6.2.3", @@ -2458,7 +2473,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-crypto-hashing-proc-macro", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -2466,8 +2481,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-weights", "static_assertions", "tt-call", @@ -2476,7 +2491,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "Inflector", "cfg-expr", @@ -2495,7 +2510,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.1.0", @@ -2507,7 +2522,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "proc-macro2", "quote", @@ -2517,7 +2532,7 @@ dependencies = [ [[package]] name = "frame-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "cfg-if", "docify", @@ -2529,7 +2544,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-version", "sp-weights", ] @@ -2537,7 +2552,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -2546,13 +2561,13 @@ dependencies = [ "scale-info", "sp-core", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "frame-system-rpc-runtime-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "sp-api", @@ -2561,13 +2576,13 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "parity-scale-codec", "sp-api", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -4334,6 +4349,20 @@ dependencies = [ "hash-db", ] +[[package]] +name = "merkleized-metadata" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" +dependencies = [ + "array-bytes 6.2.3", + "blake3", + "frame-metadata", + "parity-scale-codec", + "scale-decode", + "scale-info", +] + [[package]] name = "merlin" version = "3.0.0" @@ -4699,6 +4728,7 @@ dependencies = [ "frame-benchmarking", "frame-executive", "frame-metadata", + "frame-metadata-hash-extension", "frame-support", "frame-system", "frame-system-benchmarking", @@ -4739,9 +4769,9 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -4952,7 +4982,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-weights", "substrate-fixed", "subtensor-macros", @@ -4961,7 +4991,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-system", @@ -4972,13 +5002,13 @@ dependencies = [ "sp-application-crypto", "sp-consensus-aura", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-authorship" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-system", @@ -4986,13 +5016,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-balances" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "frame-benchmarking", @@ -5002,7 +5032,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -5018,7 +5048,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "subtensor-macros", ] @@ -5036,14 +5066,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "subtensor-macros", ] [[package]] name = "pallet-grandpa" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5060,13 +5090,13 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-system", @@ -5074,13 +5104,13 @@ dependencies = [ "safe-mix", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-membership" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5091,13 +5121,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-multisig" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5107,13 +5137,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-preimage" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5124,13 +5154,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-proxy" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5139,7 +5169,7 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -5155,14 +5185,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "subtensor-macros", ] [[package]] name = "pallet-safe-mode" version = "9.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "frame-benchmarking", @@ -5175,13 +5205,13 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-scheduler" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "frame-benchmarking", @@ -5192,14 +5222,14 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-weights", ] [[package]] name = "pallet-session" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-system", @@ -5214,7 +5244,7 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", ] @@ -5246,8 +5276,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-version", "substrate-fixed", "subtensor-macros", @@ -5256,7 +5286,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "frame-benchmarking", @@ -5266,13 +5296,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-timestamp" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "frame-benchmarking", @@ -5284,15 +5314,15 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-timestamp", ] [[package]] name = "pallet-transaction-payment" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-support", "frame-system", @@ -5302,13 +5332,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5324,7 +5354,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5336,7 +5366,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-benchmarking", "frame-support", @@ -5346,7 +5376,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -6684,18 +6714,18 @@ dependencies = [ [[package]] name = "sc-allocator" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "log", "sp-core", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", ] [[package]] name = "sc-basic-authorship" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "futures", "futures-timer", @@ -6717,7 +6747,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "sp-api", @@ -6732,7 +6762,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "docify", @@ -6758,7 +6788,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -6769,7 +6799,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "chrono", @@ -6810,7 +6840,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "fnv", "futures", @@ -6825,11 +6855,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", "substrate-prometheus-endpoint", ] @@ -6837,7 +6867,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "hash-db", "kvdb", @@ -6863,7 +6893,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -6888,7 +6918,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -6917,7 +6947,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "ahash 0.8.11", "array-bytes 6.2.3", @@ -6960,7 +6990,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "finality-grandpa", "futures", @@ -6980,7 +7010,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -7003,7 +7033,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -7013,25 +7043,25 @@ dependencies = [ "schnellru", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-io", "sp-panic-handler", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", "sp-version", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "tracing", ] [[package]] name = "sc-executor-common" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", "wasm-instrument", ] @@ -7039,18 +7069,18 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "log", "polkavm", "sc-executor-common", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "sc-executor-wasmtime" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "anyhow", "cfg-if", @@ -7060,15 +7090,15 @@ dependencies = [ "rustix 0.36.17", "sc-allocator", "sc-executor-common", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "wasmtime", ] [[package]] name = "sc-informant" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "ansi_term", "futures", @@ -7085,7 +7115,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "parking_lot 0.12.3", @@ -7099,7 +7129,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 4.2.0", "arrayvec", @@ -7128,7 +7158,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7171,7 +7201,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-channel", "cid", @@ -7191,7 +7221,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -7208,7 +7238,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "ahash 0.8.11", "futures", @@ -7227,7 +7257,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7248,7 +7278,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7284,7 +7314,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7303,7 +7333,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "bytes", @@ -7326,7 +7356,7 @@ dependencies = [ "sc-utils", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-keystore", "sp-offchain", "sp-runtime", @@ -7337,7 +7367,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -7346,7 +7376,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "futures", "jsonrpsee", @@ -7378,7 +7408,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -7398,7 +7428,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "futures", "governor", @@ -7416,7 +7446,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7447,7 +7477,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "directories", @@ -7489,12 +7519,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -7511,7 +7541,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.30.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "log", "parity-scale-codec", @@ -7522,7 +7552,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "derive_more", "futures", @@ -7537,13 +7567,13 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "sc-telemetry" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "chrono", "futures", @@ -7562,7 +7592,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "ansi_term", "chrono", @@ -7582,7 +7612,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", "tracing", "tracing-log 0.1.4", @@ -7592,7 +7622,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -7603,7 +7633,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -7621,7 +7651,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-transaction-pool", "substrate-prometheus-endpoint", "thiserror", @@ -7630,7 +7660,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -7646,7 +7676,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-channel", "futures", @@ -7658,6 +7688,29 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "scale-bits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57b1e7f6b65ed1f04e79a85a57d755ad56d76fdf1e9bddcc9ae14f71fcdcf54" +dependencies = [ + "parity-scale-codec", + "scale-type-resolver", +] + +[[package]] +name = "scale-decode" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e98f3262c250d90e700bb802eb704e1f841e03331c2eb815e46516c4edbf5b27" +dependencies = [ + "derive_more", + "parity-scale-codec", + "scale-bits", + "scale-type-resolver", + "smallvec", +] + [[package]] name = "scale-info" version = "2.11.3" @@ -7684,6 +7737,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scale-type-resolver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0cded6518aa0bd6c1be2b88ac81bf7044992f0f154bfbabd5ad34f43512abcb" + [[package]] name = "schannel" version = "0.1.23" @@ -8129,7 +8188,7 @@ dependencies = [ [[package]] name = "sp-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "hash-db", "log", @@ -8137,12 +8196,12 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-metadata-ir", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", "sp-version", "thiserror", @@ -8151,7 +8210,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "Inflector", "blake2 0.10.6", @@ -8165,20 +8224,20 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "scale-info", "serde", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "sp-arithmetic" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "integer-sqrt", @@ -8186,7 +8245,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "static_assertions", ] @@ -8211,7 +8270,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "sp-api", "sp-inherents", @@ -8221,7 +8280,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "futures", "log", @@ -8239,7 +8298,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "futures", @@ -8254,7 +8313,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "parity-scale-codec", @@ -8270,7 +8329,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "finality-grandpa", "log", @@ -8287,7 +8346,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "scale-info", @@ -8298,7 +8357,7 @@ dependencies = [ [[package]] name = "sp-core" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "array-bytes 6.2.3", "bandersnatch_vrfs", @@ -8329,11 +8388,11 @@ dependencies = [ "secrecy", "serde", "sp-crypto-hashing", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "ss58-registry", "substrate-bip39", "thiserror", @@ -8365,7 +8424,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "blake2b_simd", "byteorder", @@ -8378,7 +8437,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "quote", "sp-crypto-hashing", @@ -8388,7 +8447,7 @@ dependencies = [ [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "kvdb", "parking_lot 0.12.3", @@ -8397,7 +8456,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "proc-macro2", "quote", @@ -8417,11 +8476,11 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -8437,7 +8496,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.7.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "serde_json", "sp-api", @@ -8447,7 +8506,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -8460,7 +8519,7 @@ dependencies = [ [[package]] name = "sp-io" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "bytes", "ed25519-dalek", @@ -8472,12 +8531,12 @@ dependencies = [ "secp256k1", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-keystore", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-trie", "tracing", "tracing-core", @@ -8486,7 +8545,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "31.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "sp-core", "sp-runtime", @@ -8496,18 +8555,18 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "thiserror", "zstd 0.12.4", @@ -8516,7 +8575,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-metadata", "parity-scale-codec", @@ -8526,7 +8585,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "scale-info", @@ -8537,7 +8596,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "sp-api", "sp-core", @@ -8547,7 +8606,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "backtrace", "lazy_static", @@ -8557,7 +8616,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "rustc-hash", "serde", @@ -8567,7 +8626,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "31.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "docify", "either", @@ -8584,26 +8643,26 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-weights", ] [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", "primitive-types", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "static_assertions", ] @@ -8629,7 +8688,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "Inflector", "expander", @@ -8655,7 +8714,7 @@ dependencies = [ [[package]] name = "sp-session" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "scale-info", @@ -8669,7 +8728,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8682,7 +8741,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "hash-db", "log", @@ -8691,7 +8750,7 @@ dependencies = [ "rand", "smallvec", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-panic-handler", "sp-trie", "thiserror", @@ -8702,7 +8761,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "aes-gcm", "curve25519-dalek 4.1.3", @@ -8716,9 +8775,9 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", "x25519-dalek 2.0.1", ] @@ -8726,7 +8785,7 @@ dependencies = [ [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" [[package]] name = "sp-std" @@ -8736,13 +8795,13 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "impl-serde", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -8760,7 +8819,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "parity-scale-codec", @@ -8772,7 +8831,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "tracing", @@ -8794,7 +8853,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "sp-api", "sp-runtime", @@ -8803,7 +8862,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "async-trait", "parity-scale-codec", @@ -8817,7 +8876,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "ahash 0.8.11", "hash-db", @@ -8830,7 +8889,7 @@ dependencies = [ "scale-info", "schnellru", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "thiserror", "tracing", "trie-db", @@ -8840,7 +8899,7 @@ dependencies = [ [[package]] name = "sp-version" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8849,7 +8908,7 @@ dependencies = [ "serde", "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", "sp-version-proc-macro", "thiserror", ] @@ -8857,7 +8916,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -8868,7 +8927,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -8890,7 +8949,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -8898,7 +8957,7 @@ dependencies = [ "serde", "smallvec", "sp-arithmetic", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", ] [[package]] @@ -9037,7 +9096,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.4.7" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -9049,7 +9108,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" [[package]] name = "substrate-fixed" @@ -9065,7 +9124,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -9084,7 +9143,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ "hyper", "log", @@ -9096,15 +9155,24 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10.0#7049c3c98836b3e9253f6aaa69b6bf3d622e3962" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" dependencies = [ + "array-bytes 6.2.3", "build-helper", "cargo_metadata", "console", "filetime", + "frame-metadata", + "merkleized-metadata", + "parity-scale-codec", "parity-wasm", "polkavm-linker", + "sc-executor", + "sp-core", + "sp-io", "sp-maybe-compressed-blob", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-version", "strum 0.26.2", "tempfile", "toml 0.8.14", From fc0ccbd44cc40466baa4437b017c600b293ba636 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Jul 2024 12:25:42 -0400 Subject: [PATCH 006/269] add production just target --- justfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/justfile b/justfile index e33fdf685..f99f3913a 100644 --- a/justfile +++ b/justfile @@ -47,4 +47,8 @@ lint: @echo "Running cargo clippy with automatic fixes on potentially dirty code..." just clippy-fix @echo "Running cargo clippy..." - just clippy \ No newline at end of file + just clippy + +production: + @echo "Running cargo build with metadata-hash generation..." + cargo +{{RUSTV}} build --profile production --features="runtime-benchmarks metadata-hash" From b09b3e6b6c9a233ba9d40559207c6647e1b82cd8 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Jul 2024 12:27:59 -0400 Subject: [PATCH 007/269] fmt --- runtime/build.rs | 10 +++++----- runtime/src/lib.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/runtime/build.rs b/runtime/build.rs index 4d65d12a7..c0fa0405b 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -7,13 +7,13 @@ fn main() { .import_memory() .build(); } - #[cfg(all(feature = "std", feature = "metadata-hash"))] - { - substrate_wasm_builder::WasmBuilder::new() + #[cfg(all(feature = "std", feature = "metadata-hash"))] + { + substrate_wasm_builder::WasmBuilder::new() .with_current_project() .export_heap_base() .import_memory() - .enable_metadata_hash("TAO", 9) + .enable_metadata_hash("TAO", 9) .build(); - } + } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3457ecb5f..58c415bc0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,6 +12,7 @@ pub mod check_nonce; mod migrations; use codec::{Decode, Encode, MaxEncodedLen}; +use frame_metadata_hash_extension::CheckMetadataHash; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, @@ -23,7 +24,6 @@ use pallet_commitments::CanCommit; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; -use frame_metadata_hash_extension::CheckMetadataHash; use pallet_registry::CanRegisterIdentity; use scale_info::TypeInfo; use smallvec::smallvec; @@ -1284,7 +1284,7 @@ pub type SignedExtra = ( pallet_transaction_payment::ChargeTransactionPayment, pallet_subtensor::SubtensorSignedExtension, pallet_commitments::CommitmentsSignedExtension, - frame_metadata_hash_extension::CheckMetadataHash, + frame_metadata_hash_extension::CheckMetadataHash, ); type Migrations = pallet_grandpa::migrations::MigrateV4ToV5; From bddf9774ad41a07470b21091d183a15e014e9136 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 13:17:44 -0500 Subject: [PATCH 008/269] save --- .../src/{ => coinbase}/block_step.rs | 0 pallets/subtensor/src/coinbase/mod.rs | 2 + pallets/subtensor/src/{ => epoch}/epoch.rs | 2 +- pallets/subtensor/src/{ => epoch}/math.rs | 0 pallets/subtensor/src/epoch/mod.rs | 3 + pallets/subtensor/src/lib.rs | 2529 +++-------------- pallets/subtensor/src/macros/config.rs | 175 ++ pallets/subtensor/src/macros/dispatches.rs | 847 ++++++ pallets/subtensor/src/{ => macros}/errors.rs | 0 pallets/subtensor/src/{ => macros}/events.rs | 0 pallets/subtensor/src/macros/genesis.rs | 163 ++ pallets/subtensor/src/macros/hooks.rs | 75 + pallets/subtensor/src/macros/mod.rs | 6 + .../migrations/migrate_create_root_network.rs | 99 + .../migrations/migrate_delete_subnet_21.rs | 127 + .../src/migrations/migrate_delete_subnet_3.rs | 130 + .../migrate_fix_total_coldkey_stake.rs | 73 + .../migrate_populate_owned_hotkeys.rs | 83 + .../migrate_populate_staking_hotkeys.rs | 85 + .../migrate_to_v1_separate_emission.rs | 106 + .../migrate_to_v2_fixed_total_stake.rs | 103 + .../src/migrations/migrate_total_issuance.rs | 84 + ...igrate_transfer_ownership_to_foundation.rs | 87 + pallets/subtensor/src/migrations/mod.rs | 11 + pallets/subtensor/src/root.rs | 2 +- .../src/{ => rpc_info}/delegate_info.rs | 0 pallets/subtensor/src/rpc_info/mod.rs | 5 + .../src/{ => rpc_info}/neuron_info.rs | 0 .../src/{ => rpc_info}/stake_info.rs | 0 .../src/{ => rpc_info}/subnet_info.rs | 0 pallets/subtensor/src/weights.rs | 2 +- pallets/subtensor/tests/math.rs | 2 +- 32 files changed, 2701 insertions(+), 2100 deletions(-) rename pallets/subtensor/src/{ => coinbase}/block_step.rs (100%) create mode 100644 pallets/subtensor/src/coinbase/mod.rs rename pallets/subtensor/src/{ => epoch}/epoch.rs (99%) rename pallets/subtensor/src/{ => epoch}/math.rs (100%) create mode 100644 pallets/subtensor/src/epoch/mod.rs create mode 100644 pallets/subtensor/src/macros/config.rs create mode 100644 pallets/subtensor/src/macros/dispatches.rs rename pallets/subtensor/src/{ => macros}/errors.rs (100%) rename pallets/subtensor/src/{ => macros}/events.rs (100%) create mode 100644 pallets/subtensor/src/macros/genesis.rs create mode 100644 pallets/subtensor/src/macros/hooks.rs create mode 100644 pallets/subtensor/src/macros/mod.rs create mode 100644 pallets/subtensor/src/migrations/migrate_create_root_network.rs create mode 100644 pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs create mode 100644 pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs create mode 100644 pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs create mode 100644 pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs create mode 100644 pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs create mode 100644 pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs create mode 100644 pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs create mode 100644 pallets/subtensor/src/migrations/migrate_total_issuance.rs create mode 100644 pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs create mode 100644 pallets/subtensor/src/migrations/mod.rs rename pallets/subtensor/src/{ => rpc_info}/delegate_info.rs (100%) create mode 100644 pallets/subtensor/src/rpc_info/mod.rs rename pallets/subtensor/src/{ => rpc_info}/neuron_info.rs (100%) rename pallets/subtensor/src/{ => rpc_info}/stake_info.rs (100%) rename pallets/subtensor/src/{ => rpc_info}/subnet_info.rs (100%) diff --git a/pallets/subtensor/src/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs similarity index 100% rename from pallets/subtensor/src/block_step.rs rename to pallets/subtensor/src/coinbase/block_step.rs diff --git a/pallets/subtensor/src/coinbase/mod.rs b/pallets/subtensor/src/coinbase/mod.rs new file mode 100644 index 000000000..e86c66b59 --- /dev/null +++ b/pallets/subtensor/src/coinbase/mod.rs @@ -0,0 +1,2 @@ +use super::*; +pub mod block_step; \ No newline at end of file diff --git a/pallets/subtensor/src/epoch.rs b/pallets/subtensor/src/epoch/epoch.rs similarity index 99% rename from pallets/subtensor/src/epoch.rs rename to pallets/subtensor/src/epoch/epoch.rs index 12c407efa..d67eeded0 100644 --- a/pallets/subtensor/src/epoch.rs +++ b/pallets/subtensor/src/epoch/epoch.rs @@ -1,5 +1,5 @@ use super::*; -use crate::math::*; +use crate::epoch::math::*; use frame_support::IterableStorageDoubleMap; use sp_std::vec; use substrate_fixed::types::{I32F32, I64F64, I96F32}; diff --git a/pallets/subtensor/src/math.rs b/pallets/subtensor/src/epoch/math.rs similarity index 100% rename from pallets/subtensor/src/math.rs rename to pallets/subtensor/src/epoch/math.rs diff --git a/pallets/subtensor/src/epoch/mod.rs b/pallets/subtensor/src/epoch/mod.rs new file mode 100644 index 000000000..74f3b1094 --- /dev/null +++ b/pallets/subtensor/src/epoch/mod.rs @@ -0,0 +1,3 @@ +use super::*; +pub mod math; +pub mod epoch; \ No newline at end of file diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8d713d76a..528c06229 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -35,11 +35,12 @@ mod benchmarks; // ========================= // ==== Pallet Imports ===== // ========================= -mod block_step; +mod rpc_info; +mod coinbase; mod epoch; -mod errors; -mod events; -pub mod math; +mod macros; +use macros::{events, errors, dispatches, genesis, hooks, config}; + mod registration; mod root; mod serving; @@ -49,18 +50,17 @@ mod uids; mod utils; mod weights; -pub mod delegate_info; -pub mod neuron_info; -pub mod stake_info; -pub mod subnet_info; - // apparently this is stabilized since rust 1.36 extern crate alloc; -pub mod migration; +pub mod migrations; #[deny(missing_docs)] #[import_section(errors::errors)] #[import_section(events::events)] +#[import_section(dispatches::dispatches)] +#[import_section(genesis::genesis)] +#[import_section(hooks::hooks)] +#[import_section(config::config)] #[frame_support::pallet] pub mod pallet { @@ -69,14 +69,13 @@ pub mod pallet { pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, traits::{tokens::fungible, UnfilteredDispatchable}, }; + use crate::migrations; use frame_system::pallet_prelude::*; use sp_core::H256; use sp_runtime::traits::TrailingZeroInput; use sp_std::vec; use sp_std::vec::Vec; - use subtensor_macros::freeze_struct; - #[cfg(not(feature = "std"))] use alloc::boxed::Box; #[cfg(feature = "std")] @@ -94,653 +93,13 @@ pub mod pallet { #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); - /// Configure the pallet by specifying the parameters and types on which it depends. - #[pallet::config] - pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// A sudo-able call. - type SudoRuntimeCall: Parameter - + UnfilteredDispatchable - + GetDispatchInfo; - - /// Origin checking for council majority - type CouncilOrigin: EnsureOrigin; - - /// Currency type that will be used to place deposits on neurons - type Currency: fungible::Balanced - + fungible::Mutate; - - /// Senate members with members management functions. - type SenateMembers: crate::MemberManagement; - - /// Interface to allow other pallets to control who can register identities - type TriumvirateInterface: crate::CollectiveInterface; - - /// ================================= - /// ==== Initial Value Constants ==== - /// ================================= - - /// Initial currency issuance. - #[pallet::constant] - type InitialIssuance: Get; - /// Initial min allowed weights setting. - #[pallet::constant] - type InitialMinAllowedWeights: Get; - /// Initial Emission Ratio. - #[pallet::constant] - type InitialEmissionValue: Get; - /// Initial max weight limit. - #[pallet::constant] - type InitialMaxWeightsLimit: Get; - /// Tempo for each network. - #[pallet::constant] - type InitialTempo: Get; - /// Initial Difficulty. - #[pallet::constant] - type InitialDifficulty: Get; - /// Initial Max Difficulty. - #[pallet::constant] - type InitialMaxDifficulty: Get; - /// Initial Min Difficulty. - #[pallet::constant] - type InitialMinDifficulty: Get; - /// Initial RAO Recycled. - #[pallet::constant] - type InitialRAORecycledForRegistration: Get; - /// Initial Burn. - #[pallet::constant] - type InitialBurn: Get; - /// Initial Max Burn. - #[pallet::constant] - type InitialMaxBurn: Get; - /// Initial Min Burn. - #[pallet::constant] - type InitialMinBurn: Get; - /// Initial adjustment interval. - #[pallet::constant] - type InitialAdjustmentInterval: Get; - /// Initial bonds moving average. - #[pallet::constant] - type InitialBondsMovingAverage: Get; - /// Initial target registrations per interval. - #[pallet::constant] - type InitialTargetRegistrationsPerInterval: Get; - /// Rho constant. - #[pallet::constant] - type InitialRho: Get; - /// Kappa constant. - #[pallet::constant] - type InitialKappa: Get; - /// Max UID constant. - #[pallet::constant] - type InitialMaxAllowedUids: Get; - /// Initial validator context pruning length. - #[pallet::constant] - type InitialValidatorPruneLen: Get; - /// Initial scaling law power. - #[pallet::constant] - type InitialScalingLawPower: Get; - /// Immunity Period Constant. - #[pallet::constant] - type InitialImmunityPeriod: Get; - /// Activity constant. - #[pallet::constant] - type InitialActivityCutoff: Get; - /// Initial max registrations per block. - #[pallet::constant] - type InitialMaxRegistrationsPerBlock: Get; - /// Initial pruning score for each neuron. - #[pallet::constant] - type InitialPruningScore: Get; - /// Initial maximum allowed validators per network. - #[pallet::constant] - type InitialMaxAllowedValidators: Get; - /// Initial default delegation take. - #[pallet::constant] - type InitialDefaultTake: Get; - /// Initial minimum delegation take. - #[pallet::constant] - type InitialMinTake: Get; - /// Initial weights version key. - #[pallet::constant] - type InitialWeightsVersionKey: Get; - /// Initial serving rate limit. - #[pallet::constant] - type InitialServingRateLimit: Get; - /// Initial transaction rate limit. - #[pallet::constant] - type InitialTxRateLimit: Get; - /// Initial delegate take transaction rate limit. - #[pallet::constant] - type InitialTxDelegateTakeRateLimit: Get; - /// Initial percentage of total stake required to join senate. - #[pallet::constant] - type InitialSenateRequiredStakePercentage: Get; - /// Initial adjustment alpha on burn and pow. - #[pallet::constant] - type InitialAdjustmentAlpha: Get; - /// Initial network immunity period - #[pallet::constant] - type InitialNetworkImmunityPeriod: Get; - /// Initial minimum allowed network UIDs - #[pallet::constant] - type InitialNetworkMinAllowedUids: Get; - /// Initial network minimum burn cost - #[pallet::constant] - type InitialNetworkMinLockCost: Get; - /// Initial network subnet cut. - #[pallet::constant] - type InitialSubnetOwnerCut: Get; - /// Initial lock reduction interval. - #[pallet::constant] - type InitialNetworkLockReductionInterval: Get; - /// Initial max allowed subnets - #[pallet::constant] - type InitialSubnetLimit: Get; - /// Initial network creation rate limit - #[pallet::constant] - type InitialNetworkRateLimit: Get; - /// Initial target stakes per interval issuance. - #[pallet::constant] - type InitialTargetStakesPerInterval: Get; - /// Cost of swapping a hotkey. - #[pallet::constant] - type KeySwapCost: Get; - /// The upper bound for the alpha parameter. Used for Liquid Alpha. - #[pallet::constant] - type AlphaHigh: Get; - /// The lower bound for the alpha parameter. Used for Liquid Alpha. - #[pallet::constant] - type AlphaLow: Get; - /// A flag to indicate if Liquid Alpha is enabled. - #[pallet::constant] - type LiquidAlphaOn: Get; - /// The base difficulty for proof of work for coldkey swaps - #[pallet::constant] - type InitialBaseDifficulty: Get; - } - /// Alias for the account ID. pub type AccountIdOf = ::AccountId; - /// Senate requirements - #[pallet::type_value] - pub fn DefaultSenateRequiredStakePercentage() -> u64 { - T::InitialSenateRequiredStakePercentage::get() - } - - #[pallet::storage] - pub(super) type SenateRequiredStakePercentage = - StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; - - /// ============================ - /// ==== Staking + Accounts ==== - /// ============================ - - /// Total Rao in circulation. - #[pallet::type_value] - pub fn TotalSupply() -> u64 { - 21_000_000_000_000_000 // Rao => 21_000_000 Tao - } - /// Default total stake. - #[pallet::type_value] - pub fn DefaultDefaultTake() -> u16 { - T::InitialDefaultTake::get() - } - /// Default minimum take. - #[pallet::type_value] - pub fn DefaultMinTake() -> u16 { - T::InitialMinTake::get() - } - /// Default account take. - #[pallet::type_value] - pub fn DefaultAccountTake() -> u64 { - 0 - } - /// Default stakes per interval. - #[pallet::type_value] - pub fn DefaultStakesPerInterval() -> (u64, u64) { - (0, 0) - } - /// Default emission per block. - #[pallet::type_value] - pub fn DefaultBlockEmission() -> u64 { - 1_000_000_000 - } - /// Default allowed delegation. - #[pallet::type_value] - pub fn DefaultAllowsDelegation() -> bool { - false - } - /// Default total issuance. - #[pallet::type_value] - pub fn DefaultTotalIssuance() -> u64 { - T::InitialIssuance::get() - } - /// Default account, derived from zero trailing bytes. - #[pallet::type_value] - pub fn DefaultAccount() -> T::AccountId { - T::AccountId::decode(&mut TrailingZeroInput::zeroes()) - .expect("trailing zeroes always produce a valid account ID; qed") - } - /// Default target stakes per interval. - #[pallet::type_value] - pub fn DefaultTargetStakesPerInterval() -> u64 { - T::InitialTargetStakesPerInterval::get() - } - /// Default stake interval. - #[pallet::type_value] - pub fn DefaultStakeInterval() -> u64 { - 360 - } - - /// Default base difficulty for proof of work for coldkey swaps - #[pallet::type_value] - pub fn DefaultBaseDifficulty() -> u64 { - T::InitialBaseDifficulty::get() - } - - #[pallet::storage] // --- ITEM ( total_stake ) - pub type TotalStake = StorageValue<_, u64, ValueQuery>; - #[pallet::storage] // --- ITEM ( default_take ) - pub type MaxTake = StorageValue<_, u16, ValueQuery, DefaultDefaultTake>; - #[pallet::storage] // --- ITEM ( min_take ) - pub type MinTake = StorageValue<_, u16, ValueQuery, DefaultMinTake>; - #[pallet::storage] // --- ITEM ( global_block_emission ) - pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; - #[pallet::storage] // --- ITEM ( total_issuance ) - pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; - #[pallet::storage] // --- ITEM (target_stakes_per_interval) - pub type TargetStakesPerInterval = - StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; - - #[pallet::storage] // --- ITEM ( base_difficulty ) - pub type BaseDifficulty = StorageValue<_, u64, ValueQuery, DefaultBaseDifficulty>; - #[pallet::storage] // --- ITEM (default_stake_interval) - pub type StakeInterval = StorageValue<_, u64, ValueQuery, DefaultStakeInterval>; - #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. - pub type TotalHotkeyStake = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey. - pub type TotalColdkeyStake = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] - /// MAP (hot, cold) --> stake | Returns a tuple (u64: stakes, u64: block_number) - pub type TotalHotkeyColdkeyStakesThisInterval = StorageDoubleMap< - _, - Identity, - T::AccountId, - Identity, - T::AccountId, - (u64, u64), - ValueQuery, - DefaultStakesPerInterval, - >; - #[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. - pub type Owner = - StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; - #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. - pub type OwnedHotkeys = - StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it - pub type StakingHotkeys = - StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::storage] // --- MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. - pub type Delegates = - StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; - #[pallet::storage] // --- DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. - pub type Stake = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, - Identity, - T::AccountId, - u64, - ValueQuery, - DefaultAccountTake, - >; - - #[pallet::type_value] - /// Default value for hotkeys. - pub fn EmptyAccounts() -> Vec { - vec![] - } - #[pallet::type_value] - /// Default arbitration period. - /// This value represents the default arbitration period in blocks. - /// The period is set to 18 hours, assuming a block time of 12 seconds. - pub fn DefaultArbitrationPeriod() -> u64 { - 7200 * 3 // 3 days - } - #[pallet::storage] // ---- StorageItem Global Used Work. - pub type ArbitrationPeriod = - StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; - #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. - pub type ColdkeySwapDestinations = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - Vec, - ValueQuery, - EmptyAccounts, - >; - #[pallet::storage] // --- MAP ( cold ) --> u64 | Block when the coldkey will be arbitrated. - pub type ColdkeyArbitrationBlock = - StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; - #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. - pub type ColdkeysToSwapAtBlock = - StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; - /// -- ITEM (switches liquid alpha on) - #[pallet::type_value] - pub fn DefaultLiquidAlpha() -> bool { - false - } - #[pallet::storage] // --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled - pub type LiquidAlphaOn = - StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; - - /// ===================================== - /// ==== Difficulty / Registrations ===== - /// ===================================== - - /// Default last adjustment block. - #[pallet::type_value] - pub fn DefaultLastAdjustmentBlock() -> u64 { - 0 - } - /// Default registrations this block. - #[pallet::type_value] - pub fn DefaultRegistrationsThisBlock() -> u16 { - 0 - } - /// Default burn token. - #[pallet::type_value] - pub fn DefaultBurn() -> u64 { - T::InitialBurn::get() - } - /// Default min burn token. - #[pallet::type_value] - pub fn DefaultMinBurn() -> u64 { - T::InitialMinBurn::get() - } - /// Default max burn token. - #[pallet::type_value] - pub fn DefaultMaxBurn() -> u64 { - T::InitialMaxBurn::get() - } - /// Default difficulty value. - #[pallet::type_value] - pub fn DefaultDifficulty() -> u64 { - T::InitialDifficulty::get() - } - /// Default min difficulty value. - #[pallet::type_value] - pub fn DefaultMinDifficulty() -> u64 { - T::InitialMinDifficulty::get() - } - /// Default max difficulty value. - #[pallet::type_value] - pub fn DefaultMaxDifficulty() -> u64 { - T::InitialMaxDifficulty::get() - } - /// Default max registrations per block. - #[pallet::type_value] - pub fn DefaultMaxRegistrationsPerBlock() -> u16 { - T::InitialMaxRegistrationsPerBlock::get() - } - /// Default RAO recycled for registration. - #[pallet::type_value] - pub fn DefaultRAORecycledForRegistration() -> u64 { - T::InitialRAORecycledForRegistration::get() - } - - #[pallet::storage] // ---- StorageItem Global Used Work. - pub type UsedWork = StorageMap<_, Identity, Vec, u64, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> Burn - pub type Burn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBurn>; - #[pallet::storage] // --- MAP ( netuid ) --> Difficulty - pub type Difficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultDifficulty>; - #[pallet::storage] // --- MAP ( netuid ) --> MinBurn - pub type MinBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinBurn>; - #[pallet::storage] // --- MAP ( netuid ) --> MaxBurn - pub type MaxBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxBurn>; - #[pallet::storage] // --- MAP ( netuid ) --> MinDifficulty - pub type MinDifficulty = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinDifficulty>; - #[pallet::storage] // --- MAP ( netuid ) --> MaxDifficulty - pub type MaxDifficulty = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxDifficulty>; - #[pallet::storage] // --- MAP ( netuid ) --> Block at last adjustment. - pub type LastAdjustmentBlock = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastAdjustmentBlock>; - #[pallet::storage] // --- MAP ( netuid ) --> Registrations of this Block. - pub type RegistrationsThisBlock = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRegistrationsThisBlock>; - #[pallet::storage] // --- ITEM( global_max_registrations_per_block ) - pub type MaxRegistrationsPerBlock = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; - #[pallet::storage] // --- MAP ( netuid, global_RAO_recycled_for_registration ) - pub type RAORecycledForRegistration = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; - - /// ============================== - /// ==== Subnetworks Storage ===== - /// ============================== - - /// Default number of networks. - #[pallet::type_value] - pub fn DefaultN() -> u16 { - 0 - } - /// Default value for modality. - #[pallet::type_value] - pub fn DefaultModality() -> u16 { - 0 - } - /// Default value for hotkeys. - #[pallet::type_value] - pub fn DefaultHotkeys() -> Vec { - vec![] - } - /// Default value if network is added. - #[pallet::type_value] - pub fn DefaultNeworksAdded() -> bool { - false - } - /// Default value for network member. - #[pallet::type_value] - pub fn DefaultIsNetworkMember() -> bool { - false - } - /// Default value for registration allowed. - #[pallet::type_value] - pub fn DefaultRegistrationAllowed() -> bool { - false - } - /// Default value for network registered at. - #[pallet::type_value] - pub fn DefaultNetworkRegisteredAt() -> u64 { - 0 - } - /// Default value for network immunity period. - #[pallet::type_value] - pub fn DefaultNetworkImmunityPeriod() -> u64 { - T::InitialNetworkImmunityPeriod::get() - } - /// Default value for network last registered. - #[pallet::type_value] - pub fn DefaultNetworkLastRegistered() -> u64 { - 0 - } - /// Default value for nominator min required stake. - #[pallet::type_value] - pub fn DefaultNominatorMinRequiredStake() -> u64 { - 0 - } - /// Default value for network min allowed UIDs. - #[pallet::type_value] - pub fn DefaultNetworkMinAllowedUids() -> u16 { - T::InitialNetworkMinAllowedUids::get() - } - /// Default value for network min lock cost. - #[pallet::type_value] - pub fn DefaultNetworkMinLockCost() -> u64 { - T::InitialNetworkMinLockCost::get() - } - /// Default value for network lock reduction interval. - #[pallet::type_value] - pub fn DefaultNetworkLockReductionInterval() -> u64 { - T::InitialNetworkLockReductionInterval::get() - } - /// Default value for subnet owner cut. - #[pallet::type_value] - pub fn DefaultSubnetOwnerCut() -> u16 { - T::InitialSubnetOwnerCut::get() - } - /// Default value for subnet limit. - #[pallet::type_value] - pub fn DefaultSubnetLimit() -> u16 { - T::InitialSubnetLimit::get() - } - /// Default value for network rate limit. - #[pallet::type_value] - pub fn DefaultNetworkRateLimit() -> u64 { - if cfg!(feature = "pow-faucet") { - return 0; - } - - T::InitialNetworkRateLimit::get() - } - - #[pallet::storage] // --- ITEM( maximum_number_of_networks ) - pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; - #[pallet::storage] // --- ITEM( total_number_of_existing_networks ) - pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> subnetwork_n (Number of UIDs in the network). - pub type SubnetworkN = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultN>; - #[pallet::storage] // --- MAP ( netuid ) --> modality TEXT: 0, IMAGE: 1, TENSOR: 2 - pub type NetworkModality = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultModality>; - #[pallet::storage] // --- MAP ( netuid ) --> network_is_added - pub type NetworksAdded = - StorageMap<_, Identity, u16, bool, ValueQuery, DefaultNeworksAdded>; - #[pallet::storage] // --- DMAP ( hotkey, netuid ) --> bool - pub type IsNetworkMember = StorageDoubleMap< - _, - Blake2_128Concat, - T::AccountId, - Identity, - u16, - bool, - ValueQuery, - DefaultIsNetworkMember, - >; - #[pallet::storage] // --- MAP ( netuid ) --> network_registration_allowed - pub type NetworkRegistrationAllowed = - StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] // --- MAP ( netuid ) --> network_pow_allowed - pub type NetworkPowRegistrationAllowed = - StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] // --- MAP ( netuid ) --> block_created - pub type NetworkRegisteredAt = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkRegisteredAt>; - #[pallet::storage] // ITEM( network_immunity_period ) - pub type NetworkImmunityPeriod = - StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; - #[pallet::storage] // ITEM( network_last_registered_block ) - pub type NetworkLastRegistered = - StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; - #[pallet::storage] // ITEM( network_min_allowed_uids ) - pub type NetworkMinAllowedUids = - StorageValue<_, u16, ValueQuery, DefaultNetworkMinAllowedUids>; - #[pallet::storage] // ITEM( min_network_lock_cost ) - pub type NetworkMinLockCost = StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] // ITEM( last_network_lock_cost ) - pub type NetworkLastLockCost = - StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] // ITEM( network_lock_reduction_interval ) - pub type NetworkLockReductionInterval = - StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; - #[pallet::storage] // ITEM( subnet_owner_cut ) - pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; - #[pallet::storage] // ITEM( network_rate_limit ) - pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; - #[pallet::storage] // ITEM( nominator_min_required_stake ) - pub type NominatorMinRequiredStake = - StorageValue<_, u64, ValueQuery, DefaultNominatorMinRequiredStake>; - - /// ============================== - /// ==== Subnetwork Features ===== - /// ============================== - - /// Default value for emission values. - #[pallet::type_value] - pub fn DefaultEmissionValues() -> u64 { - 0 - } - /// Default value for pending emission. - #[pallet::type_value] - pub fn DefaultPendingEmission() -> u64 { - 0 - } - /// Default value for blocks since last step. - #[pallet::type_value] - pub fn DefaultBlocksSinceLastStep() -> u64 { - 0 - } - /// Default value for last mechanism step block. - #[pallet::type_value] - pub fn DefaultLastMechanismStepBlock() -> u64 { - 0 - } - /// Default value for subnet owner. - #[pallet::type_value] - pub fn DefaultSubnetOwner() -> T::AccountId { - T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("trailing zeroes always produce a valid account ID; qed") - } - /// Default value for subnet locked. - #[pallet::type_value] - pub fn DefaultSubnetLocked() -> u64 { - 0 - } - /// Default value for network tempo - #[pallet::type_value] - pub fn DefaultTempo() -> u16 { - T::InitialTempo::get() - } - - #[pallet::storage] // --- MAP ( netuid ) --> tempo - pub type Tempo = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTempo>; - #[pallet::storage] // --- MAP ( netuid ) --> emission_values - pub type EmissionValues = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEmissionValues>; - #[pallet::storage] // --- MAP ( netuid ) --> pending_emission - pub type PendingEmission = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultPendingEmission>; - #[pallet::storage] // --- MAP ( netuid ) --> blocks_since_last_step - pub type BlocksSinceLastStep = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBlocksSinceLastStep>; - #[pallet::storage] // --- MAP ( netuid ) --> last_mechanism_step_block - pub type LastMechansimStepBlock = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastMechanismStepBlock>; - #[pallet::storage] // --- MAP ( netuid ) --> subnet_owner - pub type SubnetOwner = - StorageMap<_, Identity, u16, T::AccountId, ValueQuery, DefaultSubnetOwner>; - #[pallet::storage] // --- MAP ( netuid ) --> subnet_locked - pub type SubnetLocked = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultSubnetLocked>; - - /// ================================= - /// ==== Axon / Promo Endpoints ===== - /// ================================= - /// Struct for Axon. pub type AxonInfoOf = AxonInfo; /// Data structure for Axon information. - #[freeze_struct("3545cfb0cac4c1f5")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct AxonInfo { /// Axon serving block. @@ -764,7 +123,6 @@ pub mod pallet { /// Struct for Prometheus. pub type PrometheusInfoOf = PrometheusInfo; /// Data structure for Prometheus information. - #[freeze_struct("5dde687e63baf0cd")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct PrometheusInfo { /// Prometheus serving block. @@ -779,366 +137,452 @@ pub mod pallet { pub ip_type: u8, } - /// Default value for rate limiting - #[pallet::type_value] - pub fn DefaultTxRateLimit() -> u64 { - T::InitialTxRateLimit::get() - } - /// Default value for delegate take rate limiting - #[pallet::type_value] - pub fn DefaultTxDelegateTakeRateLimit() -> u64 { - T::InitialTxDelegateTakeRateLimit::get() - } - /// Default value for last extrinsic block. - #[pallet::type_value] - pub fn DefaultLastTxBlock() -> u64 { - 0 - } - #[pallet::storage] // --- ITEM ( tx_rate_limit ) - pub(super) type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; - #[pallet::storage] // --- ITEM ( tx_rate_limit ) - pub(super) type TxDelegateTakeRateLimit = - StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; - #[pallet::storage] // --- MAP ( key ) --> last_block - pub type LastTxBlock = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; - #[pallet::storage] // --- MAP ( key ) --> last_block - pub(super) type LastTxBlockDelegateTake = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; - - /// Default value for serving rate limit. - #[pallet::type_value] - pub fn DefaultServingRateLimit() -> u64 { - T::InitialServingRateLimit::get() - } + /// ============================ + /// ==== Staking + Accounts ==== + /// ============================ - #[pallet::storage] // --- MAP ( netuid ) --> serving_rate_limit - pub type ServingRateLimit = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultServingRateLimit>; - #[pallet::storage] // --- MAP ( netuid, hotkey ) --> axon_info - pub type Axons = - StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; - #[pallet::storage] // --- MAP ( netuid, hotkey ) --> prometheus_info - pub type Prometheus = StorageDoubleMap< - _, - Identity, - u16, - Blake2_128Concat, - T::AccountId, - PrometheusInfoOf, - OptionQuery, - >; + #[pallet::type_value] /// Total Rao in circulation. + pub fn TotalSupply() -> u64 { 21_000_000_000_000_000 } + #[pallet::type_value] /// Default total stake. + pub fn DefaultDefaultTake() -> u16 { T::InitialDefaultTake::get() } + #[pallet::type_value] /// Default minimum take. + pub fn DefaultMinTake() -> u16 { T::InitialMinTake::get() } + #[pallet::type_value] /// Default account take. + pub fn DefaultAccountTake() -> u64 { 0 } + #[pallet::type_value] /// Default stakes per interval. + pub fn DefaultStakesPerInterval() -> (u64, u64) { (0, 0) } + #[pallet::type_value] /// Default emission per block. + pub fn DefaultBlockEmission() -> u64 { 1_000_000_000 } + #[pallet::type_value] /// Default allowed delegation. + pub fn DefaultAllowsDelegation() -> bool { false } + #[pallet::type_value] /// Default total issuance. + pub fn DefaultTotalIssuance() -> u64 { T::InitialIssuance::get() } + #[pallet::type_value] /// Default account, derived from zero trailing bytes. + pub fn DefaultAccount() -> T::AccountId { T::AccountId::decode(&mut TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } + #[pallet::type_value] /// Default target stakes per interval. + pub fn DefaultTargetStakesPerInterval() -> u64 { T::InitialTargetStakesPerInterval::get() } + #[pallet::type_value] /// Default stake interval. + pub fn DefaultStakeInterval() -> u64 { 360 } + #[pallet::type_value] /// Default account linkage + pub fn DefaultAccountLinkage() -> Vec<(u64, T::AccountId)> { vec![] } + #[pallet::type_value] /// Default account linkage + pub fn DefaultProportion() -> u64 { 0 } + #[pallet::type_value] /// Default accumulated emission for a hotkey + pub fn DefaultAccumulatedEmission() -> u64 { 0 } + #[pallet::type_value] /// Default last adjustment block. + pub fn DefaultLastAdjustmentBlock() -> u64 { 0 } + #[pallet::type_value] /// Default last adjustment block. + pub fn DefaultRegistrationsThisBlock() -> u16 { 0 } + #[pallet::type_value] /// Default registrations this block. + pub fn DefaultBurn() -> u64 { T::InitialBurn::get() } + #[pallet::type_value] /// Default burn token. + pub fn DefaultMinBurn() -> u64 { T::InitialMinBurn::get() } + #[pallet::type_value] /// Default min burn token. + pub fn DefaultMaxBurn() -> u64 { T::InitialMaxBurn::get() } + #[pallet::type_value] /// Default max burn token. + pub fn DefaultDifficulty() -> u64 { T::InitialDifficulty::get() } + #[pallet::type_value] /// Default difficulty value. + pub fn DefaultMinDifficulty() -> u64 { T::InitialMinDifficulty::get() } + #[pallet::type_value] /// Default min difficulty value. + pub fn DefaultMaxDifficulty() -> u64 { T::InitialMaxDifficulty::get() } + #[pallet::type_value] /// Default max difficulty value. + pub fn DefaultMaxRegistrationsPerBlock() -> u16 { T::InitialMaxRegistrationsPerBlock::get() } + #[pallet::type_value] /// Default max registrations per block. + pub fn DefaultRAORecycledForRegistration() -> u64 { T::InitialRAORecycledForRegistration::get() } + #[pallet::type_value] /// Default number of networks. + pub fn DefaultN() -> u16 { 0 } + #[pallet::type_value] /// Default value for modality. + pub fn DefaultModality() -> u16 { 0 } + #[pallet::type_value] /// Default value for hotkeys. + pub fn DefaultHotkeys() -> Vec { vec![] } + #[pallet::type_value] /// Default value if network is added. + pub fn DefaultNeworksAdded() -> bool { false } + #[pallet::type_value] /// Default value for network member. + pub fn DefaultIsNetworkMember() -> bool { false } + #[pallet::type_value] /// Default value for registration allowed. + pub fn DefaultRegistrationAllowed() -> bool { false } + #[pallet::type_value] /// Default value for network registered at. + pub fn DefaultNetworkRegisteredAt() -> u64 { 0 } + #[pallet::type_value] /// Default value for network immunity period. + pub fn DefaultNetworkImmunityPeriod() -> u64 { T::InitialNetworkImmunityPeriod::get() } + #[pallet::type_value] /// Default value for network last registered. + pub fn DefaultNetworkLastRegistered() -> u64 { 0 } + #[pallet::type_value] /// Default value for nominator min required stake. + pub fn DefaultNominatorMinRequiredStake() -> u64 { 0 } + #[pallet::type_value] /// Default value for network min allowed UIDs. + pub fn DefaultNetworkMinAllowedUids() -> u16 { T::InitialNetworkMinAllowedUids::get() } + #[pallet::type_value] /// Default value for network min lock cost. + pub fn DefaultNetworkMinLockCost() -> u64 { T::InitialNetworkMinLockCost::get() } + #[pallet::type_value] /// Default value for network lock reduction interval. + pub fn DefaultNetworkLockReductionInterval() -> u64 { T::InitialNetworkLockReductionInterval::get() } + #[pallet::type_value] /// Default value for subnet owner cut. + pub fn DefaultSubnetOwnerCut() -> u16 { T::InitialSubnetOwnerCut::get() } + #[pallet::type_value] /// Default value for subnet limit. + pub fn DefaultSubnetLimit() -> u16 { T::InitialSubnetLimit::get() } + #[pallet::type_value] /// Default value for network rate limit. + pub fn DefaultNetworkRateLimit() -> u64 { if cfg!(feature = "pow-faucet") { return 0; } T::InitialNetworkRateLimit::get() } + // #[pallet::type_value] /// Default value for network max stake. + // pub fn DefaultNetworkMaxStake() -> u64 { T::InitialNetworkMaxStake::get() } + #[pallet::type_value] /// Default value for emission values. + pub fn DefaultEmissionValues() -> u64 { 0 } + #[pallet::type_value] /// Default value for pending emission. + pub fn DefaultPendingEmission() -> u64 { 0 } + #[pallet::type_value] /// Default value for blocks since last step. + pub fn DefaultBlocksSinceLastStep() -> u64 { 0 } + #[pallet::type_value] /// Default value for last mechanism step block. + pub fn DefaultLastMechanismStepBlock() -> u64 { 0 } + #[pallet::type_value] /// Default value for subnet owner. + pub fn DefaultSubnetOwner() -> T::AccountId { T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } + #[pallet::type_value] /// Default value for subnet locked. + pub fn DefaultSubnetLocked() -> u64 { 0 } + #[pallet::type_value] /// Default value for network tempo + pub fn DefaultTempo() -> u16 { T::InitialTempo::get() } + #[pallet::type_value] /// Default value for weights set rate limit. + pub fn DefaultWeightsSetRateLimit() -> u64 { 100 } + #[pallet::type_value] /// Default block number at registration. + pub fn DefaultBlockAtRegistration() -> u64 { 0 } + #[pallet::type_value] /// Default value for rho parameter. + pub fn DefaultRho() -> u16 { T::InitialRho::get() } + #[pallet::type_value] /// Default value for kappa parameter. + pub fn DefaultKappa() -> u16 { T::InitialKappa::get() } + #[pallet::type_value] /// Default maximum allowed UIDs. + pub fn DefaultMaxAllowedUids() -> u16 { T::InitialMaxAllowedUids::get() } + #[pallet::type_value] /// Default immunity period. + pub fn DefaultImmunityPeriod() -> u16 { T::InitialImmunityPeriod::get() } + #[pallet::type_value] /// Default activity cutoff. + pub fn DefaultActivityCutoff() -> u16 { T::InitialActivityCutoff::get() } + #[pallet::type_value] /// Default maximum weights limit. + pub fn DefaultMaxWeightsLimit() -> u16 { T::InitialMaxWeightsLimit::get() } + #[pallet::type_value] /// Default weights version key. + pub fn DefaultWeightsVersionKey() -> u64 { T::InitialWeightsVersionKey::get() } + #[pallet::type_value] /// Default minimum allowed weights. + pub fn DefaultMinAllowedWeights() -> u16 { T::InitialMinAllowedWeights::get() } + #[pallet::type_value] /// Default maximum allowed validators. + pub fn DefaultMaxAllowedValidators() -> u16 { T::InitialMaxAllowedValidators::get() } + #[pallet::type_value] /// Default adjustment interval. + pub fn DefaultAdjustmentInterval() -> u16 { T::InitialAdjustmentInterval::get() } + #[pallet::type_value] /// Default bonds moving average. + pub fn DefaultBondsMovingAverage() -> u64 { T::InitialBondsMovingAverage::get() } + #[pallet::type_value] /// Default validator prune length. + pub fn DefaultValidatorPruneLen() -> u64 { T::InitialValidatorPruneLen::get() } + #[pallet::type_value] /// Default scaling law power. + pub fn DefaultScalingLawPower() -> u16 { T::InitialScalingLawPower::get() } + #[pallet::type_value] /// Default target registrations per interval. + pub fn DefaultTargetRegistrationsPerInterval() -> u16 { T::InitialTargetRegistrationsPerInterval::get() } + #[pallet::type_value] /// Default adjustment alpha. + pub fn DefaultAdjustmentAlpha() -> u64 { T::InitialAdjustmentAlpha::get() } + #[pallet::type_value] /// Default minimum stake for weights. + pub fn DefaultWeightsMinStake() -> u64 { 0 } + #[pallet::type_value] /// Value definition for vector of u16. + pub fn EmptyU16Vec() -> Vec { vec![] } + #[pallet::type_value] /// Value definition for vector of u64. + pub fn EmptyU64Vec() -> Vec { vec![] } + #[pallet::type_value] /// Value definition for vector of bool. + pub fn EmptyBoolVec() -> Vec { vec![] } + #[pallet::type_value] /// Value definition for bonds with type vector of (u16, u16). + pub fn DefaultBonds() -> Vec<(u16, u16)> { vec![] } + #[pallet::type_value] /// Value definition for weights with vector of (u16, u16). + pub fn DefaultWeights() -> Vec<(u16, u16)> { vec![] } + #[pallet::type_value] /// Default value for key with type T::AccountId derived from trailing zeroes. + pub fn DefaultKey() -> T::AccountId { T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } + #[pallet::type_value] /// Default value for network immunity period. + pub fn DefaultHotkeyEmissionTempo() -> u64 { 7200 } + #[pallet::type_value] /// Default value for rate limiting + pub fn DefaultTxRateLimit() -> u64 { T::InitialTxRateLimit::get() } + #[pallet::type_value] /// Default value for delegate take rate limiting + pub fn DefaultTxDelegateTakeRateLimit() -> u64 { T::InitialTxDelegateTakeRateLimit::get() } + #[pallet::type_value] /// Default value for last extrinsic block. + pub fn DefaultLastTxBlock() -> u64 { 0 } + #[pallet::type_value] /// Default value for serving rate limit. + pub fn DefaultServingRateLimit() -> u64 { T::InitialServingRateLimit::get() } + #[pallet::type_value] /// Default value for weight commit reveal interval. + pub fn DefaultWeightCommitRevealInterval() -> u64 { 1000 } + #[pallet::type_value] /// Default value for weight commit/reveal enabled. + pub fn DefaultCommitRevealWeightsEnabled() -> bool { false } + #[pallet::type_value] /// Senate requirements + pub fn DefaultSenateRequiredStakePercentage() -> u64 { T::InitialSenateRequiredStakePercentage::get() } + #[pallet::type_value] /// -- ITEM (switches liquid alpha on) + pub fn DefaultLiquidAlpha() -> bool {false} + #[pallet::type_value] /// (alpha_low: 0.7, alpha_high: 0.9) + pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - /// ======================================= - /// ==== Subnetwork Hyperparam storage ==== - /// ======================================= + #[pallet::storage] + pub(super) type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; - /// Default weights set rate limit. - #[pallet::type_value] - pub fn DefaultWeightsSetRateLimit() -> u64 { - 100 - } - /// Default block at registration. - #[pallet::type_value] - pub fn DefaultBlockAtRegistration() -> u64 { - 0 - } - /// Default Rho parameter value. - #[pallet::type_value] - pub fn DefaultRho() -> u16 { - T::InitialRho::get() - } - /// Default Kai parameter value. - #[pallet::type_value] - pub fn DefaultKappa() -> u16 { - T::InitialKappa::get() - } - /// Default max allowed uids. - #[pallet::type_value] - pub fn DefaultMaxAllowedUids() -> u16 { - T::InitialMaxAllowedUids::get() - } - /// Default immunity period value. - #[pallet::type_value] - pub fn DefaultImmunityPeriod() -> u16 { - T::InitialImmunityPeriod::get() - } - /// Default activity cutoff value. - #[pallet::type_value] - pub fn DefaultActivityCutoff() -> u16 { - T::InitialActivityCutoff::get() - } - /// Default max weights limit. - #[pallet::type_value] - pub fn DefaultMaxWeightsLimit() -> u16 { - T::InitialMaxWeightsLimit::get() - } - /// Default weights version key. - #[pallet::type_value] - pub fn DefaultWeightsVersionKey() -> u64 { - T::InitialWeightsVersionKey::get() - } - /// Default minimal allowed weights. - #[pallet::type_value] - pub fn DefaultMinAllowedWeights() -> u16 { - T::InitialMinAllowedWeights::get() - } - /// Default max allowed validators. - #[pallet::type_value] - pub fn DefaultMaxAllowedValidators() -> u16 { - T::InitialMaxAllowedValidators::get() - } - /// Default adjustment interval. - #[pallet::type_value] - pub fn DefaultAdjustmentInterval() -> u16 { - T::InitialAdjustmentInterval::get() - } - /// Default bonds moving average. - #[pallet::type_value] - pub fn DefaultBondsMovingAverage() -> u64 { - T::InitialBondsMovingAverage::get() - } - /// Default validator prune length. - #[pallet::type_value] - pub fn DefaultValidatorPruneLen() -> u64 { - T::InitialValidatorPruneLen::get() - } - /// Default scaling law power. - #[pallet::type_value] - pub fn DefaultScalingLawPower() -> u16 { - T::InitialScalingLawPower::get() - } - /// Default target registrations per interval. - #[pallet::type_value] - pub fn DefaultTargetRegistrationsPerInterval() -> u16 { - T::InitialTargetRegistrationsPerInterval::get() - } - /// Default adjustment alpha. - #[pallet::type_value] - pub fn DefaultAdjustmentAlpha() -> u64 { - T::InitialAdjustmentAlpha::get() - } - /// Default weights min stake. - #[pallet::type_value] - pub fn DefaultWeightsMinStake() -> u64 { - 0 - } - /// Provides the default value for the upper bound of the alpha parameter. + /// ============================ + /// ==== Staking Variables ==== + /// ============================ + #[pallet::storage] // --- ITEM ( total_issuance ) + pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; + #[pallet::storage] // --- ITEM ( total_stake ) + pub type TotalStake = StorageValue<_, u64, ValueQuery>; + #[pallet::storage] // --- ITEM ( default_take ) + pub type MaxTake = StorageValue<_, u16, ValueQuery, DefaultDefaultTake>; + #[pallet::storage] // --- ITEM ( min_take ) + pub type MinTake = StorageValue<_, u16, ValueQuery, DefaultMinTake>; + #[pallet::storage] // --- ITEM ( global_block_emission ) + pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; + #[pallet::storage] // --- ITEM (target_stakes_per_interval) + pub type TargetStakesPerInterval = StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; + #[pallet::storage] // --- ITEM (default_stake_interval) + pub type StakeInterval = StorageValue<_, u64, ValueQuery, DefaultStakeInterval>; + #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. + pub type TotalHotkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey. + pub type TotalColdkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] /// MAP (hot, cold) --> stake | Returns a tuple (u64: stakes, u64: block_number) + pub type TotalHotkeyColdkeyStakesThisInterval = StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, (u64, u64), ValueQuery, DefaultStakesPerInterval>; + #[pallet::storage] /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. + pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; + #[pallet::storage] /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. + pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; + #[pallet::storage] /// DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. + pub type Stake = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] /// Map ( hot ) --> last_hotkey_emission_drain | Last block we drained this hotkey's emission. + pub type LastHotkeyEmissionDrain = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery, DefaultAccumulatedEmission>; + #[pallet::storage] /// ITEM ( hotkey_emission_tempo ) + pub type HotkeyEmissionTempo = StorageValue<_, u64, ValueQuery, DefaultHotkeyEmissionTempo>; + #[pallet::storage] /// Map ( hot ) --> emission | Accumulated hotkey emission. + pub type PendingdHotkeyEmission = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery, DefaultAccumulatedEmission>; + #[pallet::storage] /// Map ( hot, cold ) --> block_number | Last add stake increase. + pub type LastAddStakeIncrease = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> + pub type ChildKeys = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, Vec<(u64, T::AccountId)>, ValueQuery, DefaultAccountLinkage>; + #[pallet::storage] /// DMAP ( child, netuid ) --> Vec<(proportion,parent)> + pub type ParentKeys = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, Vec<(u64, T::AccountId)>, ValueQuery, DefaultAccountLinkage>; + #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. + pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - #[pallet::type_value] - pub fn DefaultAlphaValues() -> (u16, u16) { - (45875, 58982) // (alpha_low: 0.7, alpha_high: 0.9) - } - #[pallet::storage] // ITEM( weights_min_stake ) - pub type WeightsMinStake = StorageValue<_, u64, ValueQuery, DefaultWeightsMinStake>; - #[pallet::storage] // --- MAP ( netuid ) --> Rho + /// ============================ + /// ==== Global Parameters ===== + /// ============================ + #[pallet::storage] /// --- StorageItem Global Used Work. + pub type UsedWork = StorageMap<_, Identity, Vec, u64, ValueQuery>; + #[pallet::storage] /// --- ITEM( global_max_registrations_per_block ) + pub type MaxRegistrationsPerBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; + #[pallet::storage] /// --- ITEM( maximum_number_of_networks ) + pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; + #[pallet::storage] /// --- ITEM( total_number_of_existing_networks ) + pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; + #[pallet::storage] /// ITEM( network_immunity_period ) + pub type NetworkImmunityPeriod = StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; + #[pallet::storage] /// ITEM( network_last_registered_block ) + pub type NetworkLastRegistered = StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; + #[pallet::storage] /// ITEM( network_min_allowed_uids ) + pub type NetworkMinAllowedUids = StorageValue<_, u16, ValueQuery, DefaultNetworkMinAllowedUids>; + #[pallet::storage] /// ITEM( min_network_lock_cost ) + pub type NetworkMinLockCost = StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; + #[pallet::storage] /// ITEM( last_network_lock_cost ) + pub type NetworkLastLockCost = StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; + #[pallet::storage] /// ITEM( network_lock_reduction_interval ) + pub type NetworkLockReductionInterval = StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; + #[pallet::storage] /// ITEM( subnet_owner_cut ) + pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; + #[pallet::storage] /// ITEM( network_rate_limit ) + pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; + #[pallet::storage] /// ITEM( nominator_min_required_stake ) + pub type NominatorMinRequiredStake = StorageValue<_, u64, ValueQuery, DefaultNominatorMinRequiredStake>; + + /// ============================ + /// ==== Subnet Parameters ===== + /// ============================ + #[pallet::storage] /// --- MAP ( netuid ) --> subnetwork_n (Number of UIDs in the network). + pub type SubnetworkN = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultN>; + #[pallet::storage] /// --- MAP ( netuid ) --> modality TEXT: 0, IMAGE: 1, TENSOR: 2 + pub type NetworkModality = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultModality>; + #[pallet::storage] /// --- MAP ( netuid ) --> network_is_added + pub type NetworksAdded = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultNeworksAdded>; + #[pallet::storage] /// --- DMAP ( hotkey, netuid ) --> bool + pub type IsNetworkMember = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, bool, ValueQuery, DefaultIsNetworkMember>; + #[pallet::storage] /// --- MAP ( netuid ) --> network_registration_allowed + pub type NetworkRegistrationAllowed = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; + #[pallet::storage] /// --- MAP ( netuid ) --> network_pow_allowed + pub type NetworkPowRegistrationAllowed = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; + #[pallet::storage] /// --- MAP ( netuid ) --> block_created + pub type NetworkRegisteredAt = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkRegisteredAt>; + #[pallet::storage] /// --- MAP ( netuid ) --> tempo + pub type Tempo = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTempo>; + #[pallet::storage] /// --- MAP ( netuid ) --> emission_values + pub type EmissionValues = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEmissionValues>; + #[pallet::storage] /// --- MAP ( netuid ) --> pending_emission + pub type PendingEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultPendingEmission>; + #[pallet::storage] /// --- MAP ( netuid ) --> blocks_since_last_step + pub type BlocksSinceLastStep = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBlocksSinceLastStep>; + #[pallet::storage] /// --- MAP ( netuid ) --> last_mechanism_step_block + pub type LastMechansimStepBlock = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastMechanismStepBlock>; + #[pallet::storage] /// --- MAP ( netuid ) --> subnet_owner + pub type SubnetOwner = StorageMap<_, Identity, u16, T::AccountId, ValueQuery, DefaultSubnetOwner>; + #[pallet::storage] /// --- MAP ( netuid ) --> subnet_locked + pub type SubnetLocked = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultSubnetLocked>; + #[pallet::storage] /// --- MAP ( netuid ) --> serving_rate_limit + pub type ServingRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultServingRateLimit>; + #[pallet::storage] /// --- MAP ( netuid ) --> Rho pub type Rho = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRho>; - #[pallet::storage] // --- MAP ( netuid ) --> Kappa + #[pallet::storage] /// --- MAP ( netuid ) --> Kappa pub type Kappa = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultKappa>; - #[pallet::storage] // --- MAP ( netuid ) --> uid, we use to record uids to prune at next epoch. + #[pallet::storage] /// --- MAP ( netuid ) --> uid, we use to record uids to prune at next epoch. pub type NeuronsToPruneAtNextEpoch = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> registrations_this_interval + #[pallet::storage] /// --- MAP ( netuid ) --> registrations_this_interval pub type RegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> pow_registrations_this_interval - pub type POWRegistrationsThisInterval = - StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> burn_registrations_this_interval - pub type BurnRegistrationsThisInterval = - StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] // --- MAP ( netuid ) --> max_allowed_uids - pub type MaxAllowedUids = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedUids>; - #[pallet::storage] // --- MAP ( netuid ) --> immunity_period - pub type ImmunityPeriod = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultImmunityPeriod>; - #[pallet::storage] // --- MAP ( netuid ) --> activity_cutoff - pub type ActivityCutoff = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultActivityCutoff>; - #[pallet::storage] // --- MAP ( netuid ) --> max_weight_limit - pub type MaxWeightsLimit = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxWeightsLimit>; - #[pallet::storage] // --- MAP ( netuid ) --> weights_version_key - pub type WeightsVersionKey = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsVersionKey>; - #[pallet::storage] // --- MAP ( netuid ) --> min_allowed_weights - pub type MinAllowedWeights = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMinAllowedWeights>; - #[pallet::storage] // --- MAP ( netuid ) --> max_allowed_validators - pub type MaxAllowedValidators = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedValidators>; - #[pallet::storage] // --- MAP ( netuid ) --> adjustment_interval - pub type AdjustmentInterval = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultAdjustmentInterval>; - #[pallet::storage] // --- MAP ( netuid ) --> bonds_moving_average - pub type BondsMovingAverage = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; - #[pallet::storage] // --- MAP ( netuid ) --> weights_set_rate_limit - pub type WeightsSetRateLimit = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsSetRateLimit>; - #[pallet::storage] // --- MAP ( netuid ) --> validator_prune_len - pub type ValidatorPruneLen = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultValidatorPruneLen>; - #[pallet::storage] // --- MAP ( netuid ) --> scaling_law_power - pub type ScalingLawPower = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultScalingLawPower>; - #[pallet::storage] // --- MAP ( netuid ) --> target_registrations_this_interval - pub type TargetRegistrationsPerInterval = - StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTargetRegistrationsPerInterval>; - #[pallet::storage] // --- DMAP ( netuid, uid ) --> block_at_registration - pub type BlockAtRegistration = StorageDoubleMap< - _, - Identity, - u16, - Identity, - u16, - u64, - ValueQuery, - DefaultBlockAtRegistration, - >; - #[pallet::storage] // --- DMAP ( netuid ) --> adjustment_alpha - pub type AdjustmentAlpha = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultAdjustmentAlpha>; - - // MAP ( netuid ) --> (alpha_low, alpha_high) - #[pallet::storage] - pub type AlphaValues = - StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; - - #[pallet::storage] // --- MAP (netuid, who) --> (hash, weight) | Returns the hash and weight committed by an account for a given netuid. - pub type WeightCommits = StorageDoubleMap< - _, - Twox64Concat, - u16, - Twox64Concat, - T::AccountId, - (H256, u64), - OptionQuery, - >; - - /// Default value for weight commit reveal interval. - #[pallet::type_value] - pub fn DefaultWeightCommitRevealInterval() -> u64 { - 1000 - } - // --- DMAP ( netuid ) --> interval - #[pallet::storage] - pub type WeightCommitRevealInterval = - StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightCommitRevealInterval>; + #[pallet::storage] /// --- MAP ( netuid ) --> pow_registrations_this_interval + pub type POWRegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; + #[pallet::storage] /// --- MAP ( netuid ) --> burn_registrations_this_interval + pub type BurnRegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; + #[pallet::storage] /// --- MAP ( netuid ) --> max_allowed_uids + pub type MaxAllowedUids = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedUids>; + #[pallet::storage] /// --- MAP ( netuid ) --> immunity_period + pub type ImmunityPeriod = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultImmunityPeriod>; + #[pallet::storage] /// --- MAP ( netuid ) --> activity_cutoff + pub type ActivityCutoff = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultActivityCutoff>; + #[pallet::storage] /// --- MAP ( netuid ) --> max_weight_limit + pub type MaxWeightsLimit = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxWeightsLimit>; + #[pallet::storage] /// --- MAP ( netuid ) --> weights_version_key + pub type WeightsVersionKey = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsVersionKey>; + #[pallet::storage] /// --- MAP ( netuid ) --> min_allowed_weights + pub type MinAllowedWeights = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMinAllowedWeights>; + #[pallet::storage] /// --- MAP ( netuid ) --> max_allowed_validators + pub type MaxAllowedValidators = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedValidators>; + #[pallet::storage] /// --- MAP ( netuid ) --> adjustment_interval + pub type AdjustmentInterval = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultAdjustmentInterval>; + #[pallet::storage] /// --- MAP ( netuid ) --> bonds_moving_average + pub type BondsMovingAverage = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; + #[pallet::storage] /// --- MAP ( netuid ) --> weights_set_rate_limit + pub type WeightsSetRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsSetRateLimit>; + #[pallet::storage] /// --- MAP ( netuid ) --> validator_prune_len + pub type ValidatorPruneLen = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultValidatorPruneLen>; + #[pallet::storage] /// --- MAP ( netuid ) --> scaling_law_power + pub type ScalingLawPower = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultScalingLawPower>; + #[pallet::storage] /// --- MAP ( netuid ) --> target_registrations_this_interval + pub type TargetRegistrationsPerInterval = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTargetRegistrationsPerInterval>; + #[pallet::storage] /// --- MAP ( netuid ) --> adjustment_alpha + pub type AdjustmentAlpha = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultAdjustmentAlpha>; + #[pallet::storage] /// --- MAP ( netuid ) --> interval + pub type WeightCommitRevealInterval = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightCommitRevealInterval>; + #[pallet::storage] /// --- MAP ( netuid ) --> interval + pub type CommitRevealWeightsEnabled = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; + #[pallet::storage] /// --- MAP ( netuid ) --> Burn + pub type Burn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBurn>; + #[pallet::storage] /// --- MAP ( netuid ) --> Difficulty + pub type Difficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultDifficulty>; + #[pallet::storage] /// --- MAP ( netuid ) --> MinBurn + pub type MinBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinBurn>; + #[pallet::storage] /// --- MAP ( netuid ) --> MaxBurn + pub type MaxBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxBurn>; + #[pallet::storage] /// --- MAP ( netuid ) --> MinDifficulty + pub type MinDifficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinDifficulty>; + #[pallet::storage] /// --- MAP ( netuid ) --> MaxDifficulty + pub type MaxDifficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxDifficulty>; + #[pallet::storage] /// --- MAP ( netuid ) --> Block at last adjustment. + pub type LastAdjustmentBlock = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastAdjustmentBlock>; + #[pallet::storage] /// --- MAP ( netuid ) --> Registrations of this Block. + pub type RegistrationsThisBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRegistrationsThisBlock>; + #[pallet::storage] /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration + pub type RAORecycledForRegistration = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; + #[pallet::storage] /// --- ITEM ( tx_rate_limit ) + pub(super) type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; + #[pallet::storage] /// --- ITEM ( tx_rate_limit ) + pub(super) type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; + #[pallet::storage] /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled + pub type LiquidAlphaOn = StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; + #[pallet::storage] /// MAP ( netuid ) --> (alpha_low, alpha_high) + pub type AlphaValues = StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; - /// Default value for weight commit/reveal enabled. - #[pallet::type_value] - pub fn DefaultCommitRevealWeightsEnabled() -> bool { - false - } - // --- DMAP ( netuid ) --> interval - #[pallet::storage] - pub type CommitRevealWeightsEnabled = - StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; /// ======================================= /// ==== Subnetwork Consensus Storage ==== /// ======================================= + #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid + pub(super) type Uids = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; + #[pallet::storage] /// --- DMAP ( netuid, uid ) --> hotkey + pub(super) type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; + #[pallet::storage] /// --- DMAP ( netuid ) --> (hotkey, se, ve) + pub(super) type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; + #[pallet::storage] /// --- DMAP ( netuid ) --> active + pub(super) type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> rank + pub(super) type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> trust + pub(super) type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> consensus + pub(super) type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> incentive + pub(super) type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> dividends + pub(super) type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> emission + pub(super) type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> last_update + pub(super) type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> validator_trust + pub(super) type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> pruning_scores + pub(super) type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] /// --- DMAP ( netuid ) --> validator_permit + pub(super) type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + #[pallet::storage] /// --- DMAP ( netuid, uid ) --> weights + pub(super) type Weights = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultWeights>; + #[pallet::storage] /// --- DMAP ( netuid, uid ) --> bonds + pub(super) type Bonds = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultBonds>; + #[pallet::storage] /// --- DMAP ( netuid, uid ) --> block_at_registration + pub type BlockAtRegistration = StorageDoubleMap<_, Identity, u16, Identity, u16, u64, ValueQuery, DefaultBlockAtRegistration>; + #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> axon_info + pub(super) type Axons = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; + #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> prometheus_info + pub(super) type Prometheus = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, PrometheusInfoOf, OptionQuery>; - /// Value definition for vector of u16. - #[pallet::type_value] - pub fn EmptyU16Vec() -> Vec { - vec![] - } - /// Value definition for vector of u64. - #[pallet::type_value] - pub fn EmptyU64Vec() -> Vec { - vec![] - } - /// Value definition for vector of bool. - #[pallet::type_value] - pub fn EmptyBoolVec() -> Vec { - vec![] - } - /// Value definition for bonds with type vector of (u16, u16). - #[pallet::type_value] - pub fn DefaultBonds() -> Vec<(u16, u16)> { - vec![] - } - /// Value definition for weights with vector of (u16, u16). + /// ================================= + /// ==== Axon / Promo Endpoints ===== + /// ================================= + #[pallet::storage] /// --- MAP ( key ) --> last_block + pub(super) type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] /// --- MAP ( key ) --> last_block + pub(super) type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] /// ITEM( weights_min_stake ) + pub type WeightsMinStake = StorageValue<_, u64, ValueQuery, DefaultWeightsMinStake>; + #[pallet::storage] /// --- MAP (netuid, who) --> (hash, weight) | Returns the hash and weight committed by an account for a given netuid. + pub type WeightCommits = StorageDoubleMap<_, Twox64Concat, u16, Twox64Concat, T::AccountId, (H256, u64), OptionQuery>; + + /// =============================== + /// ==== Coldkey Arbitrations ===== + /// =============================== + #[pallet::type_value] /// Default base difficulty for proof of work for coldkey swaps + pub fn DefaultBaseDifficulty() -> u64 { T::InitialBaseDifficulty::get() } + #[pallet::storage] // --- ITEM ( base_difficulty ) + pub type BaseDifficulty = StorageValue<_, u64, ValueQuery, DefaultBaseDifficulty>; #[pallet::type_value] - pub fn DefaultWeights() -> Vec<(u16, u16)> { + /// Default value for hotkeys. + pub fn EmptyAccounts() -> Vec { vec![] } - /// Default value for key with type T::AccountId derived from trailing zeroes. #[pallet::type_value] - pub fn DefaultKey() -> T::AccountId { - T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("trailing zeroes always produce a valid account ID; qed") + /// Default arbitration period. + /// This value represents the default arbitration period in blocks. + /// The period is set to 18 hours, assuming a block time of 12 seconds. + pub fn DefaultArbitrationPeriod() -> u64 { + 7200 * 3 // 3 days } - - #[pallet::storage] // --- DMAP ( netuid, hotkey ) --> uid - pub type Uids = - StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; - #[pallet::storage] // --- DMAP ( netuid, uid ) --> hotkey - pub type Keys = - StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; - #[pallet::storage] // --- DMAP ( netuid ) --> (hotkey, se, ve) - pub type LoadedEmission = - StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; - - #[pallet::storage] // --- DMAP ( netuid ) --> active - pub(super) type Active = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; - #[pallet::storage] // --- DMAP ( netuid ) --> rank - pub(super) type Rank = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> trust - pub(super) type Trust = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> consensus - pub(super) type Consensus = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> incentive - pub(super) type Incentive = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> dividends - pub(super) type Dividends = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> emission - pub(super) type Emission = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> last_update - pub(super) type LastUpdate = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> validator_trust - pub(super) type ValidatorTrust = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> pruning_scores - pub(super) type PruningScores = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] // --- DMAP ( netuid ) --> validator_permit - pub(super) type ValidatorPermit = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; - - #[pallet::storage] // --- DMAP ( netuid, uid ) --> weights - pub(super) type Weights = StorageDoubleMap< - _, - Identity, - u16, - Identity, - u16, - Vec<(u16, u16)>, - ValueQuery, - DefaultWeights, - >; - #[pallet::storage] // --- DMAP ( netuid, uid ) --> bonds - pub(super) type Bonds = StorageDoubleMap< + #[pallet::storage] // ---- StorageItem Global Used Work. + pub type ArbitrationPeriod = + StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; + #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. + pub type ColdkeySwapDestinations = StorageMap< _, - Identity, - u16, - Identity, - u16, - Vec<(u16, u16)>, + Blake2_128Concat, + T::AccountId, + Vec, ValueQuery, - DefaultBonds, + EmptyAccounts, >; + #[pallet::storage] // --- MAP ( cold ) --> u64 | Block when the coldkey will be arbitrated. + pub type ColdkeyArbitrationBlock = + StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; + #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. + pub type ColdkeysToSwapAtBlock = + StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; /// ================== /// ==== Genesis ===== @@ -1161,1113 +605,6 @@ pub mod pallet { } } - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - // Set initial total issuance from balances - TotalIssuance::::put(self.balances_issuance); - - // Subnet config values - let netuid: u16 = 3; - let tempo = 99; - let max_uids = 4096; - - // The functions for initializing new networks/setting defaults cannot be run directly from genesis functions like extrinsics would - // --- Set this network uid to alive. - NetworksAdded::::insert(netuid, true); - - // --- Fill tempo memory item. - Tempo::::insert(netuid, tempo); - - // --- Fill modality item. - // Only modality 0 exists (text) - NetworkModality::::insert(netuid, 0); - - // Make network parameters explicit. - if !Tempo::::contains_key(netuid) { - Tempo::::insert(netuid, Tempo::::get(netuid)); - } - if !Kappa::::contains_key(netuid) { - Kappa::::insert(netuid, Kappa::::get(netuid)); - } - if !Difficulty::::contains_key(netuid) { - Difficulty::::insert(netuid, Difficulty::::get(netuid)); - } - if !MaxAllowedUids::::contains_key(netuid) { - MaxAllowedUids::::insert(netuid, MaxAllowedUids::::get(netuid)); - } - if !ImmunityPeriod::::contains_key(netuid) { - ImmunityPeriod::::insert(netuid, ImmunityPeriod::::get(netuid)); - } - if !ActivityCutoff::::contains_key(netuid) { - ActivityCutoff::::insert(netuid, ActivityCutoff::::get(netuid)); - } - if !EmissionValues::::contains_key(netuid) { - EmissionValues::::insert(netuid, EmissionValues::::get(netuid)); - } - if !MaxWeightsLimit::::contains_key(netuid) { - MaxWeightsLimit::::insert(netuid, MaxWeightsLimit::::get(netuid)); - } - if !MinAllowedWeights::::contains_key(netuid) { - MinAllowedWeights::::insert(netuid, MinAllowedWeights::::get(netuid)); - } - if !RegistrationsThisInterval::::contains_key(netuid) { - RegistrationsThisInterval::::insert( - netuid, - RegistrationsThisInterval::::get(netuid), - ); - } - if !POWRegistrationsThisInterval::::contains_key(netuid) { - POWRegistrationsThisInterval::::insert( - netuid, - POWRegistrationsThisInterval::::get(netuid), - ); - } - if !BurnRegistrationsThisInterval::::contains_key(netuid) { - BurnRegistrationsThisInterval::::insert( - netuid, - BurnRegistrationsThisInterval::::get(netuid), - ); - } - - // Set max allowed uids - MaxAllowedUids::::insert(netuid, max_uids); - - let mut next_uid = 0u16; - - for (coldkey, hotkeys) in self.stakes.iter() { - for (hotkey, stake_uid) in hotkeys.iter() { - let (stake, uid) = stake_uid; - - // Expand Yuma Consensus with new position. - Rank::::mutate(netuid, |v| v.push(0)); - Trust::::mutate(netuid, |v| v.push(0)); - Active::::mutate(netuid, |v| v.push(true)); - Emission::::mutate(netuid, |v| v.push(0)); - Consensus::::mutate(netuid, |v| v.push(0)); - Incentive::::mutate(netuid, |v| v.push(0)); - Dividends::::mutate(netuid, |v| v.push(0)); - LastUpdate::::mutate(netuid, |v| v.push(0)); - PruningScores::::mutate(netuid, |v| v.push(0)); - ValidatorTrust::::mutate(netuid, |v| v.push(0)); - ValidatorPermit::::mutate(netuid, |v| v.push(false)); - - // Insert account information. - Keys::::insert(netuid, uid, hotkey.clone()); // Make hotkey - uid association. - Uids::::insert(netuid, hotkey.clone(), uid); // Make uid - hotkey association. - BlockAtRegistration::::insert(netuid, uid, 0); // Fill block at registration. - IsNetworkMember::::insert(hotkey.clone(), netuid, true); // Fill network is member. - - // Fill stake information. - Owner::::insert(hotkey.clone(), coldkey.clone()); - - // Update OwnedHotkeys map - let mut hotkeys = OwnedHotkeys::::get(coldkey); - if !hotkeys.contains(hotkey) { - hotkeys.push(hotkey.clone()); - OwnedHotkeys::::insert(coldkey, hotkeys); - } - - TotalHotkeyStake::::insert(hotkey.clone(), stake); - TotalColdkeyStake::::insert( - coldkey.clone(), - TotalColdkeyStake::::get(coldkey).saturating_add(*stake), - ); - - // Update total issuance value - TotalIssuance::::put(TotalIssuance::::get().saturating_add(*stake)); - - Stake::::insert(hotkey.clone(), coldkey.clone(), stake); - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(coldkey); - if !staking_hotkeys.contains(hotkey) { - staking_hotkeys.push(hotkey.clone()); - StakingHotkeys::::insert(coldkey, staking_hotkeys); - } - - next_uid = next_uid.checked_add(1).expect( - "should not have total number of hotkey accounts larger than u16::MAX", - ); - } - } - - // Set correct length for Subnet neurons - SubnetworkN::::insert(netuid, next_uid); - - // --- Increase total network count. - TotalNetworks::::mutate(|n| { - *n = n.checked_add(1).expect( - "should not have total number of networks larger than u16::MAX in genesis", - ) - }); - - // Get the root network uid. - let root_netuid: u16 = 0; - - // Set the root network as added. - NetworksAdded::::insert(root_netuid, true); - - // Increment the number of total networks. - TotalNetworks::::mutate(|n| { - *n = n.checked_add(1).expect( - "should not have total number of networks larger than u16::MAX in genesis", - ) - }); - // Set the number of validators to 1. - SubnetworkN::::insert(root_netuid, 0); - - // Set the maximum number to the number of senate members. - MaxAllowedUids::::insert(root_netuid, 64u16); - - // Set the maximum number to the number of validators to all members. - MaxAllowedValidators::::insert(root_netuid, 64u16); - - // Set the min allowed weights to zero, no weights restrictions. - MinAllowedWeights::::insert(root_netuid, 0); - - // Set the max weight limit to infinity, no weight restrictions. - MaxWeightsLimit::::insert(root_netuid, u16::MAX); - - // Add default root tempo. - Tempo::::insert(root_netuid, 100); - - // Set the root network as open. - NetworkRegistrationAllowed::::insert(root_netuid, true); - - // Set target registrations for validators as 1 per block. - TargetRegistrationsPerInterval::::insert(root_netuid, 1); - } - } - - // ================ - // ==== Hooks ===== - // ================ - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_idle(_n: BlockNumberFor, _remaining_weight: Weight) -> Weight { - Weight::zero() - } - - fn on_initialize(_block_number: BlockNumberFor) -> Weight { - let mut total_weight = Weight::zero(); - - // Create a Weight::MAX value to pass to swap_coldkeys_this_block - let max_weight = Weight::MAX; - - // Perform coldkey swapping - let swap_weight = match Self::swap_coldkeys_this_block(&max_weight) { - Ok(weight_used) => weight_used, - Err(e) => { - log::error!("Error while swapping coldkeys: {:?}", e); - Weight::zero() - } - }; - total_weight = total_weight.saturating_add(swap_weight); - - // Perform block step - let block_step_result = Self::block_step(); - match block_step_result { - Ok(_) => { - log::debug!("Successfully ran block step."); - total_weight = total_weight.saturating_add( - Weight::from_parts(110_634_229_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(8304_u64)) - .saturating_add(T::DbWeight::get().writes(110_u64)), - ); - } - Err(e) => { - log::error!("Error while stepping block: {:?}", e); - total_weight = total_weight.saturating_add( - Weight::from_parts(110_634_229_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(8304_u64)) - .saturating_add(T::DbWeight::get().writes(110_u64)), - ); - } - } - - total_weight - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - // --- Migrate storage - use crate::migration; - let mut weight = frame_support::weights::Weight::from_parts(0, 0); - - // Hex encoded foundation coldkey - let hex = hex_literal::hex![ - "feabaafee293d3b76dae304e2f9d885f77d2b17adab9e17e921b321eccd61c77" - ]; - weight = weight - // Initializes storage version (to 1) - .saturating_add(migration::migrate_to_v1_separate_emission::()) - // Storage version v1 -> v2 - .saturating_add(migration::migrate_to_v2_fixed_total_stake::()) - // Doesn't check storage version. TODO: Remove after upgrade - .saturating_add(migration::migrate_create_root_network::()) - // Storage version v2 -> v3 - .saturating_add(migration::migrate_transfer_ownership_to_foundation::( - hex, - )) - // Storage version v3 -> v4 - .saturating_add(migration::migrate_delete_subnet_21::()) - // Storage version v4 -> v5 - .saturating_add(migration::migrate_delete_subnet_3::()) - // Doesn't check storage version. TODO: Remove after upgrade - .saturating_add(migration::migration5_total_issuance::(false)) - // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_owned::()) - // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. - .saturating_add(migration::migrate_populate_staking_hotkeys::()) - // Fix total coldkey stake. - .saturating_add(migration::migrate_fix_total_coldkey_stake::()); - - weight - } - } - - /// Dispatchable functions allow users to interact with the pallet and invoke state changes. - /// These functions materialize as "extrinsics", which are often compared to transactions. - /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. - #[pallet::call] - impl Pallet { - /// --- Sets the caller weights for the incentive mechanism. The call can be - /// made from the hotkey account so is potentially insecure, however, the damage - /// of changing weights is minimal if caught early. This function includes all the - /// checks that the passed weights meet the requirements. Stored as u16s they represent - /// rational values in the range [0,1] which sum to 1 and can be interpreted as - /// probabilities. The specific weights determine how inflation propagates outward - /// from this peer. - /// - /// Note: The 16 bit integers weights should represent 1.0 as the max u16. - /// However, the function normalizes all integers to u16_max anyway. This means that if the sum of all - /// elements is larger or smaller than the amount of elements * u16_max, all elements - /// will be corrected for this deviation. - /// - /// # Args: - /// * `origin`: (Origin): - /// - The caller, a hotkey who wishes to set their weights. - /// - /// * `netuid` (u16): - /// - The network uid we are setting these weights on. - /// - /// * `dests` (Vec): - /// - The edge endpoint for the weight, i.e. j for w_ij. - /// - /// * 'weights' (Vec): - /// - The u16 integer encoded weights. Interpreted as rational - /// values in the range [0,1]. They must sum to in32::MAX. - /// - /// * 'version_key' ( u64 ): - /// - The network version key to check if the validator is up to date. - /// - /// # Event: - /// * WeightsSet; - /// - On successfully setting the weights on chain. - /// - /// # Raises: - /// * 'SubNetworkDoesNotExist': - /// - Attempting to set weights on a non-existent network. - /// - /// * 'NotRegistered': - /// - Attempting to set weights from a non registered account. - /// - /// * 'WeightVecNotEqualSize': - /// - Attempting to set weights with uids not of same length. - /// - /// * 'DuplicateUids': - /// - Attempting to set weights with duplicate uids. - /// - /// * 'UidsLengthExceedUidsInSubNet': - /// - Attempting to set weights above the max allowed uids. - /// - /// * 'UidVecContainInvalidOne': - /// - Attempting to set weights with invalid uids. - /// - /// * 'WeightVecLengthIsLow': - /// - Attempting to set weights with fewer weights than min. - /// - /// * 'MaxWeightExceeded': - /// - Attempting to set weights with max value exceeding limit. - #[pallet::call_index(0)] - #[pallet::weight((Weight::from_parts(22_060_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4106)) - .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] - pub fn set_weights( - origin: OriginFor, - netuid: u16, - dests: Vec, - weights: Vec, - version_key: u64, - ) -> DispatchResult { - if !Self::get_commit_reveal_weights_enabled(netuid) { - return Self::do_set_weights(origin, netuid, dests, weights, version_key); - } - - Err(Error::::CommitRevealEnabled.into()) - } - - /// ---- Used to commit a hash of your weight values to later be revealed. - /// - /// # Args: - /// * `origin`: (`::RuntimeOrigin`): - /// - The signature of the committing hotkey. - /// - /// * `netuid` (`u16`): - /// - The u16 network identifier. - /// - /// * `commit_hash` (`H256`): - /// - The hash representing the committed weights. - /// - /// # Raises: - /// * `WeightsCommitNotAllowed`: - /// - Attempting to commit when it is not allowed. - /// - #[pallet::call_index(96)] - #[pallet::weight((Weight::from_parts(46_000_000, 0) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] - pub fn commit_weights( - origin: T::RuntimeOrigin, - netuid: u16, - commit_hash: H256, - ) -> DispatchResult { - Self::do_commit_weights(origin, netuid, commit_hash) - } - - /// ---- Used to reveal the weights for a previously committed hash. - /// - /// # Args: - /// * `origin`: (`::RuntimeOrigin`): - /// - The signature of the revealing hotkey. - /// - /// * `netuid` (`u16`): - /// - The u16 network identifier. - /// - /// * `uids` (`Vec`): - /// - The uids for the weights being revealed. - /// - /// * `values` (`Vec`): - /// - The values of the weights being revealed. - /// - /// * `salt` (`Vec`): - /// - The random salt to protect from brute-force guessing attack in case of small weight changes bit-wise. - /// - /// * `version_key` (`u64`): - /// - The network version key. - /// - /// # Raises: - /// * `NoWeightsCommitFound`: - /// - Attempting to reveal weights without an existing commit. - /// - /// * `InvalidRevealCommitHashNotMatchTempo`: - /// - Attempting to reveal weights outside the valid tempo. - /// - /// * `InvalidRevealCommitHashNotMatch`: - /// - The revealed hash does not match the committed hash. - /// - #[pallet::call_index(97)] - #[pallet::weight((Weight::from_parts(103_000_000, 0) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Normal, Pays::No))] - pub fn reveal_weights( - origin: T::RuntimeOrigin, - netuid: u16, - uids: Vec, - values: Vec, - salt: Vec, - version_key: u64, - ) -> DispatchResult { - Self::do_reveal_weights(origin, netuid, uids, values, salt, version_key) - } - - /// # Args: - /// * `origin`: (Origin): - /// - The caller, a hotkey who wishes to set their weights. - /// - /// * `netuid` (u16): - /// - The network uid we are setting these weights on. - /// - /// * `hotkey` (T::AccountId): - /// - The hotkey associated with the operation and the calling coldkey. - /// - /// * `dests` (Vec): - /// - The edge endpoint for the weight, i.e. j for w_ij. - /// - /// * 'weights' (Vec): - /// - The u16 integer encoded weights. Interpreted as rational - /// values in the range [0,1]. They must sum to in32::MAX. - /// - /// * 'version_key' ( u64 ): - /// - The network version key to check if the validator is up to date. - /// - /// # Event: - /// - /// * WeightsSet; - /// - On successfully setting the weights on chain. - /// - /// # Raises: - /// - /// * NonAssociatedColdKey; - /// - Attempting to set weights on a non-associated cold key. - /// - /// * 'SubNetworkDoesNotExist': - /// - Attempting to set weights on a non-existent network. - /// - /// * 'NotRootSubnet': - /// - Attempting to set weights on a subnet that is not the root network. - /// - /// * 'WeightVecNotEqualSize': - /// - Attempting to set weights with uids not of same length. - /// - /// * 'UidVecContainInvalidOne': - /// - Attempting to set weights with invalid uids. - /// - /// * 'NotRegistered': - /// - Attempting to set weights from a non registered account. - /// - /// * 'WeightVecLengthIsLow': - /// - Attempting to set weights with fewer weights than min. - /// - /// * 'IncorrectWeightVersionKey': - /// - Attempting to set weights with the incorrect network version key. - /// - /// * 'SettingWeightsTooFast': - /// - Attempting to set weights too fast. - /// - /// * 'WeightVecLengthIsLow': - /// - Attempting to set weights with fewer weights than min. - /// - /// * 'MaxWeightExceeded': - /// - Attempting to set weights with max value exceeding limit. - /// - #[pallet::call_index(8)] - #[pallet::weight((Weight::from_parts(10_151_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4104)) - .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] - pub fn set_root_weights( - origin: OriginFor, - netuid: u16, - hotkey: T::AccountId, - dests: Vec, - weights: Vec, - version_key: u64, - ) -> DispatchResult { - Self::do_set_root_weights(origin, netuid, hotkey, dests, weights, version_key) - } - - /// --- Sets the key as a delegate. - /// - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'take' (u64): - /// - The stake proportion that this hotkey takes from delegations. - /// - /// # Event: - /// * DelegateAdded; - /// - On successfully setting a hotkey as a delegate. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldket. - /// - #[pallet::call_index(1)] - #[pallet::weight((Weight::from_parts(79_000_000, 0) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Normal, Pays::No))] - pub fn become_delegate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { - Self::do_become_delegate(origin, hotkey, Self::get_default_take()) - } - - /// --- Allows delegates to decrease its take value. - /// - /// # Args: - /// * 'origin': (::Origin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'netuid' (u16): - /// - Subnet ID to decrease take for - /// - /// * 'take' (u16): - /// - The new stake proportion that this hotkey takes from delegations. - /// The new value can be between 0 and 11_796 and should be strictly - /// lower than the previous value. It T is the new value (rational number), - /// the the parameter is calculated as [65535 * T]. For example, 1% would be - /// [0.01 * 65535] = [655.35] = 655 - /// - /// # Event: - /// * TakeDecreased; - /// - On successfully setting a decreased take for this hotkey. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldkey. - /// - /// * 'DelegateTakeTooLow': - /// - The delegate is setting a take which is not lower than the previous. - /// - #[pallet::call_index(65)] - #[pallet::weight((0, DispatchClass::Normal, Pays::No))] - pub fn decrease_take( - origin: OriginFor, - hotkey: T::AccountId, - take: u16, - ) -> DispatchResult { - Self::do_decrease_take(origin, hotkey, take) - } - - /// --- Allows delegates to increase its take value. This call is rate-limited. - /// - /// # Args: - /// * 'origin': (::Origin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'take' (u16): - /// - The new stake proportion that this hotkey takes from delegations. - /// The new value can be between 0 and 11_796 and should be strictly - /// greater than the previous value. T is the new value (rational number), - /// the the parameter is calculated as [65535 * T]. For example, 1% would be - /// [0.01 * 65535] = [655.35] = 655 - /// - /// # Event: - /// * TakeIncreased; - /// - On successfully setting a increased take for this hotkey. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldkey. - /// - /// * 'DelegateTakeTooHigh': - /// - The delegate is setting a take which is not greater than the previous. - /// - #[pallet::call_index(66)] - #[pallet::weight((0, DispatchClass::Normal, Pays::No))] - pub fn increase_take( - origin: OriginFor, - hotkey: T::AccountId, - take: u16, - ) -> DispatchResult { - Self::do_increase_take(origin, hotkey, take) - } - - /// --- Adds stake to a hotkey. The call is made from the - /// coldkey account linked in the hotkey. - /// Only the associated coldkey is allowed to make staking and - /// unstaking requests. This protects the neuron against - /// attacks on its hotkey running in production code. - /// - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The associated hotkey account. - /// - /// * 'amount_staked' (u64): - /// - The amount of stake to be added to the hotkey staking account. - /// - /// # Event: - /// * StakeAdded; - /// - On the successfully adding stake to a global account. - /// - /// # Raises: - /// * 'NotEnoughBalanceToStake': - /// - Not enough balance on the coldkey to add onto the global account. - /// - /// * 'NonAssociatedColdKey': - /// - The calling coldkey is not associated with this hotkey. - /// - /// * 'BalanceWithdrawalError': - /// - Errors stemming from transaction pallet. - /// - #[pallet::call_index(2)] - #[pallet::weight((Weight::from_parts(124_000_000, 0) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().writes(7)), DispatchClass::Normal, Pays::No))] - pub fn add_stake( - origin: OriginFor, - hotkey: T::AccountId, - amount_staked: u64, - ) -> DispatchResult { - Self::do_add_stake(origin, hotkey, amount_staked) - } - - /// Remove stake from the staking account. The call must be made - /// from the coldkey account attached to the neuron metadata. Only this key - /// has permission to make staking and unstaking requests. - /// - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The associated hotkey account. - /// - /// * 'amount_unstaked' (u64): - /// - The amount of stake to be added to the hotkey staking account. - /// - /// # Event: - /// * StakeRemoved; - /// - On the successfully removing stake from the hotkey account. - /// - /// # Raises: - /// * 'NotRegistered': - /// - Thrown if the account we are attempting to unstake from is non existent. - /// - /// * 'NonAssociatedColdKey': - /// - Thrown if the coldkey does not own the hotkey we are unstaking from. - /// - /// * 'NotEnoughStakeToWithdraw': - /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. - /// - #[pallet::call_index(3)] - #[pallet::weight((Weight::from_parts(111_000_000, 0) - .saturating_add(Weight::from_parts(0, 43991)) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().writes(7)), DispatchClass::Normal, Pays::No))] - pub fn remove_stake( - origin: OriginFor, - hotkey: T::AccountId, - amount_unstaked: u64, - ) -> DispatchResult { - Self::do_remove_stake(origin, hotkey, amount_unstaked) - } - - /// Serves or updates axon /promethteus information for the neuron associated with the caller. If the caller is - /// already registered the metadata is updated. If the caller is not registered this call throws NotRegistered. - /// - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the caller. - /// - /// * 'netuid' (u16): - /// - The u16 network identifier. - /// - /// * 'version' (u64): - /// - The bittensor version identifier. - /// - /// * 'ip' (u64): - /// - The endpoint ip information as a u128 encoded integer. - /// - /// * 'port' (u16): - /// - The endpoint port information as a u16 encoded integer. - /// - /// * 'ip_type' (u8): - /// - The endpoint ip version as a u8, 4 or 6. - /// - /// * 'protocol' (u8): - /// - UDP:1 or TCP:0 - /// - /// * 'placeholder1' (u8): - /// - Placeholder for further extra params. - /// - /// * 'placeholder2' (u8): - /// - Placeholder for further extra params. - /// - /// # Event: - /// * AxonServed; - /// - On successfully serving the axon info. - /// - /// # Raises: - /// * 'SubNetworkDoesNotExist': - /// - Attempting to set weights on a non-existent network. - /// - /// * 'NotRegistered': - /// - Attempting to set weights from a non registered account. - /// - /// * 'InvalidIpType': - /// - The ip type is not 4 or 6. - /// - /// * 'InvalidIpAddress': - /// - The numerically encoded ip address does not resolve to a proper ip. - /// - /// * 'ServingRateLimitExceeded': - /// - Attempting to set prometheus information withing the rate limit min. - /// - #[pallet::call_index(4)] - #[pallet::weight((Weight::from_parts(46_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] - pub fn serve_axon( - origin: OriginFor, - netuid: u16, - version: u32, - ip: u128, - port: u16, - ip_type: u8, - protocol: u8, - placeholder1: u8, - placeholder2: u8, - ) -> DispatchResult { - Self::do_serve_axon( - origin, - netuid, - version, - ip, - port, - ip_type, - protocol, - placeholder1, - placeholder2, - ) - } - - /// ---- Set prometheus information for the neuron. - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the calling hotkey. - /// - /// * 'netuid' (u16): - /// - The u16 network identifier. - /// - /// * 'version' (u16): - /// - The bittensor version identifier. - /// - /// * 'ip' (u128): - /// - The prometheus ip information as a u128 encoded integer. - /// - /// * 'port' (u16): - /// - The prometheus port information as a u16 encoded integer. - /// - /// * 'ip_type' (u8): - /// - The ip type v4 or v6. - /// - #[pallet::call_index(5)] - #[pallet::weight((Weight::from_parts(45_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] - pub fn serve_prometheus( - origin: OriginFor, - netuid: u16, - version: u32, - ip: u128, - port: u16, - ip_type: u8, - ) -> DispatchResult { - Self::do_serve_prometheus(origin, netuid, version, ip, port, ip_type) - } - - /// ---- Registers a new neuron to the subnetwork. - /// - /// # Args: - /// * 'origin': (Origin): - /// - The signature of the calling hotkey. - /// - /// * 'netuid' (u16): - /// - The u16 network identifier. - /// - /// * 'block_number' ( u64 ): - /// - Block hash used to prove work done. - /// - /// * 'nonce' ( u64 ): - /// - Positive integer nonce used in POW. - /// - /// * 'work' ( Vec ): - /// - Vector encoded bytes representing work done. - /// - /// * 'hotkey' ( T::AccountId ): - /// - Hotkey to be registered to the network. - /// - /// * 'coldkey' ( T::AccountId ): - /// - Associated coldkey account. - /// - /// # Event: - /// * NeuronRegistered; - /// - On successfully registering a uid to a neuron slot on a subnetwork. - /// - /// # Raises: - /// * 'SubNetworkDoesNotExist': - /// - Attempting to register to a non existent network. - /// - /// * 'TooManyRegistrationsThisBlock': - /// - This registration exceeds the total allowed on this network this block. - /// - /// * 'HotKeyAlreadyRegisteredInSubNet': - /// - The hotkey is already registered on this network. - /// - /// * 'InvalidWorkBlock': - /// - The work has been performed on a stale, future, or non existent block. - /// - /// * 'InvalidDifficulty': - /// - The work does not match the difficulty. - /// - /// * 'InvalidSeal': - /// - The seal is incorrect. - /// - #[pallet::call_index(6)] - #[pallet::weight((Weight::from_parts(192_000_000, 0) - .saturating_add(T::DbWeight::get().reads(24)) - .saturating_add(T::DbWeight::get().writes(22)), DispatchClass::Normal, Pays::No))] - pub fn register( - origin: OriginFor, - netuid: u16, - block_number: u64, - nonce: u64, - work: Vec, - hotkey: T::AccountId, - coldkey: T::AccountId, - ) -> DispatchResult { - Self::do_registration(origin, netuid, block_number, nonce, work, hotkey, coldkey) - } - - /// Register the hotkey to root network - #[pallet::call_index(62)] - #[pallet::weight((Weight::from_parts(164_000_000, 0) - .saturating_add(T::DbWeight::get().reads(23)) - .saturating_add(T::DbWeight::get().writes(20)), DispatchClass::Normal, Pays::No))] - pub fn root_register(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { - Self::do_root_register(origin, hotkey) - } - - /// Attempt to adjust the senate membership to include a hotkey - #[pallet::call_index(63)] - #[pallet::weight((Weight::from_parts(0, 0) - .saturating_add(T::DbWeight::get().reads(0)) - .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::Yes))] - pub fn adjust_senate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { - Self::do_adjust_senate(origin, hotkey) - } - - /// User register a new subnetwork via burning token - #[pallet::call_index(7)] - #[pallet::weight((Weight::from_parts(177_000_000, 0) - .saturating_add(T::DbWeight::get().reads(26)) - .saturating_add(T::DbWeight::get().writes(24)), DispatchClass::Normal, Pays::No))] - pub fn burned_register( - origin: OriginFor, - netuid: u16, - hotkey: T::AccountId, - ) -> DispatchResult { - Self::do_burned_registration(origin, netuid, hotkey) - } - - /// The extrinsic for user to change its hotkey - ///#[pallet::call_index(70)] - ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) - ///.saturating_add(T::DbWeight::get().reads(272)) - ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - ///pub fn swap_hotkey( - /// origin: OriginFor, - /// hotkey: T::AccountId, - /// new_hotkey: T::AccountId, - ///) -> DispatchResultWithPostInfo { - /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) - ///} - - /// 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 - /// - /// 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(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - pub fn swap_coldkey( - origin: OriginFor, - new_coldkey: T::AccountId, - ) -> DispatchResultWithPostInfo { - Self::do_swap_coldkey(origin, &new_coldkey) - } - /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. - /// - /// # Arguments - /// - /// * `origin` - The origin of the call, must be signed by the current coldkey. - /// * `hotkey` - The hotkey associated with the stakes to be unstaked. - /// * `new_coldkey` - The new coldkey to receive the unstaked tokens. - /// - /// # Returns - /// - /// Returns a `DispatchResult` indicating success or failure of the operation. - /// - /// # Weight - /// - /// Weight is calculated based on the number of database reads and writes. - #[cfg(test)] - #[pallet::call_index(72)] - #[pallet::weight((Weight::from_parts(21_000_000, 0) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] - pub fn schedule_coldkey_swap( - origin: OriginFor, - new_coldkey: T::AccountId, - work: Vec, - block_number: u64, - nonce: u64, - ) -> DispatchResult { - // Attain the calling coldkey from the origin. - let old_coldkey: T::AccountId = ensure_signed(origin)?; - Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey, work, block_number, nonce) - } - - // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ - - // ================================== - // ==== Parameter Sudo calls ======== - // ================================== - // Each function sets the corresponding hyper paramter on the specified network - // Args: - // * 'origin': (Origin): - // - The caller, must be sudo. - // - // * `netuid` (u16): - // - The network identifier. - // - // * `hyperparameter value` (u16): - // - The value of the hyper parameter. - // - - /// Authenticates a council proposal and dispatches a function call with `Root` origin. - /// - /// The dispatch origin for this call must be a council majority. - /// - /// ## Complexity - /// - O(1). - #[pallet::call_index(51)] - #[pallet::weight((Weight::from_parts(0, 0), DispatchClass::Operational, Pays::No))] - pub fn sudo( - origin: OriginFor, - call: Box, - ) -> DispatchResultWithPostInfo { - // This is a public call, so we ensure that the origin is a council majority. - T::CouncilOrigin::ensure_origin(origin)?; - - let result = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); - let error = result.map(|_| ()).map_err(|e| e.error); - Self::deposit_event(Event::Sudid(error)); - - return result; - } - - /// Authenticates a council proposal and dispatches a function call with `Root` origin. - /// This function does not check the weight of the call, and instead allows the - /// user to specify the weight of the call. - /// - /// The dispatch origin for this call must be a council majority. - /// - /// ## Complexity - /// - O(1). - #[allow(deprecated)] - #[pallet::call_index(52)] - #[pallet::weight((*weight, call.get_dispatch_info().class, Pays::No))] - pub fn sudo_unchecked_weight( - origin: OriginFor, - call: Box, - weight: Weight, - ) -> DispatchResultWithPostInfo { - // We dont need to check the weight witness, suppress warning. - // See https://github.com/paritytech/polkadot-sdk/pull/1818. - let _ = weight; - - // This is a public call, so we ensure that the origin is a council majority. - T::CouncilOrigin::ensure_origin(origin)?; - - let result = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); - let error = result.map(|_| ()).map_err(|e| e.error); - Self::deposit_event(Event::Sudid(error)); - - return result; - } - - /// User vote on a proposal - #[pallet::call_index(55)] - #[pallet::weight((Weight::from_parts(0, 0) - .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().reads(0)) - .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Operational))] - pub fn vote( - origin: OriginFor, - hotkey: T::AccountId, - proposal: T::Hash, - #[pallet::compact] index: u32, - approve: bool, - ) -> DispatchResultWithPostInfo { - Self::do_vote_root(origin, &hotkey, proposal, index, approve) - } - - /// User register a new subnetwork - #[pallet::call_index(59)] - #[pallet::weight((Weight::from_parts(157_000_000, 0) - .saturating_add(T::DbWeight::get().reads(16)) - .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] - pub fn register_network(origin: OriginFor) -> DispatchResult { - Self::user_add_network(origin) - } - - /// Facility extrinsic for user to get taken from faucet - /// It is only available when pow-faucet feature enabled - /// Just deployed in testnet and devnet for testing purpose - #[pallet::call_index(60)] - #[pallet::weight((Weight::from_parts(91_000_000, 0) - .saturating_add(T::DbWeight::get().reads(27)) - .saturating_add(T::DbWeight::get().writes(22)), DispatchClass::Normal, Pays::No))] - pub fn faucet( - origin: OriginFor, - block_number: u64, - nonce: u64, - work: Vec, - ) -> DispatchResult { - if cfg!(feature = "pow-faucet") { - return Self::do_faucet(origin, block_number, nonce, work); - } - - Err(Error::::FaucetDisabled.into()) - } - - /// Remove a user's subnetwork - /// The caller must be the owner of the network - #[pallet::call_index(61)] - #[pallet::weight((Weight::from_parts(119_000_000, 0) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] - pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { - Self::user_remove_network(origin, netuid) - } - - /// Sets values for liquid alpha - #[pallet::call_index(64)] - #[pallet::weight((0, DispatchClass::Operational, Pays::No))] - pub fn sudo_hotfix_swap_coldkey_delegates( - _origin: OriginFor, - _old_coldkey: T::AccountId, - _new_coldkey: T::AccountId, - ) -> DispatchResult { - Ok(()) - } - } - // ---- Subtensor helper functions. impl Pallet { /// Returns the transaction priority for setting weights. diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs new file mode 100644 index 000000000..e54cd222f --- /dev/null +++ b/pallets/subtensor/src/macros/config.rs @@ -0,0 +1,175 @@ +use frame_support::pallet_macros::pallet_section; + +/// A [`pallet_section`] that defines the errors for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod config { + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// A sudo-able call. + type SudoRuntimeCall: Parameter + + UnfilteredDispatchable + + GetDispatchInfo; + + /// Origin checking for council majority + type CouncilOrigin: EnsureOrigin; + + /// Currency type that will be used to place deposits on neurons + type Currency: fungible::Balanced + + fungible::Mutate; + + /// Senate members with members management functions. + type SenateMembers: crate::MemberManagement; + + /// Interface to allow other pallets to control who can register identities + type TriumvirateInterface: crate::CollectiveInterface; + + /// ================================= + /// ==== Initial Value Constants ==== + /// ================================= + + /// Initial currency issuance. + #[pallet::constant] + type InitialIssuance: Get; + /// Initial min allowed weights setting. + #[pallet::constant] + type InitialMinAllowedWeights: Get; + /// Initial Emission Ratio. + #[pallet::constant] + type InitialEmissionValue: Get; + /// Initial max weight limit. + #[pallet::constant] + type InitialMaxWeightsLimit: Get; + /// Tempo for each network. + #[pallet::constant] + type InitialTempo: Get; + /// Initial Difficulty. + #[pallet::constant] + type InitialDifficulty: Get; + /// Initial Max Difficulty. + #[pallet::constant] + type InitialMaxDifficulty: Get; + /// Initial Min Difficulty. + #[pallet::constant] + type InitialMinDifficulty: Get; + /// Initial RAO Recycled. + #[pallet::constant] + type InitialRAORecycledForRegistration: Get; + /// Initial Burn. + #[pallet::constant] + type InitialBurn: Get; + /// Initial Max Burn. + #[pallet::constant] + type InitialMaxBurn: Get; + /// Initial Min Burn. + #[pallet::constant] + type InitialMinBurn: Get; + /// Initial adjustment interval. + #[pallet::constant] + type InitialAdjustmentInterval: Get; + /// Initial bonds moving average. + #[pallet::constant] + type InitialBondsMovingAverage: Get; + /// Initial target registrations per interval. + #[pallet::constant] + type InitialTargetRegistrationsPerInterval: Get; + /// Rho constant. + #[pallet::constant] + type InitialRho: Get; + /// Kappa constant. + #[pallet::constant] + type InitialKappa: Get; + /// Max UID constant. + #[pallet::constant] + type InitialMaxAllowedUids: Get; + /// Initial validator context pruning length. + #[pallet::constant] + type InitialValidatorPruneLen: Get; + /// Initial scaling law power. + #[pallet::constant] + type InitialScalingLawPower: Get; + /// Immunity Period Constant. + #[pallet::constant] + type InitialImmunityPeriod: Get; + /// Activity constant. + #[pallet::constant] + type InitialActivityCutoff: Get; + /// Initial max registrations per block. + #[pallet::constant] + type InitialMaxRegistrationsPerBlock: Get; + /// Initial pruning score for each neuron. + #[pallet::constant] + type InitialPruningScore: Get; + /// Initial maximum allowed validators per network. + #[pallet::constant] + type InitialMaxAllowedValidators: Get; + /// Initial default delegation take. + #[pallet::constant] + type InitialDefaultTake: Get; + /// Initial minimum delegation take. + #[pallet::constant] + type InitialMinTake: Get; + /// Initial weights version key. + #[pallet::constant] + type InitialWeightsVersionKey: Get; + /// Initial serving rate limit. + #[pallet::constant] + type InitialServingRateLimit: Get; + /// Initial transaction rate limit. + #[pallet::constant] + type InitialTxRateLimit: Get; + /// Initial delegate take transaction rate limit. + #[pallet::constant] + type InitialTxDelegateTakeRateLimit: Get; + /// Initial percentage of total stake required to join senate. + #[pallet::constant] + type InitialSenateRequiredStakePercentage: Get; + /// Initial adjustment alpha on burn and pow. + #[pallet::constant] + type InitialAdjustmentAlpha: Get; + /// Initial network immunity period + #[pallet::constant] + type InitialNetworkImmunityPeriod: Get; + /// Initial minimum allowed network UIDs + #[pallet::constant] + type InitialNetworkMinAllowedUids: Get; + /// Initial network minimum burn cost + #[pallet::constant] + type InitialNetworkMinLockCost: Get; + /// Initial network subnet cut. + #[pallet::constant] + type InitialSubnetOwnerCut: Get; + /// Initial lock reduction interval. + #[pallet::constant] + type InitialNetworkLockReductionInterval: Get; + /// Initial max allowed subnets + #[pallet::constant] + type InitialSubnetLimit: Get; + /// Initial network creation rate limit + #[pallet::constant] + type InitialNetworkRateLimit: Get; + /// Initial target stakes per interval issuance. + #[pallet::constant] + type InitialTargetStakesPerInterval: Get; + /// Cost of swapping a hotkey. + #[pallet::constant] + type KeySwapCost: Get; + /// The upper bound for the alpha parameter. Used for Liquid Alpha. + #[pallet::constant] + type AlphaHigh: Get; + /// The lower bound for the alpha parameter. Used for Liquid Alpha. + #[pallet::constant] + type AlphaLow: Get; + /// A flag to indicate if Liquid Alpha is enabled. + #[pallet::constant] + type LiquidAlphaOn: Get; + /// The base difficulty for proof of work for coldkey swaps + #[pallet::constant] + type InitialBaseDifficulty: Get; + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs new file mode 100644 index 000000000..62b02f6a4 --- /dev/null +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -0,0 +1,847 @@ +use frame_support::pallet_macros::pallet_section; + +/// A [`pallet_section`] that defines the errors for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod dispatches { + /// Dispatchable functions allow users to interact with the pallet and invoke state changes. + /// These functions materialize as "extrinsics", which are often compared to transactions. + /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// --- Sets the caller weights for the incentive mechanism. The call can be + /// made from the hotkey account so is potentially insecure, however, the damage + /// of changing weights is minimal if caught early. This function includes all the + /// checks that the passed weights meet the requirements. Stored as u16s they represent + /// rational values in the range [0,1] which sum to 1 and can be interpreted as + /// probabilities. The specific weights determine how inflation propagates outward + /// from this peer. + /// + /// Note: The 16 bit integers weights should represent 1.0 as the max u16. + /// However, the function normalizes all integers to u16_max anyway. This means that if the sum of all + /// elements is larger or smaller than the amount of elements * u16_max, all elements + /// will be corrected for this deviation. + /// + /// # Args: + /// * `origin`: (Origin): + /// - The caller, a hotkey who wishes to set their weights. + /// + /// * `netuid` (u16): + /// - The network uid we are setting these weights on. + /// + /// * `dests` (Vec): + /// - The edge endpoint for the weight, i.e. j for w_ij. + /// + /// * 'weights' (Vec): + /// - The u16 integer encoded weights. Interpreted as rational + /// values in the range [0,1]. They must sum to in32::MAX. + /// + /// * 'version_key' ( u64 ): + /// - The network version key to check if the validator is up to date. + /// + /// # Event: + /// * WeightsSet; + /// - On successfully setting the weights on chain. + /// + /// # Raises: + /// * 'SubNetworkDoesNotExist': + /// - Attempting to set weights on a non-existent network. + /// + /// * 'NotRegistered': + /// - Attempting to set weights from a non registered account. + /// + /// * 'WeightVecNotEqualSize': + /// - Attempting to set weights with uids not of same length. + /// + /// * 'DuplicateUids': + /// - Attempting to set weights with duplicate uids. + /// + /// * 'UidsLengthExceedUidsInSubNet': + /// - Attempting to set weights above the max allowed uids. + /// + /// * 'UidVecContainInvalidOne': + /// - Attempting to set weights with invalid uids. + /// + /// * 'WeightVecLengthIsLow': + /// - Attempting to set weights with fewer weights than min. + /// + /// * 'MaxWeightExceeded': + /// - Attempting to set weights with max value exceeding limit. + #[pallet::call_index(0)] + #[pallet::weight((Weight::from_parts(22_060_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4106)) + .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] + pub fn set_weights( + origin: OriginFor, + netuid: u16, + dests: Vec, + weights: Vec, + version_key: u64, + ) -> DispatchResult { + if !Self::get_commit_reveal_weights_enabled(netuid) { + return Self::do_set_weights(origin, netuid, dests, weights, version_key); + } + + Err(Error::::CommitRevealEnabled.into()) + } + + /// ---- Used to commit a hash of your weight values to later be revealed. + /// + /// # Args: + /// * `origin`: (`::RuntimeOrigin`): + /// - The signature of the committing hotkey. + /// + /// * `netuid` (`u16`): + /// - The u16 network identifier. + /// + /// * `commit_hash` (`H256`): + /// - The hash representing the committed weights. + /// + /// # Raises: + /// * `WeightsCommitNotAllowed`: + /// - Attempting to commit when it is not allowed. + /// + #[pallet::call_index(96)] + #[pallet::weight((Weight::from_parts(46_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] + pub fn commit_weights( + origin: T::RuntimeOrigin, + netuid: u16, + commit_hash: H256, + ) -> DispatchResult { + Self::do_commit_weights(origin, netuid, commit_hash) + } + + /// ---- Used to reveal the weights for a previously committed hash. + /// + /// # Args: + /// * `origin`: (`::RuntimeOrigin`): + /// - The signature of the revealing hotkey. + /// + /// * `netuid` (`u16`): + /// - The u16 network identifier. + /// + /// * `uids` (`Vec`): + /// - The uids for the weights being revealed. + /// + /// * `values` (`Vec`): + /// - The values of the weights being revealed. + /// + /// * `salt` (`Vec`): + /// - The random salt to protect from brute-force guessing attack in case of small weight changes bit-wise. + /// + /// * `version_key` (`u64`): + /// - The network version key. + /// + /// # Raises: + /// * `NoWeightsCommitFound`: + /// - Attempting to reveal weights without an existing commit. + /// + /// * `InvalidRevealCommitHashNotMatchTempo`: + /// - Attempting to reveal weights outside the valid tempo. + /// + /// * `InvalidRevealCommitHashNotMatch`: + /// - The revealed hash does not match the committed hash. + /// + #[pallet::call_index(97)] + #[pallet::weight((Weight::from_parts(103_000_000, 0) + .saturating_add(T::DbWeight::get().reads(11)) + .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Normal, Pays::No))] + pub fn reveal_weights( + origin: T::RuntimeOrigin, + netuid: u16, + uids: Vec, + values: Vec, + salt: Vec, + version_key: u64, + ) -> DispatchResult { + Self::do_reveal_weights(origin, netuid, uids, values, salt, version_key) + } + + /// # Args: + /// * `origin`: (Origin): + /// - The caller, a hotkey who wishes to set their weights. + /// + /// * `netuid` (u16): + /// - The network uid we are setting these weights on. + /// + /// * `hotkey` (T::AccountId): + /// - The hotkey associated with the operation and the calling coldkey. + /// + /// * `dests` (Vec): + /// - The edge endpoint for the weight, i.e. j for w_ij. + /// + /// * 'weights' (Vec): + /// - The u16 integer encoded weights. Interpreted as rational + /// values in the range [0,1]. They must sum to in32::MAX. + /// + /// * 'version_key' ( u64 ): + /// - The network version key to check if the validator is up to date. + /// + /// # Event: + /// + /// * WeightsSet; + /// - On successfully setting the weights on chain. + /// + /// # Raises: + /// + /// * NonAssociatedColdKey; + /// - Attempting to set weights on a non-associated cold key. + /// + /// * 'SubNetworkDoesNotExist': + /// - Attempting to set weights on a non-existent network. + /// + /// * 'NotRootSubnet': + /// - Attempting to set weights on a subnet that is not the root network. + /// + /// * 'WeightVecNotEqualSize': + /// - Attempting to set weights with uids not of same length. + /// + /// * 'UidVecContainInvalidOne': + /// - Attempting to set weights with invalid uids. + /// + /// * 'NotRegistered': + /// - Attempting to set weights from a non registered account. + /// + /// * 'WeightVecLengthIsLow': + /// - Attempting to set weights with fewer weights than min. + /// + /// * 'IncorrectWeightVersionKey': + /// - Attempting to set weights with the incorrect network version key. + /// + /// * 'SettingWeightsTooFast': + /// - Attempting to set weights too fast. + /// + /// * 'WeightVecLengthIsLow': + /// - Attempting to set weights with fewer weights than min. + /// + /// * 'MaxWeightExceeded': + /// - Attempting to set weights with max value exceeding limit. + /// + #[pallet::call_index(8)] + #[pallet::weight((Weight::from_parts(10_151_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4104)) + .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] + pub fn set_root_weights( + origin: OriginFor, + netuid: u16, + hotkey: T::AccountId, + dests: Vec, + weights: Vec, + version_key: u64, + ) -> DispatchResult { + Self::do_set_root_weights(origin, netuid, hotkey, dests, weights, version_key) + } + + /// --- Sets the key as a delegate. + /// + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'take' (u64): + /// - The stake proportion that this hotkey takes from delegations. + /// + /// # Event: + /// * DelegateAdded; + /// - On successfully setting a hotkey as a delegate. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldket. + /// + #[pallet::call_index(1)] + #[pallet::weight((Weight::from_parts(79_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Normal, Pays::No))] + pub fn become_delegate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { + Self::do_become_delegate(origin, hotkey, Self::get_default_take()) + } + + /// --- Allows delegates to decrease its take value. + /// + /// # Args: + /// * 'origin': (::Origin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'netuid' (u16): + /// - Subnet ID to decrease take for + /// + /// * 'take' (u16): + /// - The new stake proportion that this hotkey takes from delegations. + /// The new value can be between 0 and 11_796 and should be strictly + /// lower than the previous value. It T is the new value (rational number), + /// the the parameter is calculated as [65535 * T]. For example, 1% would be + /// [0.01 * 65535] = [655.35] = 655 + /// + /// # Event: + /// * TakeDecreased; + /// - On successfully setting a decreased take for this hotkey. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldkey. + /// + /// * 'DelegateTakeTooLow': + /// - The delegate is setting a take which is not lower than the previous. + /// + #[pallet::call_index(65)] + #[pallet::weight((0, DispatchClass::Normal, Pays::No))] + pub fn decrease_take( + origin: OriginFor, + hotkey: T::AccountId, + take: u16, + ) -> DispatchResult { + Self::do_decrease_take(origin, hotkey, take) + } + + /// --- Allows delegates to increase its take value. This call is rate-limited. + /// + /// # Args: + /// * 'origin': (::Origin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'take' (u16): + /// - The new stake proportion that this hotkey takes from delegations. + /// The new value can be between 0 and 11_796 and should be strictly + /// greater than the previous value. T is the new value (rational number), + /// the the parameter is calculated as [65535 * T]. For example, 1% would be + /// [0.01 * 65535] = [655.35] = 655 + /// + /// # Event: + /// * TakeIncreased; + /// - On successfully setting a increased take for this hotkey. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldkey. + /// + /// * 'DelegateTakeTooHigh': + /// - The delegate is setting a take which is not greater than the previous. + /// + #[pallet::call_index(66)] + #[pallet::weight((0, DispatchClass::Normal, Pays::No))] + pub fn increase_take( + origin: OriginFor, + hotkey: T::AccountId, + take: u16, + ) -> DispatchResult { + Self::do_increase_take(origin, hotkey, take) + } + + /// --- Adds stake to a hotkey. The call is made from the + /// coldkey account linked in the hotkey. + /// Only the associated coldkey is allowed to make staking and + /// unstaking requests. This protects the neuron against + /// attacks on its hotkey running in production code. + /// + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The associated hotkey account. + /// + /// * 'amount_staked' (u64): + /// - The amount of stake to be added to the hotkey staking account. + /// + /// # Event: + /// * StakeAdded; + /// - On the successfully adding stake to a global account. + /// + /// # Raises: + /// * 'NotEnoughBalanceToStake': + /// - Not enough balance on the coldkey to add onto the global account. + /// + /// * 'NonAssociatedColdKey': + /// - The calling coldkey is not associated with this hotkey. + /// + /// * 'BalanceWithdrawalError': + /// - Errors stemming from transaction pallet. + /// + #[pallet::call_index(2)] + #[pallet::weight((Weight::from_parts(124_000_000, 0) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(7)), DispatchClass::Normal, Pays::No))] + pub fn add_stake( + origin: OriginFor, + hotkey: T::AccountId, + amount_staked: u64, + ) -> DispatchResult { + Self::do_add_stake(origin, hotkey, amount_staked) + } + + /// Remove stake from the staking account. The call must be made + /// from the coldkey account attached to the neuron metadata. Only this key + /// has permission to make staking and unstaking requests. + /// + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The associated hotkey account. + /// + /// * 'amount_unstaked' (u64): + /// - The amount of stake to be added to the hotkey staking account. + /// + /// # Event: + /// * StakeRemoved; + /// - On the successfully removing stake from the hotkey account. + /// + /// # Raises: + /// * 'NotRegistered': + /// - Thrown if the account we are attempting to unstake from is non existent. + /// + /// * 'NonAssociatedColdKey': + /// - Thrown if the coldkey does not own the hotkey we are unstaking from. + /// + /// * 'NotEnoughStakeToWithdraw': + /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. + /// + #[pallet::call_index(3)] + #[pallet::weight((Weight::from_parts(111_000_000, 0) + .saturating_add(Weight::from_parts(0, 43991)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(7)), DispatchClass::Normal, Pays::No))] + pub fn remove_stake( + origin: OriginFor, + hotkey: T::AccountId, + amount_unstaked: u64, + ) -> DispatchResult { + Self::do_remove_stake(origin, hotkey, amount_unstaked) + } + + /// Serves or updates axon /promethteus information for the neuron associated with the caller. If the caller is + /// already registered the metadata is updated. If the caller is not registered this call throws NotRegistered. + /// + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the caller. + /// + /// * 'netuid' (u16): + /// - The u16 network identifier. + /// + /// * 'version' (u64): + /// - The bittensor version identifier. + /// + /// * 'ip' (u64): + /// - The endpoint ip information as a u128 encoded integer. + /// + /// * 'port' (u16): + /// - The endpoint port information as a u16 encoded integer. + /// + /// * 'ip_type' (u8): + /// - The endpoint ip version as a u8, 4 or 6. + /// + /// * 'protocol' (u8): + /// - UDP:1 or TCP:0 + /// + /// * 'placeholder1' (u8): + /// - Placeholder for further extra params. + /// + /// * 'placeholder2' (u8): + /// - Placeholder for further extra params. + /// + /// # Event: + /// * AxonServed; + /// - On successfully serving the axon info. + /// + /// # Raises: + /// * 'SubNetworkDoesNotExist': + /// - Attempting to set weights on a non-existent network. + /// + /// * 'NotRegistered': + /// - Attempting to set weights from a non registered account. + /// + /// * 'InvalidIpType': + /// - The ip type is not 4 or 6. + /// + /// * 'InvalidIpAddress': + /// - The numerically encoded ip address does not resolve to a proper ip. + /// + /// * 'ServingRateLimitExceeded': + /// - Attempting to set prometheus information withing the rate limit min. + /// + #[pallet::call_index(4)] + #[pallet::weight((Weight::from_parts(46_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] + pub fn serve_axon( + origin: OriginFor, + netuid: u16, + version: u32, + ip: u128, + port: u16, + ip_type: u8, + protocol: u8, + placeholder1: u8, + placeholder2: u8, + ) -> DispatchResult { + Self::do_serve_axon( + origin, + netuid, + version, + ip, + port, + ip_type, + protocol, + placeholder1, + placeholder2, + ) + } + + /// ---- Set prometheus information for the neuron. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the calling hotkey. + /// + /// * 'netuid' (u16): + /// - The u16 network identifier. + /// + /// * 'version' (u16): + /// - The bittensor version identifier. + /// + /// * 'ip' (u128): + /// - The prometheus ip information as a u128 encoded integer. + /// + /// * 'port' (u16): + /// - The prometheus port information as a u16 encoded integer. + /// + /// * 'ip_type' (u8): + /// - The ip type v4 or v6. + /// + #[pallet::call_index(5)] + #[pallet::weight((Weight::from_parts(45_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] + pub fn serve_prometheus( + origin: OriginFor, + netuid: u16, + version: u32, + ip: u128, + port: u16, + ip_type: u8, + ) -> DispatchResult { + Self::do_serve_prometheus(origin, netuid, version, ip, port, ip_type) + } + + /// ---- Registers a new neuron to the subnetwork. + /// + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the calling hotkey. + /// + /// * 'netuid' (u16): + /// - The u16 network identifier. + /// + /// * 'block_number' ( u64 ): + /// - Block hash used to prove work done. + /// + /// * 'nonce' ( u64 ): + /// - Positive integer nonce used in POW. + /// + /// * 'work' ( Vec ): + /// - Vector encoded bytes representing work done. + /// + /// * 'hotkey' ( T::AccountId ): + /// - Hotkey to be registered to the network. + /// + /// * 'coldkey' ( T::AccountId ): + /// - Associated coldkey account. + /// + /// # Event: + /// * NeuronRegistered; + /// - On successfully registering a uid to a neuron slot on a subnetwork. + /// + /// # Raises: + /// * 'SubNetworkDoesNotExist': + /// - Attempting to register to a non existent network. + /// + /// * 'TooManyRegistrationsThisBlock': + /// - This registration exceeds the total allowed on this network this block. + /// + /// * 'HotKeyAlreadyRegisteredInSubNet': + /// - The hotkey is already registered on this network. + /// + /// * 'InvalidWorkBlock': + /// - The work has been performed on a stale, future, or non existent block. + /// + /// * 'InvalidDifficulty': + /// - The work does not match the difficulty. + /// + /// * 'InvalidSeal': + /// - The seal is incorrect. + /// + #[pallet::call_index(6)] + #[pallet::weight((Weight::from_parts(192_000_000, 0) + .saturating_add(T::DbWeight::get().reads(24)) + .saturating_add(T::DbWeight::get().writes(22)), DispatchClass::Normal, Pays::No))] + pub fn register( + origin: OriginFor, + netuid: u16, + block_number: u64, + nonce: u64, + work: Vec, + hotkey: T::AccountId, + coldkey: T::AccountId, + ) -> DispatchResult { + Self::do_registration(origin, netuid, block_number, nonce, work, hotkey, coldkey) + } + + /// Register the hotkey to root network + #[pallet::call_index(62)] + #[pallet::weight((Weight::from_parts(164_000_000, 0) + .saturating_add(T::DbWeight::get().reads(23)) + .saturating_add(T::DbWeight::get().writes(20)), DispatchClass::Normal, Pays::No))] + pub fn root_register(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { + Self::do_root_register(origin, hotkey) + } + + /// Attempt to adjust the senate membership to include a hotkey + #[pallet::call_index(63)] + #[pallet::weight((Weight::from_parts(0, 0) + .saturating_add(T::DbWeight::get().reads(0)) + .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::Yes))] + pub fn adjust_senate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { + Self::do_adjust_senate(origin, hotkey) + } + + /// User register a new subnetwork via burning token + #[pallet::call_index(7)] + #[pallet::weight((Weight::from_parts(177_000_000, 0) + .saturating_add(T::DbWeight::get().reads(26)) + .saturating_add(T::DbWeight::get().writes(24)), DispatchClass::Normal, Pays::No))] + pub fn burned_register( + origin: OriginFor, + netuid: u16, + hotkey: T::AccountId, + ) -> DispatchResult { + Self::do_burned_registration(origin, netuid, hotkey) + } + + /// The extrinsic for user to change its hotkey + ///#[pallet::call_index(70)] + ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) + ///.saturating_add(T::DbWeight::get().reads(272)) + ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + ///pub fn swap_hotkey( + /// origin: OriginFor, + /// hotkey: T::AccountId, + /// new_hotkey: T::AccountId, + ///) -> DispatchResultWithPostInfo { + /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + ///} + + /// 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 + /// + /// 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(1_940_000_000, 0) + .saturating_add(T::DbWeight::get().reads(272)) + .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + pub fn swap_coldkey( + origin: OriginFor, + new_coldkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::do_swap_coldkey(origin, &new_coldkey) + } + /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, must be signed by the current coldkey. + /// * `hotkey` - The hotkey associated with the stakes to be unstaked. + /// * `new_coldkey` - The new coldkey to receive the unstaked tokens. + /// + /// # Returns + /// + /// Returns a `DispatchResult` indicating success or failure of the operation. + /// + /// # Weight + /// + /// Weight is calculated based on the number of database reads and writes. + #[cfg(test)] + #[pallet::call_index(72)] + #[pallet::weight((Weight::from_parts(21_000_000, 0) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] + pub fn schedule_coldkey_swap( + origin: OriginFor, + new_coldkey: T::AccountId, + work: Vec, + block_number: u64, + nonce: u64, + ) -> DispatchResult { + // Attain the calling coldkey from the origin. + let old_coldkey: T::AccountId = ensure_signed(origin)?; + Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey, work, block_number, nonce) + } + + // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ + + // ================================== + // ==== Parameter Sudo calls ======== + // ================================== + // Each function sets the corresponding hyper paramter on the specified network + // Args: + // * 'origin': (Origin): + // - The caller, must be sudo. + // + // * `netuid` (u16): + // - The network identifier. + // + // * `hyperparameter value` (u16): + // - The value of the hyper parameter. + // + + /// Authenticates a council proposal and dispatches a function call with `Root` origin. + /// + /// The dispatch origin for this call must be a council majority. + /// + /// ## Complexity + /// - O(1). + #[pallet::call_index(51)] + #[pallet::weight((Weight::from_parts(0, 0), DispatchClass::Operational, Pays::No))] + pub fn sudo( + origin: OriginFor, + call: Box, + ) -> DispatchResultWithPostInfo { + // This is a public call, so we ensure that the origin is a council majority. + T::CouncilOrigin::ensure_origin(origin)?; + + let result = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + let error = result.map(|_| ()).map_err(|e| e.error); + Self::deposit_event(Event::Sudid(error)); + + return result; + } + + /// Authenticates a council proposal and dispatches a function call with `Root` origin. + /// This function does not check the weight of the call, and instead allows the + /// user to specify the weight of the call. + /// + /// The dispatch origin for this call must be a council majority. + /// + /// ## Complexity + /// - O(1). + #[allow(deprecated)] + #[pallet::call_index(52)] + #[pallet::weight((*weight, call.get_dispatch_info().class, Pays::No))] + pub fn sudo_unchecked_weight( + origin: OriginFor, + call: Box, + weight: Weight, + ) -> DispatchResultWithPostInfo { + // We dont need to check the weight witness, suppress warning. + // See https://github.com/paritytech/polkadot-sdk/pull/1818. + let _ = weight; + + // This is a public call, so we ensure that the origin is a council majority. + T::CouncilOrigin::ensure_origin(origin)?; + + let result = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into()); + let error = result.map(|_| ()).map_err(|e| e.error); + Self::deposit_event(Event::Sudid(error)); + + return result; + } + + /// User vote on a proposal + #[pallet::call_index(55)] + #[pallet::weight((Weight::from_parts(0, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().reads(0)) + .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Operational))] + pub fn vote( + origin: OriginFor, + hotkey: T::AccountId, + proposal: T::Hash, + #[pallet::compact] index: u32, + approve: bool, + ) -> DispatchResultWithPostInfo { + Self::do_vote_root(origin, &hotkey, proposal, index, approve) + } + + /// User register a new subnetwork + #[pallet::call_index(59)] + #[pallet::weight((Weight::from_parts(157_000_000, 0) + .saturating_add(T::DbWeight::get().reads(16)) + .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] + pub fn register_network(origin: OriginFor) -> DispatchResult { + Self::user_add_network(origin) + } + + /// Facility extrinsic for user to get taken from faucet + /// It is only available when pow-faucet feature enabled + /// Just deployed in testnet and devnet for testing purpose + #[pallet::call_index(60)] + #[pallet::weight((Weight::from_parts(91_000_000, 0) + .saturating_add(T::DbWeight::get().reads(27)) + .saturating_add(T::DbWeight::get().writes(22)), DispatchClass::Normal, Pays::No))] + pub fn faucet( + origin: OriginFor, + block_number: u64, + nonce: u64, + work: Vec, + ) -> DispatchResult { + if cfg!(feature = "pow-faucet") { + return Self::do_faucet(origin, block_number, nonce, work); + } + + Err(Error::::FaucetDisabled.into()) + } + + /// Remove a user's subnetwork + /// The caller must be the owner of the network + #[pallet::call_index(61)] + #[pallet::weight((Weight::from_parts(119_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] + pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { + Self::user_remove_network(origin, netuid) + } + + /// Sets values for liquid alpha + #[pallet::call_index(64)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_hotfix_swap_coldkey_delegates( + _origin: OriginFor, + _old_coldkey: T::AccountId, + _new_coldkey: T::AccountId, + ) -> DispatchResult { + Ok(()) + } + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/errors.rs b/pallets/subtensor/src/macros/errors.rs similarity index 100% rename from pallets/subtensor/src/errors.rs rename to pallets/subtensor/src/macros/errors.rs diff --git a/pallets/subtensor/src/events.rs b/pallets/subtensor/src/macros/events.rs similarity index 100% rename from pallets/subtensor/src/events.rs rename to pallets/subtensor/src/macros/events.rs diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs new file mode 100644 index 000000000..5fbf613d6 --- /dev/null +++ b/pallets/subtensor/src/macros/genesis.rs @@ -0,0 +1,163 @@ +use frame_support::pallet_macros::pallet_section; + +/// A [`pallet_section`] that defines the errors for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod genesis { + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + // Set initial total issuance from balances + TotalIssuance::::put(self.balances_issuance); + + // Subnet config values + let netuid: u16 = 3; + let tempo = 99; + let max_uids = 4096; + + // The functions for initializing new networks/setting defaults cannot be run directly from genesis functions like extrinsics would + // --- Set this network uid to alive. + NetworksAdded::::insert(netuid, true); + + // --- Fill tempo memory item. + Tempo::::insert(netuid, tempo); + + // --- Fill modality item. + // Only modality 0 exists (text) + NetworkModality::::insert(netuid, 0); + + // Make network parameters explicit. + if !Tempo::::contains_key(netuid) { + Tempo::::insert(netuid, Tempo::::get(netuid)); + } + if !Kappa::::contains_key(netuid) { + Kappa::::insert(netuid, Kappa::::get(netuid)); + } + if !Difficulty::::contains_key(netuid) { + Difficulty::::insert(netuid, Difficulty::::get(netuid)); + } + if !MaxAllowedUids::::contains_key(netuid) { + MaxAllowedUids::::insert(netuid, MaxAllowedUids::::get(netuid)); + } + if !ImmunityPeriod::::contains_key(netuid) { + ImmunityPeriod::::insert(netuid, ImmunityPeriod::::get(netuid)); + } + if !ActivityCutoff::::contains_key(netuid) { + ActivityCutoff::::insert(netuid, ActivityCutoff::::get(netuid)); + } + if !EmissionValues::::contains_key(netuid) { + EmissionValues::::insert(netuid, EmissionValues::::get(netuid)); + } + if !MaxWeightsLimit::::contains_key(netuid) { + MaxWeightsLimit::::insert(netuid, MaxWeightsLimit::::get(netuid)); + } + if !MinAllowedWeights::::contains_key(netuid) { + MinAllowedWeights::::insert(netuid, MinAllowedWeights::::get(netuid)); + } + if !RegistrationsThisInterval::::contains_key(netuid) { + RegistrationsThisInterval::::insert( + netuid, + RegistrationsThisInterval::::get(netuid), + ); + } + if !POWRegistrationsThisInterval::::contains_key(netuid) { + POWRegistrationsThisInterval::::insert( + netuid, + POWRegistrationsThisInterval::::get(netuid), + ); + } + if !BurnRegistrationsThisInterval::::contains_key(netuid) { + BurnRegistrationsThisInterval::::insert( + netuid, + BurnRegistrationsThisInterval::::get(netuid), + ); + } + + // Set max allowed uids + MaxAllowedUids::::insert(netuid, max_uids); + + let mut next_uid = 0; + + for (coldkey, hotkeys) in self.stakes.iter() { + for (hotkey, stake_uid) in hotkeys.iter() { + let (stake, uid) = stake_uid; + + // Expand Yuma Consensus with new position. + Rank::::mutate(netuid, |v| v.push(0)); + Trust::::mutate(netuid, |v| v.push(0)); + Active::::mutate(netuid, |v| v.push(true)); + Emission::::mutate(netuid, |v| v.push(0)); + Consensus::::mutate(netuid, |v| v.push(0)); + Incentive::::mutate(netuid, |v| v.push(0)); + Dividends::::mutate(netuid, |v| v.push(0)); + LastUpdate::::mutate(netuid, |v| v.push(0)); + PruningScores::::mutate(netuid, |v| v.push(0)); + ValidatorTrust::::mutate(netuid, |v| v.push(0)); + ValidatorPermit::::mutate(netuid, |v| v.push(false)); + + // Insert account information. + Keys::::insert(netuid, uid, hotkey.clone()); // Make hotkey - uid association. + Uids::::insert(netuid, hotkey.clone(), uid); // Make uid - hotkey association. + BlockAtRegistration::::insert(netuid, uid, 0); // Fill block at registration. + IsNetworkMember::::insert(hotkey.clone(), netuid, true); // Fill network is member. + + // Fill stake information. + Owner::::insert(hotkey.clone(), coldkey.clone()); + + TotalHotkeyStake::::insert(hotkey.clone(), stake); + TotalColdkeyStake::::insert( + coldkey.clone(), + TotalColdkeyStake::::get(coldkey).saturating_add(*stake), + ); + + // Update total issuance value + TotalIssuance::::put(TotalIssuance::::get().saturating_add(*stake)); + + Stake::::insert(hotkey.clone(), coldkey.clone(), stake); + + next_uid += 1; + } + } + + // Set correct length for Subnet neurons + SubnetworkN::::insert(netuid, next_uid); + + // --- Increase total network count. + TotalNetworks::::mutate(|n| *n += 1); + + // Get the root network uid. + let root_netuid: u16 = 0; + + // Set the root network as added. + NetworksAdded::::insert(root_netuid, true); + + // Increment the number of total networks. + TotalNetworks::::mutate(|n| *n += 1); + // Set the number of validators to 1. + SubnetworkN::::insert(root_netuid, 0); + + // Set the maximum number to the number of senate members. + MaxAllowedUids::::insert(root_netuid, 64u16); + + // Set the maximum number to the number of validators to all members. + MaxAllowedValidators::::insert(root_netuid, 64u16); + + // Set the min allowed weights to zero, no weights restrictions. + MinAllowedWeights::::insert(root_netuid, 0); + + // Set the max weight limit to infitiy, no weight restrictions. + MaxWeightsLimit::::insert(root_netuid, u16::MAX); + + // Add default root tempo. + Tempo::::insert(root_netuid, 100); + + // Set the root network as open. + NetworkRegistrationAllowed::::insert(root_netuid, true); + + // Set target registrations for validators as 1 per block. + TargetRegistrationsPerInterval::::insert(root_netuid, 1); + } + } + +} \ No newline at end of file diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs new file mode 100644 index 000000000..baa9de21a --- /dev/null +++ b/pallets/subtensor/src/macros/hooks.rs @@ -0,0 +1,75 @@ +use frame_support::pallet_macros::pallet_section; + +/// A [`pallet_section`] that defines the events for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod hooks { + // ================ + // ==== Hooks ===== + // ================ + #[pallet::hooks] + impl Hooks> for Pallet { + // ---- Called on the initialization of this pallet. (the order of on_finalize calls is determined in the runtime) + // + // # Args: + // * 'n': (BlockNumberFor): + // - The number of the block we are initializing. + fn on_initialize(_block_number: BlockNumberFor) -> Weight { + let block_step_result = Self::block_step(); + match block_step_result { + Ok(_) => { + // --- If the block step was successful, return the weight. + log::info!("Successfully ran block step."); + Weight::from_parts(110_634_229_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(8304_u64)) + .saturating_add(T::DbWeight::get().writes(110_u64)) + } + Err(e) => { + // --- If the block step was unsuccessful, return the weight anyway. + log::error!("Error while stepping block: {:?}", e); + Weight::from_parts(110_634_229_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(8304_u64)) + .saturating_add(T::DbWeight::get().writes(110_u64)) + } + } + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // --- Migrate storage + let mut weight = frame_support::weights::Weight::from_parts(0, 0); + + // Hex encoded foundation coldkey + let hex = hex_literal::hex![ + "feabaafee293d3b76dae304e2f9d885f77d2b17adab9e17e921b321eccd61c77" + ]; + weight = weight + // Initializes storage version (to 1) + .saturating_add(migrations::migrate_to_v1_separate_emission::migrate_to_v1_separate_emission::()) + // Storage version v1 -> v2 + .saturating_add(migrations::migrate_to_v2_fixed_total_stake::migrate_to_v2_fixed_total_stake::()) + // Doesn't check storage version. TODO: Remove after upgrade + .saturating_add(migrations::migrate_create_root_network::migrate_create_root_network::()) + // Storage version v2 -> v3 + .saturating_add(migrations::migrate_transfer_ownership_to_foundation::migrate_transfer_ownership_to_foundation::( + hex, + )) + // Storage version v3 -> v4 + .saturating_add(migrations::migrate_delete_subnet_21::migrate_delete_subnet_21::()) + // Storage version v4 -> v5 + .saturating_add(migrations::migrate_delete_subnet_3::migrate_delete_subnet_3::()) + // Doesn't check storage version. TODO: Remove after upgrade + // Storage version v5 -> v6 + .saturating_add(migrations::migrate_total_issuance::migration5_total_issuance::(false)) + // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. + // Storage version v6 -> v7 + .saturating_add(migrations::migrate_populate_owned_hotkeys::migrate_populate_owned::()) + // Populate StakingHotkeys map for coldkey swap. Doesn't update storage vesion. + // Storage version v7 -> v8 + .saturating_add(migrations::migrate_populate_staking_hotkeys::migrate_populate_staking_hotkeys::()) + // Fix total coldkey stake. + // Storage version v8 -> v9 + .saturating_add(migrations::migrate_fix_total_coldkey_stake::migrate_fix_total_coldkey_stake::()); + weight + } + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/macros/mod.rs b/pallets/subtensor/src/macros/mod.rs new file mode 100644 index 000000000..d64983d52 --- /dev/null +++ b/pallets/subtensor/src/macros/mod.rs @@ -0,0 +1,6 @@ +pub mod events; +pub mod errors; +pub mod dispatches; +pub mod genesis; +pub mod hooks; +pub mod config; \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_create_root_network.rs b/pallets/subtensor/src/migrations/migrate_create_root_network.rs new file mode 100644 index 000000000..ff858ec6a --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_create_root_network.rs @@ -0,0 +1,99 @@ +use super::*; +use frame_support::{ + pallet_prelude::{Identity, OptionQuery}, + storage_alias, + traits::{Get, DefensiveResult}, + weights::Weight, +}; +use sp_std::vec::Vec; + +// TODO (camfairchild): TEST MIGRATION + +/// Module containing deprecated storage format for LoadedEmission +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates the storage to create the root network +/// +/// This function performs the following steps: +/// 1. Checks if the root network already exists +/// 2. If not, creates the root network with default settings +/// 3. Removes all existing senate members +/// +/// # Arguments +/// +/// * `T` - The Config trait of the pallet +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let weight = migrate_create_root_network::(); +/// ``` +pub fn migrate_create_root_network() -> Weight { + // Define the root network UID + let root_netuid: u16 = 0; + + // Initialize weight counter + let mut weight = T::DbWeight::get().reads(1); + + // Check if root network already exists + if NetworksAdded::::get(root_netuid) { + // Return early if root network already exists + return weight; + } + + // Set the root network as added + NetworksAdded::::insert(root_netuid, true); + + // Increment the total number of networks + TotalNetworks::::mutate(|n| *n += 1); + + // Set the maximum number of UIDs to the number of senate members + MaxAllowedUids::::insert(root_netuid, 64); + + // Set the maximum number of validators to all members + MaxAllowedValidators::::insert(root_netuid, 64); + + // Set the minimum allowed weights to zero (no weight restrictions) + MinAllowedWeights::::insert(root_netuid, 0); + + // Set the maximum weight limit to u16::MAX (no weight restrictions) + MaxWeightsLimit::::insert(root_netuid, u16::MAX); + + // Set default root tempo + Tempo::::insert(root_netuid, 100); + + // Set the root network as open for registration + NetworkRegistrationAllowed::::insert(root_netuid, true); + + // Set target registrations for validators as 1 per block + TargetRegistrationsPerInterval::::insert(root_netuid, 1); + + // TODO: Consider if WeightsSetRateLimit should be set + // WeightsSetRateLimit::::insert(root_netuid, 7200); + + // Accrue weight for database writes + weight.saturating_accrue(T::DbWeight::get().writes(8)); + + // Remove all existing senate members + for hotkey_i in T::SenateMembers::members().iter() { + // Remove votes associated with the member + T::TriumvirateInterface::remove_votes(hotkey_i).defensive_ok(); + // Remove the member from the senate + T::SenateMembers::remove_member(hotkey_i).defensive_ok(); + + // Accrue weight for database operations + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + weight +} \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs new file mode 100644 index 000000000..df6a2ae34 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -0,0 +1,127 @@ +use super::*; +use frame_support::{ + pallet_prelude::*, storage_alias, + traits::{Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use log::info; +use sp_std::vec::Vec; + +/// Constant for logging purposes +const LOG_TARGET: &str = "migrate_delete_subnet_21"; + +/// Module containing deprecated storage format +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates the storage to delete subnet 21 +/// +/// This function performs the following steps: +/// 1. Checks if the migration is necessary +/// 2. Removes all storage related to subnet 21 +/// 3. Updates the storage version +/// +/// # Arguments +/// +/// * `T` - The Config trait of the pallet +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let weight = migrate_delete_subnet_21::(); +/// ``` +pub fn migrate_delete_subnet_21() -> Weight { + let new_storage_version = 4; + + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + + // Grab current version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only runs if we haven't already updated version past above new_storage_version and subnet 21 exists. + if onchain_version < new_storage_version && Pallet::::if_subnet_exist(21) { + info!(target: LOG_TARGET, ">>> Removing subnet 21 {:?}", onchain_version); + + let netuid = 21; + + // We do this all manually as we don't want to call code related to giving subnet owner back their locked token cost. + // Remove network count + SubnetworkN::::remove(netuid); + + // Remove network modality storage + NetworkModality::::remove(netuid); + + // Remove netuid from added networks + NetworksAdded::::remove(netuid); + + // Decrement the network counter + TotalNetworks::::mutate(|n| *n -= 1); + + // Remove network registration time + NetworkRegisteredAt::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(5)); + + // Remove incentive mechanism memory + let _ = Uids::::clear_prefix(netuid, u32::MAX, None); + let _ = Keys::::clear_prefix(netuid, u32::MAX, None); + let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); + let _ = Weights::::clear_prefix(netuid, u32::MAX, None); + + weight.saturating_accrue(T::DbWeight::get().writes(4)); + + // Remove various network-related parameters + Rank::::remove(netuid); + Trust::::remove(netuid); + Active::::remove(netuid); + Emission::::remove(netuid); + Incentive::::remove(netuid); + Consensus::::remove(netuid); + Dividends::::remove(netuid); + PruningScores::::remove(netuid); + LastUpdate::::remove(netuid); + ValidatorPermit::::remove(netuid); + ValidatorTrust::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(11)); + + // Erase network parameters + Tempo::::remove(netuid); + Kappa::::remove(netuid); + Difficulty::::remove(netuid); + MaxAllowedUids::::remove(netuid); + ImmunityPeriod::::remove(netuid); + ActivityCutoff::::remove(netuid); + EmissionValues::::remove(netuid); + MaxWeightsLimit::::remove(netuid); + MinAllowedWeights::::remove(netuid); + RegistrationsThisInterval::::remove(netuid); + POWRegistrationsThisInterval::::remove(netuid); + BurnRegistrationsThisInterval::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(12)); + + // Update storage version + StorageVersion::new(new_storage_version).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET, "Migration to v4 already done or subnet 21 doesn't exist!"); + Weight::zero() + } +} + +// TODO: Add unit tests for this migration +// TODO: Consider adding error handling for storage operations +// TODO: Verify that all relevant storage items for subnet 21 are removed \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs new file mode 100644 index 000000000..04df54269 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -0,0 +1,130 @@ +use super::*; +use frame_support::{ + pallet_prelude::*, storage_alias, + traits::{Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use log::info; +use sp_std::vec::Vec; + +/// Constant for logging purposes +const LOG_TARGET: &str = "migrate_delete_subnet_3"; + +/// Module containing deprecated storage format +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates the storage to delete subnet 3 +/// +/// This function performs the following steps: +/// 1. Checks if the migration is necessary +/// 2. Removes all storage related to subnet 3 +/// 3. Updates the storage version +/// +/// # Arguments +/// +/// * `T` - The Config trait of the pallet +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let weight = migrate_delete_subnet_3::(); +/// ``` +pub fn migrate_delete_subnet_3() -> Weight { + let new_storage_version = 5; + + // Initialize weight counter + let mut weight = T::DbWeight::get().reads(1); + + // Get current on-chain storage version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only proceed if current version is less than the new version and subnet 3 exists + if onchain_version < new_storage_version && Pallet::::if_subnet_exist(3) { + info!( + target: LOG_TARGET, + "Removing subnet 3. Current version: {:?}", + onchain_version + ); + + let netuid = 3; + + // Remove network count + SubnetworkN::::remove(netuid); + + // Remove network modality storage + NetworkModality::::remove(netuid); + + // Remove netuid from added networks + NetworksAdded::::remove(netuid); + + // Decrement the network counter + TotalNetworks::::mutate(|n| *n -= 1); + + // Remove network registration time + NetworkRegisteredAt::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(5)); + + // Remove incentive mechanism memory + let _ = Uids::::clear_prefix(netuid, u32::MAX, None); + let _ = Keys::::clear_prefix(netuid, u32::MAX, None); + let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); + let _ = Weights::::clear_prefix(netuid, u32::MAX, None); + + weight.saturating_accrue(T::DbWeight::get().writes(4)); + + // Remove various network-related parameters + Rank::::remove(netuid); + Trust::::remove(netuid); + Active::::remove(netuid); + Emission::::remove(netuid); + Incentive::::remove(netuid); + Consensus::::remove(netuid); + Dividends::::remove(netuid); + PruningScores::::remove(netuid); + LastUpdate::::remove(netuid); + ValidatorPermit::::remove(netuid); + ValidatorTrust::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(11)); + + // Erase network parameters + Tempo::::remove(netuid); + Kappa::::remove(netuid); + Difficulty::::remove(netuid); + MaxAllowedUids::::remove(netuid); + ImmunityPeriod::::remove(netuid); + ActivityCutoff::::remove(netuid); + EmissionValues::::remove(netuid); + MaxWeightsLimit::::remove(netuid); + MinAllowedWeights::::remove(netuid); + RegistrationsThisInterval::::remove(netuid); + POWRegistrationsThisInterval::::remove(netuid); + BurnRegistrationsThisInterval::::remove(netuid); + + weight.saturating_accrue(T::DbWeight::get().writes(12)); + + // Update storage version + StorageVersion::new(new_storage_version).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET, "Migration to v5 already completed or subnet 3 doesn't exist"); + Weight::zero() + } +} + +// TODO: Add unit tests for this migration +// TODO: Consider adding error handling for storage operations +// TODO: Verify that all relevant storage items for subnet 3 are removed \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs new file mode 100644 index 000000000..20c47e29c --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs @@ -0,0 +1,73 @@ +use super::*; +use frame_support::{ + pallet_prelude::{Identity, OptionQuery}, + storage_alias, + traits::{Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use sp_std::vec::Vec; + +// TODO (camfairchild): TEST MIGRATION +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates and fixes the total coldkey stake. +/// +/// This function iterates through all staking hotkeys, calculates the total stake for each coldkey, +/// and updates the `TotalColdkeyStake` storage accordingly. The migration is only performed if the +/// on-chain storage version is 6. +/// +/// # Returns +/// The weight of the migration process. +pub fn do_migrate_fix_total_coldkey_stake() -> Weight { + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Iterate through all staking hotkeys. + for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { + // Init the zero value. + let mut coldkey_stake_sum: u64 = 0; + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // Calculate the total stake for the current coldkey. + for hotkey in hotkey_vec { + // Cant fail on retrieval. + coldkey_stake_sum = + coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + // Update the `TotalColdkeyStake` storage with the calculated stake sum. + // Cant fail on insert. + TotalColdkeyStake::::insert(coldkey.clone(), coldkey_stake_sum); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + weight +} +// Public migrate function to be called by Lib.rs on upgrade. +pub fn migrate_fix_total_coldkey_stake() -> Weight { + let current_storage_version: u16 = 7; + let next_storage_version: u16 = 8; + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Grab the current on-chain storage version. + // Cant fail on retrieval. + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only run this migration on storage version 6. + if onchain_version == current_storage_version { + weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); + // Cant fail on insert. + StorageVersion::new(next_storage_version).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + // Return the migration weight. + weight +} diff --git a/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs b/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs new file mode 100644 index 000000000..23cf90d33 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs @@ -0,0 +1,83 @@ +use super::*; +use frame_support::{ + pallet_prelude::{Identity, OptionQuery}, + storage_alias, + traits::{Get}, + weights::Weight, +}; +use log::info; +use sp_std::vec::Vec; + +const LOG_TARGET_1: &str = "migrate_populate_owned"; + +/// Module containing deprecated storage format for LoadedEmission +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + + +/// Migrate the OwnedHotkeys map to the new storage format +pub fn migrate_populate_owned() -> Weight { + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + let migration_name = "Populate OwnedHotkeys map"; + + // Check if this migration is needed (if OwnedHotkeys map is empty) + let migrate = OwnedHotkeys::::iter().next().is_none(); + + // Only runs if the migration is needed + if migrate { + info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); + + let mut longest_hotkey_vector: usize = 0; + let mut longest_coldkey: Option = None; + let mut keys_touched: u64 = 0; + let mut storage_reads: u64 = 0; + let mut storage_writes: u64 = 0; + + // Iterate through all Owner entries + Owner::::iter().for_each(|(hotkey, coldkey)| { + storage_reads = storage_reads.saturating_add(1); // Read from Owner storage + let mut hotkeys = OwnedHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from OwnedHotkeys storage + + // Add the hotkey if it's not already in the vector + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + // Update the OwnedHotkeys storage + OwnedHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to OwnedHotkeys storage + } + + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + }); + + // Log migration results + info!( + target: LOG_TARGET_1, + "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", + migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes + ); + if let Some(c) = longest_coldkey { + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); + } + + weight + } else { + info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); + Weight::zero() + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs b/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs new file mode 100644 index 000000000..2c0988bbd --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs @@ -0,0 +1,85 @@ + +use super::*; +use frame_support::{ + pallet_prelude::{Identity, OptionQuery}, + storage_alias, + traits::{Get}, + weights::Weight, +}; +use log::info; +use sp_std::vec::Vec; +const LOG_TARGET_1: &str = "migrate_populate_owned"; + +/// Module containing deprecated storage format for LoadedEmission +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + + +/// Populate the StakingHotkeys map from Stake map +pub fn migrate_populate_staking_hotkeys() -> Weight { + // Setup migration weight + let mut weight = T::DbWeight::get().reads(1); + let migration_name = "Populate StakingHotkeys map"; + + // Check if this migration is needed (if StakingHotkeys map is empty) + let migrate = StakingHotkeys::::iter().next().is_none(); + + // Only runs if the migration is needed + if migrate { + info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); + + let mut longest_hotkey_vector: usize = 0; + let mut longest_coldkey: Option = None; + let mut keys_touched: u64 = 0; + let mut storage_reads: u64 = 0; + let mut storage_writes: u64 = 0; + + // Iterate through all Owner entries + Stake::::iter().for_each(|(hotkey, coldkey, stake)| { + storage_reads = storage_reads.saturating_add(1); // Read from Owner storage + if stake > 0 { + let mut hotkeys = StakingHotkeys::::get(&coldkey); + storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage + + // Add the hotkey if it's not already in the vector + if !hotkeys.contains(&hotkey) { + hotkeys.push(hotkey); + keys_touched = keys_touched.saturating_add(1); + + // Update longest hotkey vector info + if longest_hotkey_vector < hotkeys.len() { + longest_hotkey_vector = hotkeys.len(); + longest_coldkey = Some(coldkey.clone()); + } + + // Update the StakingHotkeys storage + StakingHotkeys::::insert(&coldkey, hotkeys); + storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage + } + + // Accrue weight for reads and writes + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + } + }); + + // Log migration results + info!( + target: LOG_TARGET_1, + "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", + migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes + ); + if let Some(c) = longest_coldkey { + info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); + } + + weight + } else { + info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); + Weight::zero() + } +} diff --git a/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs new file mode 100644 index 000000000..4512801fd --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs @@ -0,0 +1,106 @@ +use super::*; +use frame_support::{ + pallet_prelude::*, storage_alias, + traits::{Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use log::{info, warn}; +use sp_std::vec::Vec; + +/// Constant for logging purposes +const LOG_TARGET: &str = "loadedemissionmigration"; +const LOG_TARGET_1: &str = "fixtotalstakestorage"; + +/// Module containing deprecated storage format +pub mod deprecated_loaded_emission_format { + use super::*; + + type AccountIdOf = ::AccountId; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates the LoadedEmission storage to a new format +/// +/// # Arguments +/// +/// * `T` - The runtime configuration trait +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let weight = migrate_to_v1_separate_emission::(); +/// ``` +pub fn migrate_to_v1_separate_emission() -> Weight { + use deprecated_loaded_emission_format as old; + + // Initialize weight counter + let mut weight = T::DbWeight::get().reads_writes(1, 0); + + // Get current on-chain storage version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only proceed if current version is less than 1 + if onchain_version < 1 { + info!( + target: LOG_TARGET, + ">>> Updating the LoadedEmission to a new format {:?}", onchain_version + ); + + // Collect all network IDs (netuids) from old LoadedEmission storage + let curr_loaded_emission: Vec = old::LoadedEmission::::iter_keys().collect(); + + // Remove any undecodable entries + for netuid in curr_loaded_emission { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if old::LoadedEmission::::try_get(netuid).is_err() { + weight.saturating_accrue(T::DbWeight::get().writes(1)); + old::LoadedEmission::::remove(netuid); + warn!( + "Was unable to decode old loaded_emission for netuid {}", + netuid + ); + } + } + + // Translate old storage values to new format + LoadedEmission::::translate::, u64)>, _>( + |netuid: u16, + netuid_emissions: Vec<(AccountIdOf, u64)>| + -> Option, u64, u64)>> { + info!(target: LOG_TARGET, " Do migration of netuid: {:?}...", netuid); + + // Convert old format (server, validator_emission) to new format (server, server_emission, validator_emission) + // Assume all loaded emission is validator emissions + let new_netuid_emissions = netuid_emissions + .into_iter() + .map(|(server, validator_emission)| (server, 0_u64, validator_emission)) + .collect(); + + // Update weight for read and write operations + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + Some(new_netuid_emissions) + }, + ); + + // Update storage version to 1 + StorageVersion::new(1).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET_1, "Migration to v1 already completed!"); + Weight::zero() + } +} + +// TODO: Add unit tests for this migration +// TODO: Consider adding error handling for edge cases +// TODO: Verify that all possible states of the old format are handled correctly \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs new file mode 100644 index 000000000..da0811521 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs @@ -0,0 +1,103 @@ +use super::*; +use frame_support::{ + pallet_prelude::*, storage_alias, + traits::{Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use log::info; +use sp_std::vec::Vec; + +/// Constant for logging purposes +const LOG_TARGET: &str = "fix_total_stake_storage"; + +/// Module containing deprecated storage format +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates the storage to fix TotalStake and TotalColdkeyStake +/// +/// This function performs the following steps: +/// 1. Resets TotalStake to 0 +/// 2. Resets all TotalColdkeyStake entries to 0 +/// 3. Recalculates TotalStake and TotalColdkeyStake based on the Stake map +/// +/// # Arguments +/// +/// * `T` - The Config trait of the pallet +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let weight = migrate_to_v2_fixed_total_stake::(); +/// ``` +pub fn migrate_to_v2_fixed_total_stake() -> Weight { + let new_storage_version = 2; + + // Initialize weight counter + let mut weight = T::DbWeight::get().reads(1); + + // Get current on-chain storage version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only proceed if current version is less than the new version + if onchain_version < new_storage_version { + info!( + target: LOG_TARGET, + "Fixing the TotalStake and TotalColdkeyStake storage. Current version: {:?}", + onchain_version + ); + + // Reset TotalStake to 0 + TotalStake::::put(0); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + // Reset all TotalColdkeyStake entries to 0 + let total_coldkey_stake_keys = TotalColdkeyStake::::iter_keys().collect::>(); + for coldkey in total_coldkey_stake_keys { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + TotalColdkeyStake::::insert(coldkey, 0); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + // Recalculate TotalStake and TotalColdkeyStake based on the Stake map + for (_, coldkey, stake) in Stake::::iter() { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + // Update TotalColdkeyStake + let mut total_coldkey_stake = TotalColdkeyStake::::get(coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + total_coldkey_stake = total_coldkey_stake.saturating_add(stake); + TotalColdkeyStake::::insert(coldkey, total_coldkey_stake); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + // Update TotalStake + let mut total_stake = TotalStake::::get(); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + total_stake = total_stake.saturating_add(stake); + TotalStake::::put(total_stake); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + + // Update storage version to prevent re-running this migration + StorageVersion::new(new_storage_version).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET, "Migration to v2 already completed"); + Weight::zero() + } +} + +// TODO: Add unit tests for this migration function +// TODO: Consider adding error handling for potential arithmetic overflow +// TODO: Optimize the iteration over Stake map if possible to reduce database reads \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_total_issuance.rs new file mode 100644 index 000000000..50d679ba0 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_total_issuance.rs @@ -0,0 +1,84 @@ +use super::*; +use frame_support::{ + pallet_prelude::Identity, + storage_alias, + traits::{fungible::Inspect, Get, GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use sp_std::vec::Vec; +use frame_support::pallet_prelude::OptionQuery; + +// TODO: Implement comprehensive tests for this migration + +/// Module containing deprecated storage format for LoadedEmission +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Performs migration to update the total issuance based on the sum of stakes and total balances. +/// +/// This migration is applicable only if the current storage version is 5, after which it updates the storage version to 6. +/// +/// # Arguments +/// +/// * `test` - A boolean flag to force migration execution for testing purposes. +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation. +/// +/// # Example +/// +/// ``` +/// let weight = migration5_total_issuance::(false); +/// ``` +pub fn migration5_total_issuance(test: bool) -> Weight { + // Initialize migration weight with the cost of reading the storage version + let mut weight = T::DbWeight::get().reads(1); + + // Execute migration if the current storage version is 5 or if in test mode + if Pallet::::on_chain_storage_version() == StorageVersion::new(5) || test { + // Calculate the sum of all stake values + let stake_sum: u64 = Stake::::iter().fold(0, |acc, (_, _, stake)| acc.saturating_add(stake)); + // Add weight for reading all stake entries + weight = weight.saturating_add(T::DbWeight::get().reads(Stake::::iter().count() as u64)); + + // Calculate the sum of all locked subnet values + let locked_sum: u64 = SubnetLocked::::iter().fold(0, |acc, (_, locked)| acc.saturating_add(locked)); + // Add weight for reading all subnet locked entries + weight = weight.saturating_add(T::DbWeight::get().reads(SubnetLocked::::iter().count() as u64)); + + // Retrieve the total balance sum + let total_balance = T::Currency::total_issuance(); + // Add weight for reading total issuance + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // Attempt to convert total balance to u64 + match TryInto::::try_into(total_balance) { + Ok(total_balance_sum) => { + // Compute the total issuance value + let total_issuance_value: u64 = stake_sum.saturating_add(total_balance_sum).saturating_add(locked_sum); + + // Update the total issuance in storage + TotalIssuance::::put(total_issuance_value); + + // Update the storage version to 6 + StorageVersion::new(6).put::>(); + + // Add weight for writing total issuance and storage version + weight = weight.saturating_add(T::DbWeight::get().writes(2)); + } + Err(_) => { + // TODO: Implement proper error handling for conversion failure + log::error!("Failed to convert total balance to u64, migration aborted"); + } + } + } + + // Return the computed weight of the migration process + weight +} \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs new file mode 100644 index 000000000..6a160c128 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs @@ -0,0 +1,87 @@ +use super::*; +use frame_support::{ + pallet_prelude::{Identity, OptionQuery}, + storage_alias, + traits::{GetStorageVersion, StorageVersion}, + weights::Weight, +}; +use log::info; +use sp_core::Get; +use sp_std::vec::Vec; + +/// Constant for logging purposes +const LOG_TARGET: &str = "migrate_transfer_ownership"; + +/// Module containing deprecated storage format +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +/// Migrates subnet ownership to the foundation and updates related storage +/// +/// # Arguments +/// +/// * `coldkey` - 32-byte array representing the foundation's coldkey +/// +/// # Returns +/// +/// * `Weight` - The computational weight of this operation +/// +/// # Example +/// +/// ``` +/// let foundation_coldkey = [0u8; 32]; // Replace with actual foundation coldkey +/// let weight = migrate_transfer_ownership_to_foundation::(foundation_coldkey); +/// ``` +pub fn migrate_transfer_ownership_to_foundation(coldkey: [u8; 32]) -> Weight { + let new_storage_version = 3; + + // Initialize weight counter + let mut weight = T::DbWeight::get().reads(1); + + // Get current on-chain storage version + let onchain_version = Pallet::::on_chain_storage_version(); + + // Only proceed if current version is less than the new version + if onchain_version < new_storage_version { + info!( + target: LOG_TARGET, + "Migrating subnet 1 and 11 to foundation control. Current version: {:?}", + onchain_version + ); + + // Decode the foundation's coldkey into an AccountId + // TODO: Consider error handling for decoding failure + let coldkey_account: T::AccountId = T::AccountId::decode(&mut &coldkey[..]) + .expect("coldkey should be a valid 32-byte array"); + info!(target: LOG_TARGET, "Foundation coldkey: {:?}", coldkey_account); + + // Get the current block number + let current_block = Pallet::::get_current_block_as_u64(); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + // Transfer ownership of subnets 1 and 11 to the foundation + SubnetOwner::::insert(1, coldkey_account.clone()); + SubnetOwner::::insert(11, coldkey_account); + + // Set the registration time for subnet 1 to extend immunity period + NetworkRegisteredAt::::insert(1, current_block.saturating_add(13 * 7200)); + // Set the registration time for subnet 11 to the current block + NetworkRegisteredAt::::insert(11, current_block); + + weight.saturating_accrue(T::DbWeight::get().writes(4)); + + // Update the storage version to prevent re-running this migration + StorageVersion::new(new_storage_version).put::>(); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + weight + } else { + info!(target: LOG_TARGET, "Migration to v3 already completed"); + Weight::zero() + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs new file mode 100644 index 000000000..cdc512d63 --- /dev/null +++ b/pallets/subtensor/src/migrations/mod.rs @@ -0,0 +1,11 @@ +use super::*; +pub mod migrate_delete_subnet_21; +pub mod migrate_create_root_network; +pub mod migrate_delete_subnet_3; +pub mod migrate_to_v1_separate_emission; +pub mod migrate_to_v2_fixed_total_stake; +pub mod migrate_transfer_ownership_to_foundation; +pub mod migrate_total_issuance; +pub mod migrate_populate_owned_hotkeys; +pub mod migrate_populate_staking_hotkeys; +pub mod migrate_fix_total_coldkey_stake; \ No newline at end of file diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index a973b8d7d..9cc38cab6 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -16,7 +16,7 @@ // DEALINGS IN THE SOFTWARE. use super::*; -use crate::math::*; +use crate::epoch::math::*; use frame_support::dispatch::Pays; use frame_support::storage::{IterableStorageDoubleMap, IterableStorageMap}; use frame_support::traits::Get; diff --git a/pallets/subtensor/src/delegate_info.rs b/pallets/subtensor/src/rpc_info/delegate_info.rs similarity index 100% rename from pallets/subtensor/src/delegate_info.rs rename to pallets/subtensor/src/rpc_info/delegate_info.rs diff --git a/pallets/subtensor/src/rpc_info/mod.rs b/pallets/subtensor/src/rpc_info/mod.rs new file mode 100644 index 000000000..70dc816cc --- /dev/null +++ b/pallets/subtensor/src/rpc_info/mod.rs @@ -0,0 +1,5 @@ +use super::*; +pub mod neuron_info; +pub mod stake_info; +pub mod subnet_info; +pub mod delegate_info; \ No newline at end of file diff --git a/pallets/subtensor/src/neuron_info.rs b/pallets/subtensor/src/rpc_info/neuron_info.rs similarity index 100% rename from pallets/subtensor/src/neuron_info.rs rename to pallets/subtensor/src/rpc_info/neuron_info.rs diff --git a/pallets/subtensor/src/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs similarity index 100% rename from pallets/subtensor/src/stake_info.rs rename to pallets/subtensor/src/rpc_info/stake_info.rs diff --git a/pallets/subtensor/src/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs similarity index 100% rename from pallets/subtensor/src/subnet_info.rs rename to pallets/subtensor/src/rpc_info/subnet_info.rs diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/weights.rs index 1866f8e62..67b1d485e 100644 --- a/pallets/subtensor/src/weights.rs +++ b/pallets/subtensor/src/weights.rs @@ -1,5 +1,5 @@ use super::*; -use crate::math::*; +use crate::epoch::math::*; use sp_core::H256; use sp_runtime::traits::{BlakeTwo256, Hash}; use sp_std::vec; diff --git a/pallets/subtensor/tests/math.rs b/pallets/subtensor/tests/math.rs index 35b383f68..7f70e89f6 100644 --- a/pallets/subtensor/tests/math.rs +++ b/pallets/subtensor/tests/math.rs @@ -5,7 +5,7 @@ )] use substrate_fixed::types::{I32F32, I64F64}; -use pallet_subtensor::math::*; +use pallet_subtensor::epoch::math::*; use rand::{seq::SliceRandom, thread_rng, Rng}; use substrate_fixed::{ transcendental::exp, From f7ade4d02dfae2caf84e64f6aefaa962e01e272c Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 14:00:10 -0500 Subject: [PATCH 009/269] clean tests --- pallets/admin-utils/tests/mock.rs | 2 - pallets/admin-utils/tests/tests.rs | 4 +- pallets/subtensor/src/lib.rs | 114 +- pallets/subtensor/src/macros/config.rs | 3 - pallets/subtensor/src/macros/dispatches.rs | 15 +- pallets/subtensor/src/macros/hooks.rs | 2 +- pallets/subtensor/src/migration.rs | 2 +- .../migrations/migrate_create_root_network.rs | 2 +- .../migrations/migrate_delete_subnet_21.rs | 2 +- .../src/migrations/migrate_delete_subnet_3.rs | 2 +- .../migrate_to_v1_separate_emission.rs | 2 +- .../migrate_to_v2_fixed_total_stake.rs | 2 +- .../src/migrations/migrate_total_issuance.rs | 6 +- ...igrate_transfer_ownership_to_foundation.rs | 2 +- pallets/subtensor/src/registration.rs | 4 - pallets/subtensor/src/root.rs | 23 +- pallets/subtensor/src/staking.rs | 20 - pallets/subtensor/src/swap.rs | 1055 ------------- pallets/subtensor/src/swap/mod.rs | 3 + pallets/subtensor/src/swap/swap_coldkey.rs | 391 +++++ pallets/subtensor/src/swap/swap_hotkey.rs | 438 ++++++ pallets/subtensor/tests/epoch.rs | 6 +- pallets/subtensor/tests/migration.rs | 38 +- pallets/subtensor/tests/mock.rs | 2 - pallets/subtensor/tests/root.rs | 20 +- pallets/subtensor/tests/senate.rs | 18 +- pallets/subtensor/tests/staking.rs | 1337 +---------------- pallets/subtensor/tests/swap.rs | 100 +- runtime/src/lib.rs | 2 - 29 files changed, 975 insertions(+), 2642 deletions(-) delete mode 100644 pallets/subtensor/src/swap.rs create mode 100644 pallets/subtensor/src/swap/mod.rs create mode 100644 pallets/subtensor/src/swap/swap_coldkey.rs create mode 100644 pallets/subtensor/src/swap/swap_hotkey.rs diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index dbf88bdfa..dee8e742f 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -114,7 +114,6 @@ parameter_types! { 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 InitialBaseDifficulty: u64 = 10_000; // Base difficulty } impl pallet_subtensor::Config for Test { @@ -170,7 +169,6 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - type InitialBaseDifficulty = InitialBaseDifficulty; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 9df59978f..6e78a1ed6 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -6,7 +6,7 @@ use frame_support::{ use frame_system::Config; use pallet_admin_utils::Error; use pallet_subtensor::Error as SubtensorError; -use pallet_subtensor::{migration, Event}; +use pallet_subtensor::{migrations, Event}; use sp_core::U256; mod mock; @@ -1232,7 +1232,7 @@ fn test_sudo_get_set_alpha() { // Enable Liquid Alpha and setup SubtensorModule::set_liquid_alpha_enabled(netuid, true); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 528c06229..329cebcdc 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -37,7 +37,8 @@ mod benchmarks; // ========================= mod rpc_info; mod coinbase; -mod epoch; +pub mod epoch; +pub mod swap; mod macros; use macros::{events, errors, dispatches, genesis, hooks, config}; @@ -45,7 +46,6 @@ mod registration; mod root; mod serving; mod staking; -mod swap; mod uids; mod utils; mod weights; @@ -308,7 +308,7 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } #[pallet::storage] - pub(super) type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; + pub type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; /// ============================ /// ==== Staking Variables ==== @@ -483,9 +483,9 @@ pub mod pallet { #[pallet::storage] /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration pub type RAORecycledForRegistration = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; #[pallet::storage] /// --- ITEM ( tx_rate_limit ) - pub(super) type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; + pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; #[pallet::storage] /// --- ITEM ( tx_rate_limit ) - pub(super) type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; + pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; #[pallet::storage] /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled pub type LiquidAlphaOn = StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; #[pallet::storage] /// MAP ( netuid ) --> (alpha_low, alpha_high) @@ -496,94 +496,55 @@ pub mod pallet { /// ==== Subnetwork Consensus Storage ==== /// ======================================= #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid - pub(super) type Uids = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; + pub type Uids = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; #[pallet::storage] /// --- DMAP ( netuid, uid ) --> hotkey - pub(super) type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; + pub type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; #[pallet::storage] /// --- DMAP ( netuid ) --> (hotkey, se, ve) - pub(super) type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; + pub type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; #[pallet::storage] /// --- DMAP ( netuid ) --> active - pub(super) type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + pub type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] /// --- DMAP ( netuid ) --> rank - pub(super) type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> trust - pub(super) type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> consensus - pub(super) type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> incentive - pub(super) type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> dividends - pub(super) type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> emission - pub(super) type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + pub type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> last_update - pub(super) type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + pub type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> validator_trust - pub(super) type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> pruning_scores - pub(super) type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> validator_permit - pub(super) type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + pub type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] /// --- DMAP ( netuid, uid ) --> weights - pub(super) type Weights = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultWeights>; + pub type Weights = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultWeights>; #[pallet::storage] /// --- DMAP ( netuid, uid ) --> bonds - pub(super) type Bonds = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultBonds>; + pub type Bonds = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultBonds>; #[pallet::storage] /// --- DMAP ( netuid, uid ) --> block_at_registration pub type BlockAtRegistration = StorageDoubleMap<_, Identity, u16, Identity, u16, u64, ValueQuery, DefaultBlockAtRegistration>; #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> axon_info - pub(super) type Axons = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; + pub type Axons = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> prometheus_info - pub(super) type Prometheus = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, PrometheusInfoOf, OptionQuery>; + pub type Prometheus = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, PrometheusInfoOf, OptionQuery>; /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= #[pallet::storage] /// --- MAP ( key ) --> last_block - pub(super) type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] /// --- MAP ( key ) --> last_block - pub(super) type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] /// ITEM( weights_min_stake ) pub type WeightsMinStake = StorageValue<_, u64, ValueQuery, DefaultWeightsMinStake>; #[pallet::storage] /// --- MAP (netuid, who) --> (hash, weight) | Returns the hash and weight committed by an account for a given netuid. pub type WeightCommits = StorageDoubleMap<_, Twox64Concat, u16, Twox64Concat, T::AccountId, (H256, u64), OptionQuery>; - - /// =============================== - /// ==== Coldkey Arbitrations ===== - /// =============================== - #[pallet::type_value] /// Default base difficulty for proof of work for coldkey swaps - pub fn DefaultBaseDifficulty() -> u64 { T::InitialBaseDifficulty::get() } - #[pallet::storage] // --- ITEM ( base_difficulty ) - pub type BaseDifficulty = StorageValue<_, u64, ValueQuery, DefaultBaseDifficulty>; - #[pallet::type_value] - /// Default value for hotkeys. - pub fn EmptyAccounts() -> Vec { - vec![] - } - #[pallet::type_value] - /// Default arbitration period. - /// This value represents the default arbitration period in blocks. - /// The period is set to 18 hours, assuming a block time of 12 seconds. - pub fn DefaultArbitrationPeriod() -> u64 { - 7200 * 3 // 3 days - } - #[pallet::storage] // ---- StorageItem Global Used Work. - pub type ArbitrationPeriod = - StorageValue<_, u64, ValueQuery, DefaultArbitrationPeriod>; - #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns a list of keys to drain to, if there are two, we extend the period. - pub type ColdkeySwapDestinations = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - Vec, - ValueQuery, - EmptyAccounts, - >; - #[pallet::storage] // --- MAP ( cold ) --> u64 | Block when the coldkey will be arbitrated. - pub type ColdkeyArbitrationBlock = - StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery>; - #[pallet::storage] // --- MAP ( u64 ) --> Vec | Coldkeys to drain on the specific block. - pub type ColdkeysToSwapAtBlock = - StorageMap<_, Identity, u64, Vec, ValueQuery, EmptyAccounts>; - /// ================== /// ==== Genesis ===== /// ================== @@ -736,19 +697,6 @@ where _info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { - // Check if the call is one of the balance transfer types we want to reject - if let Some(balances_call) = call.is_sub_type() { - match balances_call { - BalancesCall::transfer_allow_death { .. } - | BalancesCall::transfer_keep_alive { .. } - | BalancesCall::transfer_all { .. } => { - if Pallet::::coldkey_in_arbitration(who) { - return Err(TransactionValidityError::Invalid(InvalidTransaction::Call)); - } - } - _ => {} // Other Balances calls are allowed - } - } match call.is_sub_type() { Some(Call::commit_weights { netuid, .. }) => { if Self::check_weights_min_stake(who) { @@ -826,14 +774,10 @@ where ..Default::default() }), Some(Call::dissolve_network { .. }) => { - if Pallet::::coldkey_in_arbitration(who) { - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - } else { - Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }) - } + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) } _ => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index e54cd222f..4e3cf5d2a 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -168,8 +168,5 @@ mod config { /// A flag to indicate if Liquid Alpha is enabled. #[pallet::constant] type LiquidAlphaOn: Get; - /// The base difficulty for proof of work for coldkey swaps - #[pallet::constant] - type InitialBaseDifficulty: Get; } } \ No newline at end of file diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 62b02f6a4..6d4c588e9 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -677,6 +677,7 @@ mod dispatches { ) -> DispatchResultWithPostInfo { Self::do_swap_coldkey(origin, &new_coldkey) } + /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. /// /// # Arguments @@ -698,15 +699,13 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] pub fn schedule_coldkey_swap( - origin: OriginFor, - new_coldkey: T::AccountId, - work: Vec, - block_number: u64, - nonce: u64, + _origin: OriginFor, + _new_coldkey: T::AccountId, + _work: Vec, + _block_number: u64, + _nonce: u64, ) -> DispatchResult { - // Attain the calling coldkey from the origin. - let old_coldkey: T::AccountId = ensure_signed(origin)?; - Self::do_schedule_coldkey_swap(&old_coldkey, &new_coldkey, work, block_number, nonce) + Ok(()) } // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index baa9de21a..7e612fb4e 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -59,7 +59,7 @@ mod hooks { .saturating_add(migrations::migrate_delete_subnet_3::migrate_delete_subnet_3::()) // Doesn't check storage version. TODO: Remove after upgrade // Storage version v5 -> v6 - .saturating_add(migrations::migrate_total_issuance::migration5_total_issuance::(false)) + .saturating_add(migrations::migrate_total_issuance::migrate_total_issuance::(false)) // Populate OwnedHotkeys map for coldkey swap. Doesn't update storage vesion. // Storage version v6 -> v7 .saturating_add(migrations::migrate_populate_owned_hotkeys::migrate_populate_owned::()) diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs index 72a86ea4e..cd99f0128 100644 --- a/pallets/subtensor/src/migration.rs +++ b/pallets/subtensor/src/migration.rs @@ -85,7 +85,7 @@ pub fn migrate_fix_total_coldkey_stake() -> Weight { /// /// # Returns /// Weight of the migration process. -pub fn migration5_total_issuance(test: bool) -> Weight { +pub fn migrate_total_issuance(test: bool) -> Weight { let mut weight = T::DbWeight::get().reads(1); // Initialize migration weight // Execute migration if the current storage version is 5 diff --git a/pallets/subtensor/src/migrations/migrate_create_root_network.rs b/pallets/subtensor/src/migrations/migrate_create_root_network.rs index ff858ec6a..20ee82f0a 100644 --- a/pallets/subtensor/src/migrations/migrate_create_root_network.rs +++ b/pallets/subtensor/src/migrations/migrate_create_root_network.rs @@ -35,7 +35,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let weight = migrate_create_root_network::(); /// ``` pub fn migrate_create_root_network() -> Weight { diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs index df6a2ae34..23fbe122f 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -36,7 +36,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let weight = migrate_delete_subnet_21::(); /// ``` pub fn migrate_delete_subnet_21() -> Weight { diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs index 04df54269..257752c19 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -36,7 +36,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let weight = migrate_delete_subnet_3::(); /// ``` pub fn migrate_delete_subnet_3() -> Weight { diff --git a/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs index 4512801fd..5db04f0bc 100644 --- a/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs +++ b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs @@ -34,7 +34,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let weight = migrate_to_v1_separate_emission::(); /// ``` pub fn migrate_to_v1_separate_emission() -> Weight { diff --git a/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs index da0811521..ef9fe8880 100644 --- a/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs +++ b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs @@ -36,7 +36,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let weight = migrate_to_v2_fixed_total_stake::(); /// ``` pub fn migrate_to_v2_fixed_total_stake() -> Weight { diff --git a/pallets/subtensor/src/migrations/migrate_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_total_issuance.rs index 50d679ba0..187967da3 100644 --- a/pallets/subtensor/src/migrations/migrate_total_issuance.rs +++ b/pallets/subtensor/src/migrations/migrate_total_issuance.rs @@ -33,10 +33,10 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// +/// ```ignore +/// let weight = migrate_total_issuance::(false); /// ``` -/// let weight = migration5_total_issuance::(false); -/// ``` -pub fn migration5_total_issuance(test: bool) -> Weight { +pub fn migrate_total_issuance(test: bool) -> Weight { // Initialize migration weight with the cost of reading the storage version let mut weight = T::DbWeight::get().reads(1); diff --git a/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs index 6a160c128..0ca4a7fa5 100644 --- a/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs +++ b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs @@ -33,7 +33,7 @@ pub mod deprecated_loaded_emission_format { /// /// # Example /// -/// ``` +/// ```ignore /// let foundation_coldkey = [0u8; 32]; // Replace with actual foundation coldkey /// let weight = migrate_transfer_ownership_to_foundation::(foundation_coldkey); /// ``` diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 6b73f2fc3..4688bcbb5 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -41,10 +41,6 @@ impl Pallet { ) -> DispatchResult { // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); log::info!( "do_registration( coldkey:{:?} netuid:{:?} hotkey:{:?} )", coldkey, diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index 9cc38cab6..b3ac32f55 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -483,10 +483,6 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); log::info!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, @@ -734,10 +730,6 @@ impl Pallet { ) -> dispatch::DispatchResult { // Check the caller's signature. This is the coldkey of a registered account. let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); log::info!( "do_set_root_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", coldkey, @@ -859,10 +851,6 @@ impl Pallet { ) -> DispatchResultWithPostInfo { // --- 1. Ensure that the caller has signed with their coldkey. let coldkey = ensure_signed(origin.clone())?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); // --- 2. Ensure that the calling coldkey owns the associated hotkey. ensure!( @@ -916,10 +904,7 @@ impl Pallet { pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); + // --- 1. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); @@ -1008,11 +993,7 @@ impl Pallet { pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); - + // --- 2. Ensure this subnet exists. ensure!( Self::if_subnet_exist(netuid), diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 199234a30..2707d90ef 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -44,10 +44,6 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signuture. let coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); log::info!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, @@ -137,10 +133,6 @@ impl Pallet { hotkey, take ); - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -213,10 +205,6 @@ impl Pallet { hotkey, take ); - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); // --- 2. Ensure we are delegating a known key. // Ensure that the coldkey is the owner. @@ -302,10 +290,6 @@ impl Pallet { hotkey, stake_to_be_added ); - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); // Ensure the callers coldkey has enough stake to perform the transaction. ensure!( @@ -418,10 +402,6 @@ impl Pallet { hotkey, stake_to_be_removed ); - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); // Ensure that the hotkey account exists this is only possible through registration. ensure!( diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs deleted file mode 100644 index 8e4ca5cc9..000000000 --- a/pallets/subtensor/src/swap.rs +++ /dev/null @@ -1,1055 +0,0 @@ -use super::*; -use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; -use frame_support::traits::fungible::Mutate; -use frame_support::traits::tokens::Preservation; -use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; -use sp_core::{Get, U256}; - -impl Pallet { - /// Swaps the hotkey of a coldkey account. - /// - /// # Arguments - /// - /// * `origin` - The origin of the transaction, and also the coldkey account. - /// * `old_hotkey` - The old hotkey to be swapped. - /// * `new_hotkey` - The new hotkey to replace the old one. - /// - /// # Returns - /// - /// * `DispatchResultWithPostInfo` - The result of the dispatch. - /// - /// # Errors - /// - /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. - /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. - /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. - /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. - /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. - pub fn do_swap_hotkey( - origin: T::RuntimeOrigin, - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - ) -> DispatchResultWithPostInfo { - let coldkey = ensure_signed(origin)?; - - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); - - let mut weight = T::DbWeight::get().reads(2); - - ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); - ensure!( - Self::coldkey_owns_hotkey(&coldkey, old_hotkey), - Error::::NonAssociatedColdKey - ); - - let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), - Error::::HotKeySetTxRateLimitExceeded - ); - - weight.saturating_accrue( - T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), - ); - - let swap_cost = Self::get_key_swap_cost(); - log::debug!("Swap cost: {:?}", swap_cost); - - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapHotKey - ); - let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); - - Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); - Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); - Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); - Self::swap_stake(old_hotkey, new_hotkey, &mut weight); - - // Store the value of is_network_member for the old key - let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight); - - Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; - - Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); - - Self::set_last_tx_block(&coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - Self::deposit_event(Event::HotkeySwapped { - coldkey, - old_hotkey: old_hotkey.clone(), - new_hotkey: new_hotkey.clone(), - }); - - Ok(Some(weight).into()) - } - - /// 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. - /// * `old_coldkey` - The account ID of 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 the old coldkey. - /// - The new coldkey is the same as the old coldkey. - /// - The new coldkey is already associated with other hotkeys. - /// - The transaction rate limit for coldkey swaps has been exceeded. - /// - 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( - origin: T::RuntimeOrigin, - new_coldkey: &T::AccountId, - ) -> DispatchResultWithPostInfo { - let old_coldkey = ensure_signed(origin)?; - ensure!( - !Self::coldkey_in_arbitration(&old_coldkey), - Error::::ColdkeyIsInArbitration - ); - - let mut weight: Weight = T::DbWeight::get().reads(2); - - // Check that the coldkey is a new key (does not exist elsewhere.) - ensure!( - !Self::coldkey_has_associated_hotkeys(new_coldkey), - Error::::ColdKeyAlreadyAssociated - ); - // Check that the new coldkey is not a hotkey. - ensure!( - !Self::hotkey_account_exists(new_coldkey), - Error::::ColdKeyAlreadyAssociated - ); - - // Calculate and charge the swap fee - let swap_cost = Self::get_key_swap_cost(); - log::debug!("Coldkey swap cost: {:?}", swap_cost); - - ensure!( - Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapColdKey - ); - let actual_burn_amount = - Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); - - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - // Actually do the swap. - weight = weight.saturating_add( - Self::perform_swap_coldkey(&old_coldkey, new_coldkey) - .map_err(|_| Error::::ColdkeySwapError)?, - ); - - Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - Self::deposit_event(Event::ColdkeySwapped { - old_coldkey: old_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - }); - - Ok(Some(weight).into()) - } - - /// Checks if a coldkey is currently in arbitration. - /// - /// # Arguments - /// - /// * `coldkey` - The account ID of the coldkey to check. - /// - /// # Returns - /// - /// * `bool` - True if the coldkey is in arbitration, false otherwise. - /// - /// # Notes - /// - /// This function compares the arbitration block number of the coldkey with the current block number. - pub fn coldkey_in_arbitration(coldkey: &T::AccountId) -> bool { - ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() - } - - /// Returns the remaining arbitration period for a given coldkey. - /// - /// # Arguments - /// - /// * `coldkey` - The account ID of the coldkey to check. - /// - /// # Returns - /// - /// * `u64` - The remaining arbitration period in blocks. - /// - /// - /// # Notes - /// - /// This function calculates the remaining arbitration period by subtracting the current block number - /// from the arbitration block number of the coldkey. - pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { - let current_block: u64 = Self::get_current_block_as_u64(); - let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); - if arbitration_block > current_block { - arbitration_block.saturating_sub(current_block) - } else { - 0 - } - } - - pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { - let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); - let mut total_staking_balance: u64 = 0; - for hotkey in all_staked_keys { - total_staking_balance = total_staking_balance - .saturating_add(Self::get_stake_for_coldkey_and_hotkey(coldkey, &hotkey)); - } - total_staking_balance = - total_staking_balance.saturating_add(Self::get_coldkey_balance(coldkey)); - total_staking_balance >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - } - - /// Schedules a coldkey swap to a new coldkey with arbitration. - /// - /// # Arguments - /// - /// * `old_coldkey` - The account ID of the old coldkey. - /// * `new_coldkey` - The account ID of the new coldkey. - /// * `work` - The proof of work submitted by the caller. - /// * `block_number` - The block number at which the work was performed. - /// * `nonce` - The nonce used for the proof of work. - /// - /// # Returns - /// - /// * `DispatchResult` - The result of the dispatch. - /// - /// # Errors - /// - - /// - `SameColdkey`: The old coldkey is the same as the new coldkey. - /// - `DuplicateColdkey`: The new coldkey is already in the list of destination coldkeys. - /// - `MaxColdkeyDestinationsReached`: There are already the maximum allowed destination coldkeys for the old coldkey. - /// - `InsufficientBalanceToPerformColdkeySwap`: The old coldkey doesn't have the minimum required TAO balance. - /// - `InvalidDifficulty`: The proof of work is invalid or doesn't meet the required difficulty. - /// - /// # Notes - /// - /// This function ensures that the new coldkey is not already in the list of destination coldkeys. - /// It also checks for a minimum TAO balance and verifies the proof of work. - /// The difficulty of the proof of work increases exponentially with each subsequent call. - pub fn do_schedule_coldkey_swap( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - work: Vec, - block_number: u64, - nonce: u64, - ) -> DispatchResult { - ensure!(old_coldkey != new_coldkey, Error::::SameColdkey); - - // Check if the old_coldkey is a subnet owner for any network - let is_subnet_owner = (0..=TotalNetworks::::get()) - .any(|netuid| SubnetOwner::::get(netuid) == *old_coldkey); - - // Check if the old_coldkey has more than 500 TAO delegated - let total_delegated = Self::get_total_delegated_stake(old_coldkey); - let has_sufficient_delegation = total_delegated > 500_000_000_000; // 500 TAO in RAO - - // Only check the minimum balance if the old_coldkey is not a subnet owner - // and doesn't have sufficient delegation - if !(is_subnet_owner || has_sufficient_delegation) { - ensure!( - Self::meets_min_allowed_coldkey_balance(old_coldkey), - Error::::InsufficientBalanceToPerformColdkeySwap - ); - } - - // Get current destination coldkeys - let mut destination_coldkeys: Vec = - ColdkeySwapDestinations::::get(old_coldkey.clone()); - - // Calculate difficulty based on the number of existing destination coldkeys - let difficulty = Self::calculate_pow_difficulty(destination_coldkeys.len() as u32); - let work_hash = Self::vec_to_hash(work.clone()); - ensure!( - Self::hash_meets_difficulty(&work_hash, difficulty), - Error::::InvalidDifficulty - ); - - // Verify work is the product of the nonce, the block number, and coldkey - let seal = Self::create_seal_hash(block_number, nonce, old_coldkey); - ensure!(seal == work_hash, Error::::InvalidSeal); - - // Check if the new coldkey is already in the swap wallets list - ensure!( - !destination_coldkeys.contains(new_coldkey), - Error::::DuplicateColdkey - ); - - // If the destinations keys are empty or have less than the maximum allowed, we will add the new coldkey to the list - const MAX_COLDKEY_DESTINATIONS: usize = 10; - - if destination_coldkeys.len() < MAX_COLDKEY_DESTINATIONS { - destination_coldkeys.push(new_coldkey.clone()); - ColdkeySwapDestinations::::insert(old_coldkey.clone(), destination_coldkeys.clone()); - } else { - return Err(Error::::MaxColdkeyDestinationsReached.into()); - } - - // It is the first time we have seen this key - if destination_coldkeys.len() == 1_usize { - // Set the arbitration block for this coldkey - let arbitration_block: u64 = - Self::get_current_block_as_u64().saturating_add(ArbitrationPeriod::::get()); - ColdkeyArbitrationBlock::::insert(old_coldkey.clone(), arbitration_block); - - // Update the list of coldkeys to arbitrate on this block - let mut key_to_arbitrate_on_this_block: Vec = - ColdkeysToSwapAtBlock::::get(arbitration_block); - if !key_to_arbitrate_on_this_block.contains(old_coldkey) { - key_to_arbitrate_on_this_block.push(old_coldkey.clone()); - } - ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); - } - - // Emit an event indicating that a coldkey swap has been scheduled - Self::deposit_event(Event::ColdkeySwapScheduled { - old_coldkey: old_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - arbitration_block: ColdkeyArbitrationBlock::::get(old_coldkey), - }); - - Ok(()) - } - - /// Calculate the proof of work difficulty based on the number of swap attempts - #[allow(clippy::arithmetic_side_effects)] - pub fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { - let base_difficulty: U256 = U256::from(BaseDifficulty::::get()); // Base difficulty - base_difficulty.saturating_mul(U256::from(2).pow(U256::from(swap_attempts))) - } - - /// Arbitrates coldkeys that are scheduled to be swapped on this block. - /// - /// This function retrieves the list of coldkeys scheduled to be swapped on the current block, - /// and processes each coldkey by either extending the arbitration period or performing the swap - /// to the new coldkey. - /// - /// # Returns - /// - /// * `Weight` - The total weight consumed by this operation - pub fn swap_coldkeys_this_block(_weight_limit: &Weight) -> Result { - let mut weight_used = frame_support::weights::Weight::from_parts(0, 0); - - let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!("Swapping coldkeys for block: {:?}", current_block); - - let source_coldkeys: Vec = ColdkeysToSwapAtBlock::::get(current_block); - ColdkeysToSwapAtBlock::::remove(current_block); - weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - let mut keys_swapped = 0u64; - for coldkey_i in source_coldkeys.iter() { - // TODO: need a sane way to terminate early without locking users in. - // we should update the swap time - // if weight_used.ref_time() > weight_limit.ref_time() { - // log::warn!("Could not finish swapping all coldkeys this block due to weight limit, breaking after swapping {} keys.", keys_swapped); - // break; - // } - - let destinations_coldkeys: Vec = - ColdkeySwapDestinations::::get(coldkey_i); - weight_used = weight_used.saturating_add(T::DbWeight::get().reads(1)); - - if destinations_coldkeys.len() > 1 { - // Do not remove ColdkeySwapDestinations if there are multiple destinations - ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), u64::MAX); - Self::deposit_event(Event::ArbitrationPeriodExtended { - coldkey: coldkey_i.clone(), - }); - } else if let Some(new_coldkey) = destinations_coldkeys.first() { - // Only remove ColdkeySwapDestinations if there's a single destination - ColdkeySwapDestinations::::remove(&coldkey_i); - weight_used = weight_used.saturating_add(T::DbWeight::get().writes(1)); - Self::perform_swap_coldkey(coldkey_i, new_coldkey).map(|weight| { - weight_used = weight_used.saturating_add(weight); - keys_swapped = keys_swapped.saturating_add(1); - })?; - } - } - - Ok(weight_used) - } - - pub fn perform_swap_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) -> Result { - log::info!( - "Performing swap for coldkey: {:?} to {:?}", - old_coldkey, - new_coldkey - ); - // Init the weight. - let mut weight = frame_support::weights::Weight::from_parts(0, 0); - - // Swap coldkey references in storage maps - // NOTE The order of these calls is important - Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); - Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( - old_coldkey, - new_coldkey, - &mut weight, - ); - Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); - - // Transfer any remaining balance from old_coldkey to new_coldkey - let remaining_balance = Self::get_coldkey_balance(old_coldkey); - if remaining_balance > 0 { - if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { - return Err(e.into()); - } - Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); - } - - // Swap the coldkey. - let total_balance: u64 = Self::get_coldkey_balance(old_coldkey); - if total_balance > 0 { - // Attempt to transfer the entire total balance to new_coldkey. - T::Currency::transfer( - old_coldkey, - new_coldkey, - total_balance, - Preservation::Expendable, - )?; - } - - Ok(weight) - } - - /// Retrieves the network membership status for a given hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The hotkey to check for network membership. - /// - /// # Returns - /// - /// * `Vec` - A vector of network IDs where the hotkey is a member. - pub fn get_netuid_is_member(old_hotkey: &T::AccountId, weight: &mut Weight) -> Vec { - let netuid_is_member: Vec = - as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) - .map(|(netuid, _)| netuid) - .collect(); - weight.saturating_accrue(T::DbWeight::get().reads(netuid_is_member.len() as u64)); - netuid_is_member - } - - /// Swaps the owner of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `coldkey` - The coldkey owning the hotkey. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_owner( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - coldkey: &T::AccountId, - weight: &mut Weight, - ) { - Owner::::remove(old_hotkey); - Owner::::insert(new_hotkey, coldkey.clone()); - - // Update OwnedHotkeys map - let mut hotkeys = OwnedHotkeys::::get(coldkey); - if !hotkeys.contains(new_hotkey) { - hotkeys.push(new_hotkey.clone()); - } - hotkeys.retain(|hk| *hk != *old_hotkey); - OwnedHotkeys::::insert(coldkey, hotkeys); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - - /// Swaps the total stake of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. - /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). - pub fn swap_total_hotkey_stake( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { - TotalHotkeyStake::::remove(old_hotkey); - TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - /// Swaps the delegates of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. - /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). - pub fn swap_delegates( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { - Delegates::::remove(old_hotkey); - Delegates::::insert(new_hotkey, delegate_take); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - /// Swaps the stake of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { - let mut writes: u64 = 0; - let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); - let stake_count = stakes.len() as u32; - - for (coldkey, stake_amount) in stakes { - Stake::::insert(new_hotkey, &coldkey, stake_amount); - writes = writes.saturating_add(1u64); // One write for insert - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); - if !staking_hotkeys.contains(new_hotkey) { - staking_hotkeys.push(new_hotkey.clone()); - writes = writes.saturating_add(1u64); // One write for insert - } - if let Some(pos) = staking_hotkeys.iter().position(|x| x == old_hotkey) { - staking_hotkeys.remove(pos); - writes = writes.saturating_add(1u64); // One write for remove - } - StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); - writes = writes.saturating_add(1u64); // One write for insert - } - - // Clear the prefix for the old hotkey after transferring all stakes - let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); - writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix - - // TODO: Remove all entries for old hotkey from StakingHotkeys map - - weight.saturating_accrue(T::DbWeight::get().writes(writes)); - } - - /// Swaps the network membership status of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - pub fn swap_is_network_member( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); - weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); - for netuid in netuid_is_member.iter() { - IsNetworkMember::::insert(new_hotkey, netuid, true); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - - /// Swaps the axons of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 for each network ID if the old hotkey exists in that network. - /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). - pub fn swap_axons( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { - Axons::::remove(netuid, old_hotkey); - Axons::::insert(netuid, new_hotkey, axon_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - } - - /// Swaps the references in the keys storage map of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - pub fn swap_keys( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - let mut writes: u64 = 0; - for netuid in netuid_is_member { - let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); - for (uid, key) in keys { - if key == *old_hotkey { - log::info!("old hotkey found: {:?}", old_hotkey); - Keys::::insert(netuid, uid, new_hotkey.clone()); - } - writes = writes.saturating_add(2u64); - } - } - log::info!("writes: {:?}", writes); - weight.saturating_accrue(T::DbWeight::get().writes(writes)); - } - - /// Swaps the loaded emission of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_loaded_emission( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member { - if let Some(mut emissions) = LoadedEmission::::get(netuid) { - for emission in emissions.iter_mut() { - if emission.0 == *old_hotkey { - emission.0 = new_hotkey.clone(); - } - } - LoadedEmission::::insert(netuid, emissions); - } - } - weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); - } - - /// Swaps the UIDs of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_uids( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { - Uids::::remove(netuid, old_hotkey); - Uids::::insert(netuid, new_hotkey, uid); - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - } - } - - /// Swaps the Prometheus data of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 for each network ID if the old hotkey exists in that network. - /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). - pub fn swap_prometheus( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { - Prometheus::::remove(netuid, old_hotkey); - Prometheus::::insert(netuid, new_hotkey, prometheus_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - } - - /// Swaps the total hotkey-coldkey stakes for the current interval. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_total_hotkey_coldkey_stakes_this_interval( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - let stakes: Vec<(T::AccountId, (u64, u64))> = - TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); - log::info!("Stakes to swap: {:?}", stakes); - for (coldkey, stake) in stakes { - log::info!( - "Swapping stake for coldkey: {:?}, stake: {:?}", - coldkey, - stake - ); - TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake); - TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); - weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove - } - } - - /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Removes the total stake from the old coldkey. - /// * Inserts the total stake for the new coldkey. - /// * Updates the transaction weight. - pub fn swap_total_coldkey_stake( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - let stake = TotalColdkeyStake::::get(old_coldkey); - TotalColdkeyStake::::remove(old_coldkey); - TotalColdkeyStake::::insert(new_coldkey, stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - - /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Transfers all stakes from the old coldkey to the new coldkey. - /// * Updates the ownership of hotkeys. - /// * Updates the total stake for both old and new coldkeys. - /// * Updates the transaction weight. - /// - - pub fn swap_stake_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - // Retrieve the list of hotkeys owned by the old coldkey - let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); - - // Initialize the total transferred stake to zero - let mut total_transferred_stake: u64 = 0u64; - - // Log the total stake of old and new coldkeys before the swap - log::info!( - "Before swap - Old coldkey total stake: {}", - TotalColdkeyStake::::get(old_coldkey) - ); - log::info!( - "Before swap - New coldkey total stake: {}", - TotalColdkeyStake::::get(new_coldkey) - ); - - // Iterate over each hotkey owned by the old coldkey - for hotkey in old_owned_hotkeys.iter() { - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::take(hotkey, old_coldkey); - log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - - // Update the owner of the hotkey to the new coldkey - Owner::::insert(hotkey, new_coldkey); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - } - log::info!( - "Starting transfer of delegated stakes for old coldkey: {:?}", - old_coldkey - ); - - for staking_hotkey in StakingHotkeys::::get(old_coldkey) { - log::info!("Processing staking hotkey: {:?}", staking_hotkey); - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - log::info!( - "Transferring delegated stake for hotkey {:?}: {}", - hotkey, - stake - ); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - log::info!( - "Updated stake for hotkey {:?} under new coldkey {:?}: {}", - hotkey, - new_coldkey, - stake.saturating_add(old_stake) - ); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); - } - } else { - log::info!( - "No stake found for staking hotkey {:?} under old coldkey {:?}", - staking_hotkey, - old_coldkey - ); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - log::info!( - "Completed transfer of delegated stakes for old coldkey: {:?}", - old_coldkey - ); - - // Log the total transferred stake - log::info!("Total transferred stake: {}", total_transferred_stake); - - // Update the total stake for both old and new coldkeys if any stake was transferred - if total_transferred_stake > 0 { - let old_coldkey_stake: u64 = TotalColdkeyStake::::take(old_coldkey); // Remove it here. - let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); - - TotalColdkeyStake::::insert(old_coldkey, 0); - TotalColdkeyStake::::insert( - new_coldkey, - new_coldkey_stake.saturating_add(old_coldkey_stake), - ); - - log::info!("Updated old coldkey stake from {} to 0", old_coldkey_stake); - log::info!( - "Updated new coldkey stake from {} to {}", - new_coldkey_stake, - new_coldkey_stake.saturating_add(old_coldkey_stake) - ); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - - // Update the list of owned hotkeys for both old and new coldkeys - - let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); - for hotkey in old_owned_hotkeys { - if !new_owned_hotkeys.contains(&hotkey) { - new_owned_hotkeys.push(hotkey); - } - } - - OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); - OwnedHotkeys::::remove(old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - - // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); - - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - for hotkey in staking_hotkeys { - if !existing_staking_hotkeys.contains(&hotkey) { - existing_staking_hotkeys.push(hotkey); - } - } - - StakingHotkeys::::remove(old_coldkey); - StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - // Log the total stake of old and new coldkeys after the swap - log::info!( - "After swap - Old coldkey total stake: {}", - TotalColdkeyStake::::get(old_coldkey) - ); - log::info!( - "After swap - New coldkey total stake: {}", - TotalColdkeyStake::::get(new_coldkey) - ); - } - - /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Removes all total hotkey-coldkey stakes for the current interval associated with the old coldkey. - /// * Inserts all total hotkey-coldkey stakes for the current interval for the new coldkey. - /// * Updates the transaction weight. - pub fn swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - 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(2, 2)); - } - } - - /// Checks if a coldkey has any associated hotkeys. - /// - /// # Arguments - /// - /// * `coldkey` - The AccountId of the coldkey to check. - /// - /// # Returns - /// - /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. - pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { - !StakingHotkeys::::get(coldkey).is_empty() - } - - /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. - /// * Updates the transaction weight. - pub fn swap_subnet_owner_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - for netuid in 0..=TotalNetworks::::get() { - let subnet_owner = SubnetOwner::::get(netuid); - if subnet_owner == *old_coldkey { - SubnetOwner::::insert(netuid, new_coldkey.clone()); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); - } - - pub fn swap_senate_member( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> DispatchResult { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if T::SenateMembers::is_member(old_hotkey) { - T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - Ok(()) - } -} diff --git a/pallets/subtensor/src/swap/mod.rs b/pallets/subtensor/src/swap/mod.rs new file mode 100644 index 000000000..0e71b1b1d --- /dev/null +++ b/pallets/subtensor/src/swap/mod.rs @@ -0,0 +1,3 @@ +use super::*; +pub mod swap_coldkey; +pub mod swap_hotkey; \ No newline at end of file diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs new file mode 100644 index 000000000..83bad5134 --- /dev/null +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -0,0 +1,391 @@ +use super::*; +use frame_support::traits::fungible::Mutate; +use frame_support::traits::tokens::Preservation; +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use sp_core::Get; + +impl Pallet { + + + /// 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. + /// * `old_coldkey` - The account ID of 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 the old coldkey. + /// - The new coldkey is the same as the old coldkey. + /// - The new coldkey is already associated with other hotkeys. + /// - The transaction rate limit for coldkey swaps has been exceeded. + /// - 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( + origin: T::RuntimeOrigin, + new_coldkey: &T::AccountId, + ) -> DispatchResultWithPostInfo { + let old_coldkey = ensure_signed(origin)?; + let mut weight: Weight = T::DbWeight::get().reads(2); + + // Check that the coldkey is a new key (does not exist elsewhere.) + ensure!( + !Self::coldkey_has_associated_hotkeys(new_coldkey), + Error::::ColdKeyAlreadyAssociated + ); + // Check that the new coldkey is not a hotkey. + ensure!( + !Self::hotkey_account_exists(new_coldkey), + Error::::ColdKeyAlreadyAssociated + ); + + // Calculate and charge the swap fee + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Coldkey swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // Actually do the swap. + weight = weight.saturating_add( + Self::perform_swap_coldkey(&old_coldkey, new_coldkey) + .map_err(|_| Error::::ColdkeySwapError)?, + ); + + Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + Self::deposit_event(Event::ColdkeySwapped { + old_coldkey: old_coldkey.clone(), + new_coldkey: new_coldkey.clone(), + }); + + Ok(Some(weight).into()) + } + + pub fn perform_swap_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) -> Result { + log::info!( + "Performing swap for coldkey: {:?} to {:?}", + old_coldkey, + new_coldkey + ); + // Init the weight. + let mut weight = frame_support::weights::Weight::from_parts(0, 0); + + // Swap coldkey references in storage maps + // NOTE The order of these calls is important + Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); + Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + old_coldkey, + new_coldkey, + &mut weight, + ); + Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); + + // Transfer any remaining balance from old_coldkey to new_coldkey + let remaining_balance = Self::get_coldkey_balance(old_coldkey); + if remaining_balance > 0 { + if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { + return Err(e.into()); + } + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + } + + // Swap the coldkey. + let total_balance: u64 = Self::get_coldkey_balance(old_coldkey); + if total_balance > 0 { + // Attempt to transfer the entire total balance to new_coldkey. + T::Currency::transfer( + old_coldkey, + new_coldkey, + total_balance, + Preservation::Expendable, + )?; + } + + Ok(weight) + } + + + /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Removes the total stake from the old coldkey. + /// * Inserts the total stake for the new coldkey. + /// * Updates the transaction weight. + pub fn swap_total_coldkey_stake( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + let stake = TotalColdkeyStake::::get(old_coldkey); + TotalColdkeyStake::::remove(old_coldkey); + TotalColdkeyStake::::insert(new_coldkey, stake); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Transfers all stakes from the old coldkey to the new coldkey. + /// * Updates the ownership of hotkeys. + /// * Updates the total stake for both old and new coldkeys. + /// * Updates the transaction weight. + /// + + pub fn swap_stake_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + // Retrieve the list of hotkeys owned by the old coldkey + let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); + + // Initialize the total transferred stake to zero + let mut total_transferred_stake: u64 = 0u64; + + // Log the total stake of old and new coldkeys before the swap + log::info!( + "Before swap - Old coldkey total stake: {}", + TotalColdkeyStake::::get(old_coldkey) + ); + log::info!( + "Before swap - New coldkey total stake: {}", + TotalColdkeyStake::::get(new_coldkey) + ); + + // Iterate over each hotkey owned by the old coldkey + for hotkey in old_owned_hotkeys.iter() { + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::take(hotkey, old_coldkey); + log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + + // Update the owner of the hotkey to the new coldkey + Owner::::insert(hotkey, new_coldkey); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + } + log::info!( + "Starting transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); + + for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + log::info!("Processing staking hotkey: {:?}", staking_hotkey); + if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + let hotkey = &staking_hotkey; + // Retrieve and remove the stake associated with the hotkey and old coldkey + let stake: u64 = Stake::::get(hotkey, old_coldkey); + Stake::::remove(hotkey, old_coldkey); + log::info!( + "Transferring delegated stake for hotkey {:?}: {}", + hotkey, + stake + ); + if stake > 0 { + // Insert the stake for the hotkey and new coldkey + let old_stake = Stake::::get(hotkey, new_coldkey); + Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + total_transferred_stake = total_transferred_stake.saturating_add(stake); + log::info!( + "Updated stake for hotkey {:?} under new coldkey {:?}: {}", + hotkey, + new_coldkey, + stake.saturating_add(old_stake) + ); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + } + } else { + log::info!( + "No stake found for staking hotkey {:?} under old coldkey {:?}", + staking_hotkey, + old_coldkey + ); + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + + log::info!( + "Completed transfer of delegated stakes for old coldkey: {:?}", + old_coldkey + ); + + // Log the total transferred stake + log::info!("Total transferred stake: {}", total_transferred_stake); + + // Update the total stake for both old and new coldkeys if any stake was transferred + if total_transferred_stake > 0 { + let old_coldkey_stake: u64 = TotalColdkeyStake::::take(old_coldkey); // Remove it here. + let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); + + TotalColdkeyStake::::insert(old_coldkey, 0); + TotalColdkeyStake::::insert( + new_coldkey, + new_coldkey_stake.saturating_add(old_coldkey_stake), + ); + + log::info!("Updated old coldkey stake from {} to 0", old_coldkey_stake); + log::info!( + "Updated new coldkey stake from {} to {}", + new_coldkey_stake, + new_coldkey_stake.saturating_add(old_coldkey_stake) + ); + + // Update the transaction weight + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + // Update the list of owned hotkeys for both old and new coldkeys + + let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + for hotkey in old_owned_hotkeys { + if !new_owned_hotkeys.contains(&hotkey) { + new_owned_hotkeys.push(hotkey); + } + } + + OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); + OwnedHotkeys::::remove(old_coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Update the staking hotkeys for both old and new coldkeys + let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); + + let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + for hotkey in staking_hotkeys { + if !existing_staking_hotkeys.contains(&hotkey) { + existing_staking_hotkeys.push(hotkey); + } + } + + StakingHotkeys::::remove(old_coldkey); + StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // Log the total stake of old and new coldkeys after the swap + log::info!( + "After swap - Old coldkey total stake: {}", + TotalColdkeyStake::::get(old_coldkey) + ); + log::info!( + "After swap - New coldkey total stake: {}", + TotalColdkeyStake::::get(new_coldkey) + ); + } + + /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Removes all total hotkey-coldkey stakes for the current interval associated with the old coldkey. + /// * Inserts all total hotkey-coldkey stakes for the current interval for the new coldkey. + /// * Updates the transaction weight. + pub fn swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); + 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(2, 2)); + } + } + + /// Checks if a coldkey has any associated hotkeys. + /// + /// # Arguments + /// + /// * `coldkey` - The AccountId of the coldkey to check. + /// + /// # Returns + /// + /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. + pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { + !StakingHotkeys::::get(coldkey).is_empty() + } + + /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. + /// + /// # Arguments + /// + /// * `old_coldkey` - The AccountId of the old coldkey. + /// * `new_coldkey` - The AccountId of the new coldkey. + /// * `weight` - Mutable reference to the weight of the transaction. + /// + /// # Effects + /// + /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. + /// * Updates the transaction weight. + pub fn swap_subnet_owner_for_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + weight: &mut Weight, + ) { + for netuid in 0..=TotalNetworks::::get() { + let subnet_owner = SubnetOwner::::get(netuid); + if subnet_owner == *old_coldkey { + SubnetOwner::::insert(netuid, new_coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + } + weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); + } + +} diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs new file mode 100644 index 000000000..2755f1c4c --- /dev/null +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -0,0 +1,438 @@ +use super::*; +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use sp_core::Get; + +impl Pallet { + /// Swaps the hotkey of a coldkey account. + /// + /// # Arguments + /// + /// * `origin` - The origin of the transaction, and also the coldkey account. + /// * `old_hotkey` - The old hotkey to be swapped. + /// * `new_hotkey` - The new hotkey to replace the old one. + /// + /// # Returns + /// + /// * `DispatchResultWithPostInfo` - The result of the dispatch. + /// + /// # Errors + /// + /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. + /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. + /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. + /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. + /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. + pub fn do_swap_hotkey( + origin: T::RuntimeOrigin, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + ) -> DispatchResultWithPostInfo { + let coldkey = ensure_signed(origin)?; + + let mut weight = T::DbWeight::get().reads(2); + + ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); + ensure!( + Self::coldkey_owns_hotkey(&coldkey, old_hotkey), + Error::::NonAssociatedColdKey + ); + + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), + Error::::HotKeySetTxRateLimitExceeded + ); + + weight.saturating_accrue( + T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), + ); + + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Swap cost: {:?}", swap_cost); + + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + Self::burn_tokens(actual_burn_amount); + + Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); + Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); + Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); + Self::swap_stake(old_hotkey, new_hotkey, &mut weight); + + // Store the value of is_network_member for the old key + let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight); + + Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); + Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; + + Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); + + Self::set_last_tx_block(&coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + Self::deposit_event(Event::HotkeySwapped { + coldkey, + old_hotkey: old_hotkey.clone(), + new_hotkey: new_hotkey.clone(), + }); + + Ok(Some(weight).into()) + } + + /// Retrieves the network membership status for a given hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The hotkey to check for network membership. + /// + /// # Returns + /// + /// * `Vec` - A vector of network IDs where the hotkey is a member. + pub fn get_netuid_is_member(old_hotkey: &T::AccountId, weight: &mut Weight) -> Vec { + let netuid_is_member: Vec = + as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) + .map(|(netuid, _)| netuid) + .collect(); + weight.saturating_accrue(T::DbWeight::get().reads(netuid_is_member.len() as u64)); + netuid_is_member + } + + /// Swaps the owner of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `coldkey` - The coldkey owning the hotkey. + /// * `weight` - The weight of the transaction. + /// + pub fn swap_owner( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + coldkey: &T::AccountId, + weight: &mut Weight, + ) { + Owner::::remove(old_hotkey); + Owner::::insert(new_hotkey, coldkey.clone()); + + // Update OwnedHotkeys map + let mut hotkeys = OwnedHotkeys::::get(coldkey); + if !hotkeys.contains(new_hotkey) { + hotkeys.push(new_hotkey.clone()); + } + hotkeys.retain(|hk| *hk != *old_hotkey); + OwnedHotkeys::::insert(coldkey, hotkeys); + + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + + /// Swaps the total stake of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Weight Calculation + /// + /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. + /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). + pub fn swap_total_hotkey_stake( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) { + if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { + TotalHotkeyStake::::remove(old_hotkey); + TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + + /// Swaps the delegates of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + /// # Weight Calculation + /// + /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. + /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). + pub fn swap_delegates( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) { + if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { + Delegates::::remove(old_hotkey); + Delegates::::insert(new_hotkey, delegate_take); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + + /// Swaps the stake of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { + let mut writes: u64 = 0; + let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); + let stake_count = stakes.len() as u32; + + for (coldkey, stake_amount) in stakes { + Stake::::insert(new_hotkey, &coldkey, stake_amount); + writes = writes.saturating_add(1u64); // One write for insert + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); + if !staking_hotkeys.contains(new_hotkey) { + staking_hotkeys.push(new_hotkey.clone()); + writes = writes.saturating_add(1u64); // One write for insert + } + if let Some(pos) = staking_hotkeys.iter().position(|x| x == old_hotkey) { + staking_hotkeys.remove(pos); + writes = writes.saturating_add(1u64); // One write for remove + } + StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); + writes = writes.saturating_add(1u64); // One write for insert + } + + // Clear the prefix for the old hotkey after transferring all stakes + let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); + writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix + + // TODO: Remove all entries for old hotkey from StakingHotkeys map + + weight.saturating_accrue(T::DbWeight::get().writes(writes)); + } + + /// Swaps the network membership status of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + pub fn swap_is_network_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); + weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); + for netuid in netuid_is_member.iter() { + IsNetworkMember::::insert(new_hotkey, netuid, true); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + } + } + + /// Swaps the axons of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Weight Calculation + /// + /// * Reads: 1 for each network ID if the old hotkey exists in that network. + /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). + pub fn swap_axons( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + for netuid in netuid_is_member.iter() { + if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { + Axons::::remove(netuid, old_hotkey); + Axons::::insert(netuid, new_hotkey, axon_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + } + + /// Swaps the references in the keys storage map of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + pub fn swap_keys( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + let mut writes: u64 = 0; + for netuid in netuid_is_member { + let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); + for (uid, key) in keys { + if key == *old_hotkey { + log::info!("old hotkey found: {:?}", old_hotkey); + Keys::::insert(netuid, uid, new_hotkey.clone()); + } + writes = writes.saturating_add(2u64); + } + } + log::info!("writes: {:?}", writes); + weight.saturating_accrue(T::DbWeight::get().writes(writes)); + } + + /// Swaps the loaded emission of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + pub fn swap_loaded_emission( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + for netuid in netuid_is_member { + if let Some(mut emissions) = LoadedEmission::::get(netuid) { + for emission in emissions.iter_mut() { + if emission.0 == *old_hotkey { + emission.0 = new_hotkey.clone(); + } + } + LoadedEmission::::insert(netuid, emissions); + } + } + weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); + } + + /// Swaps the UIDs of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + pub fn swap_uids( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + for netuid in netuid_is_member.iter() { + if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { + Uids::::remove(netuid, old_hotkey); + Uids::::insert(netuid, new_hotkey, uid); + weight.saturating_accrue(T::DbWeight::get().writes(2)); + } + } + } + + /// Swaps the Prometheus data of the hotkey. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. + /// * `weight` - The weight of the transaction. + /// + /// # Weight Calculation + /// + /// * Reads: 1 for each network ID if the old hotkey exists in that network. + /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). + pub fn swap_prometheus( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + netuid_is_member: &[u16], + weight: &mut Weight, + ) { + for netuid in netuid_is_member.iter() { + if let Ok(prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { + Prometheus::::remove(netuid, old_hotkey); + Prometheus::::insert(netuid, new_hotkey, prometheus_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } else { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + } + } + } + + /// Swaps the total hotkey-coldkey stakes for the current interval. + /// + /// # Arguments + /// + /// * `old_hotkey` - The old hotkey. + /// * `new_hotkey` - The new hotkey. + /// * `weight` - The weight of the transaction. + /// + pub fn swap_total_hotkey_coldkey_stakes_this_interval( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) { + let stakes: Vec<(T::AccountId, (u64, u64))> = + TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); + log::info!("Stakes to swap: {:?}", stakes); + for (coldkey, stake) in stakes { + log::info!( + "Swapping stake for coldkey: {:?}, stake: {:?}", + coldkey, + stake + ); + TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake); + TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); + weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove + } + } + + + pub fn swap_senate_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + Ok(()) + } +} diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 676b3cd35..526a58b4e 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -7,7 +7,7 @@ use crate::mock::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; -use pallet_subtensor::math::safe_exp; +use pallet_subtensor::epoch::math::safe_exp; use pallet_subtensor::*; use rand::{distributions::Uniform, rngs::StdRng, seq::SliceRandom, thread_rng, Rng, SeedableRng}; use sp_core::U256; @@ -1496,7 +1496,7 @@ fn test_set_alpha_disabled() { // Enable Liquid Alpha and setup SubtensorModule::set_liquid_alpha_enabled(netuid, true); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); @@ -2574,7 +2574,7 @@ fn test_get_set_alpha() { // Enable Liquid Alpha and setup SubtensorModule::set_liquid_alpha_enabled(netuid, true); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 360568235..6d79d4794 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -67,7 +67,7 @@ fn test_migration_fix_total_stake_maps() { assert_ne!(SubtensorModule::get_total_stake(), total_stake_amount); // Run the migration to fix the total stake maps - pallet_subtensor::migration::migrate_to_v2_fixed_total_stake::(); + pallet_subtensor::migrations::migrate_to_v2_fixed_total_stake::migrate_to_v2_fixed_total_stake::(); // Verify that the total stake is now correct assert_eq!(SubtensorModule::get_total_stake(), total_stake_amount); @@ -107,19 +107,19 @@ fn test_migration_fix_total_stake_maps() { #[test] // To run this test with cargo, use the following command: -// cargo test --package pallet-subtensor --test migration test_migration5_total_issuance -fn test_migration5_total_issuance() { +// cargo test --package pallet-subtensor --test migration test_migrate_total_issuance +fn test_migrate_total_issuance() { new_test_ext(1).execute_with(|| { // Run the migration to check total issuance. let test: bool = true; assert_eq!(SubtensorModule::get_total_issuance(), 0); - pallet_subtensor::migration::migration5_total_issuance::(test); + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(test); assert_eq!(SubtensorModule::get_total_issuance(), 0); SubtensorModule::add_balance_to_coldkey_account(&U256::from(1), 10000); assert_eq!(SubtensorModule::get_total_issuance(), 0); - pallet_subtensor::migration::migration5_total_issuance::(test); + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(test); assert_eq!(SubtensorModule::get_total_issuance(), 10000); SubtensorModule::increase_stake_on_coldkey_hotkey_account( @@ -128,7 +128,7 @@ fn test_migration5_total_issuance() { 30000, ); assert_eq!(SubtensorModule::get_total_issuance(), 10000); - pallet_subtensor::migration::migration5_total_issuance::(test); + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(test); assert_eq!(SubtensorModule::get_total_issuance(), 10000 + 30000); }) } @@ -152,7 +152,7 @@ fn test_total_issuance_global() { )); SubtensorModule::set_max_allowed_uids(netuid, 1); // Set the maximum allowed unique identifiers for the network to 1. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. - pallet_subtensor::migration::migration5_total_issuance::(true); // Pick up lock. + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(true); // Pick up lock. assert_eq!(SubtensorModule::get_total_issuance(), lockcost); // Verify the total issuance is updated to 20000 after migration. assert!(SubtensorModule::if_subnet_exist(netuid)); @@ -162,7 +162,7 @@ fn test_total_issuance_global() { let _coldkey_account_id_1 = U256::from(1); // Define a coldkey account ID for further operations. assert_eq!(SubtensorModule::get_total_issuance(), lockcost); // Ensure the total issuance starts at 0 before the migration. SubtensorModule::add_balance_to_coldkey_account(&coldkey, account_balance); // Add a balance of 20000 to the coldkey account. - pallet_subtensor::migration::migration5_total_issuance::(true); // Execute the migration to update total issuance. + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(true); // Execute the migration to update total issuance. assert_eq!( SubtensorModule::get_total_issuance(), account_balance + lockcost @@ -185,7 +185,7 @@ fn test_total_issuance_global() { SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost ); // Verify the total issuance is reduced to 10000 after burning. - pallet_subtensor::migration::migration5_total_issuance::(true); // Execute the migration to update total issuance. + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(true); // Execute the migration to update total issuance. assert_eq!( SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost @@ -202,7 +202,7 @@ fn test_total_issuance_global() { SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost ); // Same - pallet_subtensor::migration::migration5_total_issuance::(true); // Fix issuance + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(true); // Fix issuance assert_eq!( SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost + new_stake @@ -222,7 +222,7 @@ fn test_total_issuance_global() { SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost + new_stake + emission ); // Verify the total issuance reflects the staked amount and emission value that has been put through the epoch. - pallet_subtensor::migration::migration5_total_issuance::(true); // Test migration does not change amount. + pallet_subtensor::migrations::migrate_total_issuance::migrate_total_issuance::(true); // Test migration does not change amount. assert_eq!( SubtensorModule::get_total_issuance(), account_balance + lockcost - burn_cost + new_stake + emission @@ -244,7 +244,7 @@ fn test_migration_transfer_nets_to_foundation() { // Run the migration to transfer ownership let hex = hex_literal::hex!["feabaafee293d3b76dae304e2f9d885f77d2b17adab9e17e921b321eccd61c77"]; - pallet_subtensor::migration::migrate_transfer_ownership_to_foundation::(hex); + pallet_subtensor::migrations::migrate_transfer_ownership_to_foundation::migrate_transfer_ownership_to_foundation::(hex); log::info!("new owner: {:?}", SubtensorModule::get_subnet_owner(1)); }) @@ -258,7 +258,7 @@ fn test_migration_delete_subnet_3() { assert!(SubtensorModule::if_subnet_exist(3)); // Run the migration to transfer ownership - pallet_subtensor::migration::migrate_delete_subnet_3::(); + pallet_subtensor::migrations::migrate_delete_subnet_3::migrate_delete_subnet_3::(); assert!(!SubtensorModule::if_subnet_exist(3)); }) @@ -272,7 +272,7 @@ fn test_migration_delete_subnet_21() { assert!(SubtensorModule::if_subnet_exist(21)); // Run the migration to transfer ownership - pallet_subtensor::migration::migrate_delete_subnet_21::(); + pallet_subtensor::migrations::migrate_delete_subnet_21::migrate_delete_subnet_21::(); assert!(!SubtensorModule::if_subnet_exist(21)); }) @@ -288,7 +288,7 @@ fn test_migrate_fix_total_coldkey_stake() { Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::do_migrate_fix_total_coldkey_stake::(); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -303,7 +303,7 @@ fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::do_migrate_fix_total_coldkey_stake::(); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -317,7 +317,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry() { Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); Stake::::insert(U256::from(3), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::do_migrate_fix_total_coldkey_stake::(); assert_eq!(TotalColdkeyStake::::get(coldkey), 30000); }) } @@ -329,7 +329,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::do_migrate_fix_total_coldkey_stake::(); assert_eq!(TotalColdkeyStake::::get(coldkey), 0); }) } @@ -343,7 +343,7 @@ fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); Stake::::insert(U256::from(2), U256::from(0), 10000); - pallet_subtensor::migration::do_migrate_fix_total_coldkey_stake::(); + pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::do_migrate_fix_total_coldkey_stake::(); assert_eq!(TotalColdkeyStake::::get(coldkey), 20000); }) } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index fc784f46f..71e1d593b 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -168,7 +168,6 @@ parameter_types! { 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 SubtensorInitialBaseDifficulty: u64 = 10_000; // Base difficulty } // Configure collective pallet for council @@ -379,7 +378,6 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - type InitialBaseDifficulty = SubtensorInitialBaseDifficulty; } impl pallet_utility::Config for Test { diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 7c6622670..d4e8448a1 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -4,7 +4,7 @@ use crate::mock::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use frame_system::{EventRecord, Phase}; -use pallet_subtensor::migration; +use pallet_subtensor::migrations; use pallet_subtensor::Error; use sp_core::{Get, H256, U256}; @@ -22,7 +22,7 @@ fn record(event: RuntimeEvent) -> EventRecord { #[test] fn test_root_register_network_exist() { new_test_ext(1).execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let hotkey_account_id: U256 = U256::from(1); let coldkey_account_id = U256::from(667); assert_ok!(SubtensorModule::root_register( @@ -63,7 +63,7 @@ fn test_set_weights_not_root_error() { #[test] fn test_root_register_normal_on_root_fails() { new_test_ext(1).execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); // Test fails because normal registrations are not allowed // on the root network. let root_netuid: u16 = 0; @@ -107,7 +107,7 @@ fn test_root_register_normal_on_root_fails() { #[test] fn test_root_register_stake_based_pruning_works() { new_test_ext(1).execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); // Add two networks. let root_netuid: u16 = 0; let other_netuid: u16 = 1; @@ -196,7 +196,7 @@ fn test_root_register_stake_based_pruning_works() { fn test_root_set_weights() { new_test_ext(1).execute_with(|| { System::set_block_number(0); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let n: usize = 10; let root_netuid: u16 = 0; @@ -338,7 +338,7 @@ fn test_root_set_weights() { fn test_root_set_weights_out_of_order_netuids() { new_test_ext(1).execute_with(|| { System::set_block_number(0); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let n: usize = 10; let root_netuid: u16 = 0; @@ -458,7 +458,7 @@ fn test_root_set_weights_out_of_order_netuids() { fn test_root_subnet_creation_deletion() { new_test_ext(1).execute_with(|| { System::set_block_number(0); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); // Owner of subnets. let owner: U256 = U256::from(0); @@ -538,7 +538,7 @@ fn test_root_subnet_creation_deletion() { fn test_network_pruning() { new_test_ext(1).execute_with(|| { System::set_block_number(0); - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); assert_eq!(SubtensorModule::get_total_issuance(), 0); @@ -630,7 +630,7 @@ fn test_network_pruning() { #[test] fn test_network_prune_results() { new_test_ext(1).execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::set_network_immunity_period(3); SubtensorModule::set_network_min_lock(0); @@ -671,7 +671,7 @@ fn test_network_prune_results() { #[test] fn test_weights_after_network_pruning() { new_test_ext(1).execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); assert_eq!(SubtensorModule::get_total_issuance(), 0); diff --git a/pallets/subtensor/tests/senate.rs b/pallets/subtensor/tests/senate.rs index bcec1a63a..e1f33db5e 100644 --- a/pallets/subtensor/tests/senate.rs +++ b/pallets/subtensor/tests/senate.rs @@ -15,7 +15,7 @@ use sp_runtime::{ use frame_system::pallet_prelude::*; use frame_system::Config; use pallet_collective::Event as CollectiveEvent; -use pallet_subtensor::migration; +use pallet_subtensor::migrations; use pallet_subtensor::Error; pub fn new_test_ext() -> sp_io::TestExternalities { @@ -57,7 +57,7 @@ fn record(event: RuntimeEvent) -> EventRecord { #[test] fn test_senate_join_works() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -125,7 +125,7 @@ fn test_senate_join_works() { #[test] fn test_senate_vote_works() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -233,7 +233,7 @@ fn test_senate_vote_works() { #[test] fn test_senate_vote_not_member() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -294,7 +294,7 @@ fn test_senate_vote_not_member() { #[test] fn test_senate_leave_works() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -362,7 +362,7 @@ fn test_senate_leave_works() { #[test] fn test_senate_leave_vote_removal() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -501,7 +501,7 @@ fn test_senate_leave_vote_removal() { #[test] fn test_senate_not_leave_when_stake_removed() { new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -582,7 +582,7 @@ fn test_senate_not_leave_when_stake_removed() { fn test_senate_join_current_delegate() { // Test that a current delegate can join the senate new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; @@ -656,7 +656,7 @@ fn test_senate_join_current_delegate() { fn test_adjust_senate_events() { // Test the events emitted after adjusting the senate successfully new_test_ext().execute_with(|| { - migration::migrate_create_root_network::(); + migrations::migrate_create_root_network::migrate_create_root_network::(); let netuid: u16 = 1; let tempo: u16 = 13; diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 5db439e5b..a923a7010 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,21 +1,14 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::arithmetic_side_effects)] -use frame_support::pallet_prelude::{ - InvalidTransaction, TransactionValidity, TransactionValidityError, -}; -use frame_support::traits::{OnFinalize, OnIdle, OnInitialize}; -use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::DispatchError; use mock::*; -use pallet_balances::Call as BalancesCall; use pallet_subtensor::*; use sp_core::{H256, U256}; -use sp_runtime::traits::SignedExtension; /*********************************************************** staking::add_stake() tests @@ -3136,1067 +3129,6 @@ fn test_rate_limits_enforced_on_increase_take() { }); } -// Helper function to set up a test environment -fn setup_test_environment() -> (AccountId, AccountId, AccountId) { - let current_coldkey = U256::from(1); - let hotkey = U256::from(2); - let new_coldkey = U256::from(3); - // Register the neuron to a new network - let netuid = 1; - add_network(netuid, 0, 0); - - // Register the hotkey and associate it with the current coldkey - register_ok_neuron(1, hotkey, current_coldkey, 0); - - // Add some balance to the hotkey - SubtensorModule::add_balance_to_coldkey_account(¤t_coldkey, 1000); - - // Stake some amount - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(current_coldkey), - hotkey, - 500 - )); - - (current_coldkey, hotkey, new_coldkey) -} - -/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_success --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_success() { - new_test_ext(1).execute_with(|| { - let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - SubtensorModule::add_balance_to_coldkey_account( - ¤t_coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - // Check that ColdkeySwapDestinations is populated correctly - assert_eq!( - pallet_subtensor::ColdkeySwapDestinations::::get(current_coldkey), - vec![new_coldkey] - ); - - // Check that drain block is set correctly - let drain_block: u64 = 7200 * 3 + 1; - - log::info!( - "ColdkeysToSwapAtBlock before scheduling: {:?}", - pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block) - ); - - assert_eq!( - pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block), - vec![current_coldkey] - ); - log::info!("Drain block set correctly: {:?}", drain_block); - log::info!( - "Drain block {:?}", - pallet_subtensor::ColdkeysToSwapAtBlock::::get(drain_block) - ); - - // Make 5400 blocks pass - run_to_block(drain_block); - - // Run unstaking - SubtensorModule::swap_coldkeys_this_block(&BlockWeights::get().max_block).unwrap(); - log::info!( - "Arbitrated coldkeys for block: {:?}", - SubtensorModule::get_current_block_as_u64() - ); - - // Check the hotkey stake. - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 500); - - // Get the owner of the hotkey now new key. - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), - new_coldkey - ); - - // Check that the balance has been transferred to the new coldkey - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 500 - ); // The new key as the 500 - }); -} - -/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_same_coldkey --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_same_coldkey() { - new_test_ext(1).execute_with(|| { - let (current_coldkey, _hotkey, _) = setup_test_environment(); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - assert_noop!( - SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - ¤t_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - ), - Error::::SameColdkey - ); - }); -} - -/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_no_balance --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_no_balance() { - new_test_ext(1).execute_with(|| { - // Create accounts manually - let current_coldkey: AccountId = U256::from(1); - let hotkey: AccountId = U256::from(2); - let new_coldkey: AccountId = U256::from(3); - - add_network(1, 0, 0); - - // Register the hotkey and associate it with the current coldkey - register_ok_neuron(1, hotkey, current_coldkey, 0); - - // Print initial balances - log::info!( - "Initial current_coldkey balance: {:?}", - Balances::total_balance(¤t_coldkey) - ); - log::info!( - "Initial hotkey balance: {:?}", - Balances::total_balance(&hotkey) - ); - log::info!( - "Initial new_coldkey balance: {:?}", - Balances::total_balance(&new_coldkey) - ); - - // Ensure there's no balance in any of the accounts - assert_eq!(Balances::total_balance(¤t_coldkey), 0); - assert_eq!(Balances::total_balance(&hotkey), 0); - assert_eq!(Balances::total_balance(&new_coldkey), 0); - - // Generate valid PoW - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - // Try to schedule coldkey swap - let result = SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce, - ); - - // Print the result - log::info!("Result of arbitrated_coldkey_swap: {:?}", result); - - // Verify that the operation failed due to insufficient balance - assert_noop!( - result, - Error::::InsufficientBalanceToPerformColdkeySwap - ); - - // Print final balances - log::info!( - "Final current_coldkey balance: {:?}", - Balances::total_balance(¤t_coldkey) - ); - log::info!( - "Final hotkey balance: {:?}", - Balances::total_balance(&hotkey) - ); - log::info!( - "Final new_coldkey balance: {:?}", - Balances::total_balance(&new_coldkey) - ); - - // Verify that no balance was transferred - assert_eq!(Balances::total_balance(¤t_coldkey), 0); - assert_eq!(Balances::total_balance(&hotkey), 0); - assert_eq!(Balances::total_balance(&new_coldkey), 0); - }); -} - -// To run this test, use the following command: -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test staking -- test_arbitrated_coldkey_swap_with_no_stake --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_with_no_stake() { - new_test_ext(1).execute_with(|| { - // Create accounts manually - let current_coldkey: AccountId = U256::from(1); - let hotkey: AccountId = U256::from(2); - let new_coldkey: AccountId = U256::from(3); - - add_network(1, 0, 0); - - // Register the hotkey and associate it with the current coldkey - register_ok_neuron(1, hotkey, current_coldkey, 0); - - // Add balance to the current coldkey without staking - Balances::make_free_balance_be(¤t_coldkey, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP); - - // Print initial balances - log::info!( - "Initial current_coldkey balance: {:?}", - Balances::total_balance(¤t_coldkey) - ); - log::info!( - "Initial hotkey balance: {:?}", - Balances::total_balance(&hotkey) - ); - log::info!( - "Initial new_coldkey balance: {:?}", - Balances::total_balance(&new_coldkey) - ); - - // Ensure initial balances are correct - assert_eq!( - Balances::total_balance(¤t_coldkey), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - assert_eq!(Balances::total_balance(&hotkey), 0); - assert_eq!(Balances::total_balance(&new_coldkey), 0); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - // Schedule coldkey swap - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - // Make 5400 blocks pass, simulating on_idle for each block - let drain_block: u64 = 7200 * 3 + 1; - for _ in 0..drain_block { - next_block(); - SubtensorModule::on_idle(System::block_number(), Weight::MAX); - } - - // Print final balances - log::info!( - "Final current_coldkey balance: {:?}", - Balances::total_balance(¤t_coldkey) - ); - log::info!( - "Final hotkey balance: {:?}", - Balances::total_balance(&hotkey) - ); - log::info!( - "Final new_coldkey balance: {:?}", - Balances::total_balance(&new_coldkey) - ); - - // Check that the balance has been transferred to the new coldkey - assert_eq!( - Balances::total_balance(&new_coldkey), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - assert_eq!(Balances::total_balance(¤t_coldkey), 0); - }); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_with_multiple_stakes --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_with_multiple_stakes() { - new_test_ext(1).execute_with(|| { - let (current_coldkey, hotkey, new_coldkey) = setup_test_environment(); - - SubtensorModule::set_target_stakes_per_interval(10); - SubtensorModule::add_balance_to_coldkey_account( - ¤t_coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - // Add more stake - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(current_coldkey), - hotkey, - 300 - )); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - // Make 5400 blocks pass, simulating on_idle for each block - let drain_block: u64 = 7200 * 3 + 1; - for _ in 0..drain_block { - next_block(); - SubtensorModule::on_idle(System::block_number(), Weight::MAX); - } - - // Check that all stake has been removed - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 800); - - // Owner has changed - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey), - new_coldkey - ); - - // Check that the full balance has been transferred to the new coldkey - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 200 - ); - - // Check that the full balance has been transferred to the new coldkey - assert_eq!(SubtensorModule::get_coldkey_balance(¤t_coldkey), 0); - }); -} -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_arbitrated_coldkey_swap_multiple_arbitrations --exact --nocapture -#[test] -fn test_arbitrated_coldkey_swap_multiple_arbitrations() { - new_test_ext(1).execute_with(|| { - // Set a very low base difficulty for testing - BaseDifficulty::::put(1); - - // Create coldkey with three choices. - let coldkey: AccountId = U256::from(1); - let new_coldkey1: AccountId = U256::from(2); - let new_coldkey2: AccountId = U256::from(3); - let new_coldkey3: AccountId = U256::from(4); - let hotkey: AccountId = U256::from(5); - - // Setup network state. - add_network(1, 0, 0); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - ArbitrationPeriod::::put(5); // Set arbitration period to 5 blocks - register_ok_neuron(1, hotkey, coldkey, 0); - - let current_block = SubtensorModule::get_current_block_as_u64(); - - // Generate valid PoW for each swap attempt - let (work1, nonce1) = generate_valid_pow(&coldkey, current_block, U256::from(1)); - let (work2, nonce2) = generate_valid_pow(&coldkey, current_block, U256::from(2)); - let (work3, nonce3) = generate_valid_pow(&coldkey, current_block, U256::from(4)); - - // Schedule three swaps - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey1, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey2, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - )); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey3, - work3.to_fixed_bytes().to_vec(), - current_block, - nonce3 - )); - - // All three keys are added in swap destinations. - assert_eq!( - pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), - vec![new_coldkey1, new_coldkey2, new_coldkey3] - ); - - // Simulate the passage of blocks and on_idle calls - for i in 0..(7200 * 3 + 1) { - next_block(); - SubtensorModule::on_idle(System::block_number(), Weight::MAX); - - log::info!( - "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", - i + 1, - SubtensorModule::coldkey_in_arbitration(&coldkey), - pallet_subtensor::ColdkeySwapDestinations::::get(coldkey) - ); - } - - // Check that the swap destinations remain unchanged due to multiple (>2) swap calls - assert_eq!( - pallet_subtensor::ColdkeySwapDestinations::::get(coldkey), - vec![new_coldkey1, new_coldkey2, new_coldkey3], - "ColdkeySwapDestinations should remain unchanged with more than two swap calls" - ); - - // Key remains in arbitration due to multiple (>2) swap calls - assert!( - SubtensorModule::coldkey_in_arbitration(&coldkey), - "Coldkey should remain in arbitration with more than two swap calls" - ); - - // Check that no balance has been transferred - assert_eq!( - SubtensorModule::get_coldkey_balance(&coldkey), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - "Original coldkey balance should remain unchanged" - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey1), - 0, - "New coldkey1 should not receive any balance" - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey2), - 0, - "New coldkey2 should not receive any balance" - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey3), - 0, - "New coldkey3 should not receive any balance" - ); - }); -} - -// TODO: Verify that we never want more than 2 destinations for a coldkey -#[test] -fn test_arbitrated_coldkey_swap_existing_destination() { - new_test_ext(1).execute_with(|| { - let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); - let another_coldkey = U256::from(4); - let third_coldkey = U256::from(5); - - let current_block = SubtensorModule::get_current_block_as_u64(); - - SubtensorModule::add_balance_to_coldkey_account( - ¤t_coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - // First swap attempt (0 existing destinations) - let difficulty1 = SubtensorModule::calculate_pow_difficulty(0); - let (work1, nonce1) = generate_valid_pow(¤t_coldkey, current_block, difficulty1); - - // Schedule a swap to new_coldkey - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey, - &new_coldkey, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - - // Second swap attempt (1 existing destination) - let difficulty2 = SubtensorModule::calculate_pow_difficulty(1); - let (work2, nonce2) = generate_valid_pow(¤t_coldkey, current_block, difficulty2); - - // Attempt to schedule a swap to the same new_coldkey again - assert_noop!( - SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - ), - Error::::DuplicateColdkey - ); - - // Schedule a swap to another_coldkey (still 1 existing destination) - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &another_coldkey, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - )); - - // Third swap attempt (2 existing destinations) - let difficulty3 = SubtensorModule::calculate_pow_difficulty(2); - let (work3, nonce3) = generate_valid_pow(¤t_coldkey, current_block, difficulty3); - - // Attempt to schedule a third swap - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &third_coldkey, - work3.to_fixed_bytes().to_vec(), - current_block, - nonce3 - )); - }); -} - -#[test] -fn test_arbitration_period_extension() { - new_test_ext(1).execute_with(|| { - let (current_coldkey, _hotkey, new_coldkey) = setup_test_environment(); - let another_coldkey = U256::from(4); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow( - ¤t_coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - let (work2, nonce2) = - generate_valid_pow(¤t_coldkey, current_block, U256::from(20_000_000u64)); - SubtensorModule::add_balance_to_coldkey_account( - ¤t_coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - // Schedule a swap to new_coldkey - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &new_coldkey, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - - // Schedule a swap to another_coldkey - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ¤t_coldkey.clone(), - &another_coldkey, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - )); - - // Check that the arbitration period is extended - let arbitration_block = - SubtensorModule::get_current_block_as_u64() + ArbitrationPeriod::::get(); - assert_eq!( - pallet_subtensor::ColdkeyArbitrationBlock::::get(current_coldkey), - arbitration_block - ); - }); -} - -#[test] -fn test_concurrent_arbitrated_coldkey_swaps() { - new_test_ext(1).execute_with(|| { - // Manually create accounts - let coldkey1: AccountId = U256::from(1); - let hotkey1: AccountId = U256::from(2); - let new_coldkey1: AccountId = U256::from(3); - - let coldkey2: AccountId = U256::from(4); - let hotkey2: AccountId = U256::from(5); - let new_coldkey2: AccountId = U256::from(6); - - // Add networks - let netuid1: u16 = 1; - let netuid2: u16 = 2; - add_network(netuid1, 13, 0); - add_network(netuid2, 13, 0); - - // Register neurons in different networks - register_ok_neuron(netuid1, hotkey1, coldkey1, 0); - register_ok_neuron(netuid2, hotkey2, coldkey2, 0); - - // Add balance to coldkeys - SubtensorModule::add_balance_to_coldkey_account( - &coldkey1, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey2, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow( - &coldkey1, - current_block, - U256::from(BaseDifficulty::::get()), - ); - let (work2, nonce2) = generate_valid_pow( - &coldkey2, - current_block, - U256::from(BaseDifficulty::::get()), - ); - // Schedule swaps for both coldkeys - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey1.clone(), - &new_coldkey1, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey2.clone(), - &new_coldkey2, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - )); - // Make 5400 blocks pass - let drain_block: u64 = 7200 * 3 + 1; - run_to_block(drain_block); - - // Run arbitration - SubtensorModule::swap_coldkeys_this_block(&BlockWeights::get().max_block).unwrap(); - - // Check that the balances have been transferred correctly - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey1), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey2), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - }); -} - -// #[test] -// fn test_get_remaining_arbitration_period() { -// new_test_ext(1).execute_with(|| { -// let coldkey_account_id = U256::from(12345); // arbitrary coldkey -// let new_coldkey_account_id = U256::from(54321); // arbitrary new coldkey - -// let current_block = SubtensorModule::get_current_block_as_u64(); -// let (work, nonce) = generate_valid_pow( -// &coldkey_account_id, -// current_block, -// U256::from(BaseDifficulty::::get()), -// ); - -// SubtensorModule::add_balance_to_coldkey_account( -// &coldkey_account_id, -// MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, -// ); - -// // Schedule a coldkey swap to set the arbitration block -// assert_ok!(SubtensorModule::do_schedule_coldkey_swap( -// &coldkey_account_id.clone(), -// &new_coldkey_account_id, -// work.to_fixed_bytes().to_vec(), -// current_block, -// nonce -// )); - -// // Get the current block number and arbitration period -// let current_block: u64 = SubtensorModule::get_current_block_as_u64(); -// let arbitration_period: u64 = ArbitrationPeriod::::get(); -// log::info!("arbitration_period: {:?}", arbitration_period); -// let arbitration_block: u64 = current_block + arbitration_period; -// log::info!("arbitration_block: {:?}", arbitration_block); - -// // Check if the remaining arbitration period is correct -// let remaining_period = -// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); -// assert_eq!(remaining_period, arbitration_period); - -// // Move the current block forward and check again -// step_block(50); -// let remaining_period = -// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); -// assert_eq!(remaining_period, arbitration_period - 50); - -// // Move the current block beyond the arbitration block and check again -// step_block((arbitration_period as u16) - 50 + 1); -// let remaining_period = -// SubtensorModule::get_remaining_arbitration_period(&coldkey_account_id); -// assert_eq!(remaining_period, 0); -// }); -// } - -#[test] -fn test_transfer_coldkey_in_arbitration() { - new_test_ext(1).execute_with(|| { - let coldkey_account_id = U256::from(1); - let recipient_account_id = U256::from(2); - let new_coldkey_account_id = U256::from(3); - - // Add balance to coldkey - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - &coldkey_account_id, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - // Schedule a coldkey swap to put the coldkey in arbitration - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id.clone(), - &new_coldkey_account_id, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - // Try to transfer balance - let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: recipient_account_id, - value: 1000, - }); - - assert_eq!( - validate_transaction(&coldkey_account_id, &call), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); -} - -#[test] -fn test_add_stake_coldkey_in_arbitration() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(561337); - let coldkey_account_id = U256::from(61337); - let new_coldkey_account_id = U256::from(71337); - let netuid: u16 = 1; - let start_nonce: u64 = 0; - let tempo: u16 = 13; - - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - &coldkey_account_id, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - // Schedule a coldkey swap to put the coldkey in arbitration - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id.clone(), - &new_coldkey_account_id, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - let call = RuntimeCall::SubtensorModule(crate::Call::add_stake { - hotkey: hotkey_account_id, - amount_staked: 1000, - }); - - // This should now be Ok - assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); - }) -} - -#[test] -fn test_remove_stake_coldkey_in_arbitration() { - new_test_ext(1).execute_with(|| { - let hotkey_account_id = U256::from(561337); - let coldkey_account_id = U256::from(61337); - let new_coldkey_account_id = U256::from(71337); - let netuid: u16 = 1; - let start_nonce: u64 = 0; - let tempo: u16 = 13; - - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey_account_id, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 1000); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work, nonce) = generate_valid_pow( - &coldkey_account_id, - current_block, - U256::from(BaseDifficulty::::get()), - ); - - // Schedule a coldkey swap to put the coldkey in arbitration - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey_account_id.clone(), - &new_coldkey_account_id, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - )); - - let call = RuntimeCall::SubtensorModule(crate::Call::remove_stake { - hotkey: hotkey_account_id, - amount_unstaked: 500, - }); - - // This should now be Ok - assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); - }); -} - -#[test] -fn test_transfer_coldkey_not_in_arbitration() { - new_test_ext(1).execute_with(|| { - let coldkey_account_id = U256::from(61337); - let recipient_account_id = U256::from(71337); - - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); - - let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { - dest: recipient_account_id, - value: 1000, - }); - - // This should be Ok - assert!(validate_transaction(&coldkey_account_id, &call).is_ok()); - }); -} - -fn validate_transaction(who: &AccountId, call: &RuntimeCall) -> TransactionValidity { - SubtensorSignedExtension::::new().validate(who, call, &DispatchInfo::default(), 0) -} - -// Helper function to generate valid PoW -fn generate_valid_pow(coldkey: &U256, block_number: u64, difficulty: U256) -> (H256, u64) { - let mut nonce: u64 = 0; - loop { - let work = SubtensorModule::create_seal_hash(block_number, nonce, coldkey); - if SubtensorModule::hash_meets_difficulty(&work, difficulty) { - return (work, nonce); - } - nonce += 1; - } -} - -// Helper function to advance to the next block and run hooks -fn next_block() { - let current_block = System::block_number(); - System::on_finalize(current_block); - System::set_block_number(current_block + 1); - System::on_initialize(System::block_number()); - SubtensorModule::on_initialize(System::block_number()); -} - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test staking -- test_coldkey_meets_enough --exact --nocapture -#[test] -fn test_coldkey_meets_enough() { - new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(2); - let netuid = 1u16; - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, coldkey, 0); - let current_block = SubtensorModule::get_current_block_as_u64(); - let (work1, nonce1) = generate_valid_pow( - &coldkey, - current_block, - U256::from(BaseDifficulty::::get()), - ); - assert_err!( - SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - ), - Error::::InsufficientBalanceToPerformColdkeySwap - ); - SubtensorModule::add_balance_to_coldkey_account( - &coldkey, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &coldkey.clone(), - &new_coldkey, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - }); -} - -#[test] -fn test_comprehensive_coldkey_swap_scenarios() { - new_test_ext(1).execute_with(|| { - // Set arbitration period to 5 blocks - ArbitrationPeriod::::put(5); - - let subnet_owner1 = U256::from(1); - let subnet_owner2 = U256::from(2); - let regular_user = U256::from(3); - let new_coldkey1 = U256::from(4); - let new_coldkey2 = U256::from(5); - let new_coldkey3 = U256::from(6); - let netuid1 = 1; - let netuid2 = 2; - - // Add networks and register subnet owners - add_network(netuid1, 13, 0); - add_network(netuid2, 13, 0); - SubnetOwner::::insert(netuid1, subnet_owner1); - SubnetOwner::::insert(netuid2, subnet_owner2); - - // Add balance to subnet owners and regular user - SubtensorModule::add_balance_to_coldkey_account( - &subnet_owner1, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - SubtensorModule::add_balance_to_coldkey_account( - &subnet_owner2, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - ); - SubtensorModule::add_balance_to_coldkey_account( - ®ular_user, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2, - ); - - // Set a very low base difficulty for testing - BaseDifficulty::::put(1); - - let current_block = SubtensorModule::get_current_block_as_u64(); - - // Schedule swaps for subnet owners and regular user - let (work1, nonce1) = generate_valid_pow(&subnet_owner1, current_block, U256::from(BaseDifficulty::::get())); - let (work2, nonce2) = generate_valid_pow(&subnet_owner2, current_block, U256::from(BaseDifficulty::::get())); - let (work3, nonce3) = generate_valid_pow(®ular_user, current_block, U256::from(BaseDifficulty::::get())); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &subnet_owner1, - &new_coldkey1, - work1.to_fixed_bytes().to_vec(), - current_block, - nonce1 - )); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &subnet_owner2, - &new_coldkey2, - work2.to_fixed_bytes().to_vec(), - current_block, - nonce2 - )); - - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - ®ular_user, - &new_coldkey3, - work3.to_fixed_bytes().to_vec(), - current_block, - nonce3 - )); - - // Check if swaps were scheduled correctly - assert_eq!( - ColdkeySwapDestinations::::get(subnet_owner1), - vec![new_coldkey1] - ); - assert_eq!( - ColdkeySwapDestinations::::get(subnet_owner2), - vec![new_coldkey2] - ); - assert_eq!( - ColdkeySwapDestinations::::get(regular_user), - vec![new_coldkey3] - ); - - // Run through the arbitration period plus one block - for i in 0..6 { - next_block(); - SubtensorModule::on_idle(System::block_number(), Weight::MAX); - - log::info!( - "Block {}: Coldkey in arbitration: {}, Swap destinations: {:?}", - i + 1, - SubtensorModule::coldkey_in_arbitration(&subnet_owner1), - ColdkeySwapDestinations::::get(subnet_owner1) - ); - - // Test edge case: try to schedule another swap during arbitration - if i == 2 { - let (work4, nonce4) = generate_valid_pow( - &subnet_owner1, - current_block + i as u64, - U256::from(4) * U256::from(BaseDifficulty::::get()), - ); - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &subnet_owner1, - &new_coldkey2, - work4.to_fixed_bytes().to_vec(), - current_block + i as u64, - nonce4 - )); - // This should add new_coldkey2 to subnet_owner1's destinations - assert_eq!( - ColdkeySwapDestinations::::get(subnet_owner1), - vec![new_coldkey1, new_coldkey2] - ); - } - } - - // Check if swaps have been executed - log::info!( - "After arbitration period - Swap destinations for subnet_owner1: {:?}", - ColdkeySwapDestinations::::get(subnet_owner1) - ); - assert_eq!( - ColdkeySwapDestinations::::get(subnet_owner1), - vec![new_coldkey1, new_coldkey2], - "ColdkeySwapDestinations for subnet_owner1 should still contain two destinations after arbitration period" - ); - assert!(ColdkeySwapDestinations::::get(subnet_owner2).is_empty()); - assert!(ColdkeySwapDestinations::::get(regular_user).is_empty()); - - // Verify that subnet ownerships have NOT been transferred for subnet_owner1 - assert_eq!(SubnetOwner::::get(netuid1), subnet_owner1); - // But subnet_owner2's ownership should have been transferred - assert_eq!(SubnetOwner::::get(netuid2), new_coldkey2); - - // Verify regular user's balance has been transferred - assert_eq!( - SubtensorModule::get_coldkey_balance(&new_coldkey3), - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP * 2 - ); - assert_eq!(SubtensorModule::get_coldkey_balance(®ular_user), 0); - }); -} - #[test] fn test_get_total_delegated_stake_after_unstaking() { new_test_ext(1).execute_with(|| { @@ -4451,271 +3383,4 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { expected_delegated_stake, actual_delegated_stake ); }); -} - -#[test] -fn test_do_schedule_coldkey_swap_subnet_owner_skips_min_balance() { - new_test_ext(1).execute_with(|| { - let netuid = 1u16; - let subnet_owner = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let current_block = 0u64; - - add_network(netuid, 0, 0); - register_ok_neuron(netuid, hotkey, subnet_owner, 0); - - // Make subnet_owner the owner of the subnet - SubnetOwner::::insert(netuid, subnet_owner); - - // Ensure subnet_owner has less than minimum balance - assert!( - SubtensorModule::get_coldkey_balance(&subnet_owner) - < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - - // Generate valid PoW - let difficulty = U256::from(4) * U256::from(BaseDifficulty::::get()); - let (work, nonce) = generate_valid_pow(&subnet_owner, current_block, difficulty); - - // Debug prints - println!("Subnet owner: {:?}", subnet_owner); - println!("New coldkey: {:?}", new_coldkey); - println!("Current block: {}", current_block); - println!("Difficulty: {:?}", difficulty); - println!("Work: {:?}", work); - println!("Nonce: {}", nonce); - - // Verify the PoW - let seal = SubtensorModule::create_seal_hash(current_block, nonce, &subnet_owner); - println!("Calculated seal: {:?}", seal); - println!("Work matches seal: {}", work == seal); - println!( - "Seal meets difficulty: {}", - SubtensorModule::hash_meets_difficulty(&seal, difficulty) - ); - - // Attempt to schedule coldkey swap - let result = SubtensorModule::do_schedule_coldkey_swap( - &subnet_owner, - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce, - ); - - // Print the result - println!("Swap result: {:?}", result); - - assert_ok!(result); - - // Verify that the swap was scheduled - assert_eq!( - ColdkeySwapDestinations::::get(subnet_owner), - vec![new_coldkey] - ); - }); -} - -#[test] -fn test_do_schedule_coldkey_swap_delegate_with_500_tao_skips_min_balance() { - new_test_ext(1).execute_with(|| { - let netuid = 1u16; - let delegate_coldkey = U256::from(1); - let delegate_hotkey = U256::from(2); - let new_coldkey = U256::from(3); - let delegator = U256::from(4); - let current_block = 0u64; - - add_network(netuid, 0, 0); - register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - - // Make delegate a delegate - assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(delegate_coldkey), - delegate_hotkey - )); - - // Add more than 500 TAO of stake to the delegate's hotkey - let stake_amount = 501_000_000_000; // 501 TAO in RAO - SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(delegator), - delegate_hotkey, - stake_amount - )); - - // Debug prints - println!( - "Delegator balance: {}", - SubtensorModule::get_coldkey_balance(&delegator) - ); - println!( - "Delegate coldkey balance: {}", - SubtensorModule::get_coldkey_balance(&delegate_coldkey) - ); - println!("Stake amount: {}", stake_amount); - println!( - "Delegate hotkey total stake: {}", - SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey) - ); - println!( - "Delegate coldkey delegated stake: {}", - SubtensorModule::get_total_delegated_stake(&delegate_coldkey) - ); - - // Ensure delegate's coldkey has less than minimum balance - assert!( - SubtensorModule::get_coldkey_balance(&delegate_coldkey) - < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - "Delegate coldkey balance should be less than minimum required" - ); - - // Ensure the delegate's hotkey has more than 500 TAO delegated - assert!( - SubtensorModule::get_total_delegated_stake(&delegate_coldkey) >= 500_000_000_000, - "Delegate hotkey should have at least 500 TAO delegated" - ); - - // Generate valid PoW - let (work, nonce) = generate_valid_pow( - &delegate_coldkey, - current_block, - U256::from(4) * U256::from(BaseDifficulty::::get()), - ); - - // Debug prints - println!("Work: {:?}", work); - println!("Nonce: {}", nonce); - - // Attempt to schedule coldkey swap - let result = SubtensorModule::do_schedule_coldkey_swap( - &delegate_coldkey, - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce, - ); - - // Print the result - println!("Swap result: {:?}", result); - - assert_ok!(result); - - // Verify that the swap was scheduled - assert_eq!( - ColdkeySwapDestinations::::get(delegate_coldkey), - vec![new_coldkey] - ); - - // Additional debug prints after swap - println!( - "Coldkey swap destinations: {:?}", - ColdkeySwapDestinations::::get(delegate_coldkey) - ); - println!( - "Is coldkey in arbitration: {}", - SubtensorModule::coldkey_in_arbitration(&delegate_coldkey) - ); - }); -} - -#[test] -fn test_do_schedule_coldkey_swap_regular_user_fails_min_balance() { - new_test_ext(1).execute_with(|| { - let netuid = 1u16; - let regular_user = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let current_block = 0u64; - let nonce = 0u64; - - add_network(netuid, 0, 0); - register_ok_neuron(netuid, hotkey, regular_user, 0); - - // Ensure regular_user has less than minimum balance - assert!( - SubtensorModule::get_coldkey_balance(®ular_user) - < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - - let (work, _) = generate_valid_pow( - ®ular_user, - current_block, - U256::from(4) * U256::from(BaseDifficulty::::get()), - ); - - // Attempt to schedule coldkey swap - assert_noop!( - SubtensorModule::do_schedule_coldkey_swap( - ®ular_user, - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce - ), - Error::::InsufficientBalanceToPerformColdkeySwap - ); - - // Verify that the swap was not scheduled - assert!(ColdkeySwapDestinations::::get(regular_user).is_empty()); - }); -} - -#[test] -fn test_do_schedule_coldkey_swap_regular_user_passes_min_balance() { - new_test_ext(1).execute_with(|| { - let netuid = 1u16; - let regular_user = U256::from(1); - let new_coldkey = U256::from(2); - let hotkey = U256::from(3); - let current_block = 0u64; - - add_network(netuid, 0, 0); - register_ok_neuron(netuid, hotkey, regular_user, 0); - - // Ensure regular_user has more than minimum balance - SubtensorModule::add_balance_to_coldkey_account( - ®ular_user, - MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP + 1, - ); - assert!( - SubtensorModule::get_coldkey_balance(®ular_user) - > MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - ); - - // Generate valid PoW - let (work, nonce) = generate_valid_pow( - ®ular_user, - current_block, - U256::from(4) * U256::from(BaseDifficulty::::get()), - ); - - // Debug prints - println!("Regular user: {:?}", regular_user); - println!("New coldkey: {:?}", new_coldkey); - println!("Current block: {}", current_block); - println!("Work: {:?}", work); - println!("Nonce: {}", nonce); - - // Attempt to schedule coldkey swap - let result = SubtensorModule::do_schedule_coldkey_swap( - ®ular_user, - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce, - ); - - // Print the result - println!("Swap result: {:?}", result); - - assert_ok!(result); - - // Verify that the swap was scheduled - assert_eq!( - ColdkeySwapDestinations::::get(regular_user), - vec![new_coldkey] - ); - }); -} +} \ No newline at end of file diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 21c3a983a..1d05b1c51 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -4,9 +4,9 @@ use codec::Encode; use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; +use pallet_subtensor::*; mod mock; use mock::*; -use pallet_subtensor::*; use sp_core::U256; #[test] @@ -65,31 +65,31 @@ fn test_do_swap_hotkey_ok() { // UIDs for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( - Uids::::get(netuid, new_hotkey), - Uids::::get(netuid, old_hotkey) + pallet_subtensor::Uids::::get(netuid, new_hotkey), + pallet_subtensor::Uids::::get(netuid, old_hotkey) ); } // Prometheus for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( - Prometheus::::get(netuid, new_hotkey), - Prometheus::::get(netuid, old_hotkey) + pallet_subtensor::Prometheus::::get(netuid, new_hotkey), + pallet_subtensor::Prometheus::::get(netuid, old_hotkey) ); } // LoadedEmission for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( - LoadedEmission::::get(netuid).unwrap(), - LoadedEmission::::get(netuid).unwrap() + pallet_subtensor::LoadedEmission::::get(netuid).unwrap(), + pallet_subtensor::LoadedEmission::::get(netuid).unwrap() ); } // IsNetworkMember for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); - assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); + assert!(pallet_subtensor::IsNetworkMember::::contains_key(new_hotkey, netuid)); + assert!(!pallet_subtensor::IsNetworkMember::::contains_key(old_hotkey, netuid)); } // Owner @@ -97,34 +97,34 @@ fn test_do_swap_hotkey_ok() { // TotalHotkeyStake assert_eq!( - TotalHotkeyStake::::get(new_hotkey), - TotalHotkeyStake::::get(old_hotkey) + pallet_subtensor::TotalHotkeyStake::::get(new_hotkey), + pallet_subtensor::TotalHotkeyStake::::get(old_hotkey) ); // Delegates assert_eq!( - Delegates::::get(new_hotkey), - Delegates::::get(old_hotkey) + pallet_subtensor::Delegates::::get(new_hotkey), + pallet_subtensor::Delegates::::get(old_hotkey) ); // LastTxBlock assert_eq!( - LastTxBlock::::get(new_hotkey), - LastTxBlock::::get(old_hotkey) + pallet_subtensor::LastTxBlock::::get(new_hotkey), + pallet_subtensor::LastTxBlock::::get(old_hotkey) ); // Axons for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { assert_eq!( - Axons::::get(netuid, new_hotkey), - Axons::::get(netuid, old_hotkey) + pallet_subtensor::Axons::::get(netuid, new_hotkey), + pallet_subtensor::Axons::::get(netuid, old_hotkey) ); } // TotalHotkeyColdkeyStakesThisInterval assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), - TotalHotkeyColdkeyStakesThisInterval::::get(old_hotkey, coldkey) + pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), + pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get(old_hotkey, coldkey) ); }); } @@ -226,8 +226,8 @@ fn test_do_swap_hotkey_ok_robust() { // Verify raw storage maps // Stake - for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkeys[i]) { - assert_eq!(Stake::::get(new_hotkeys[i], coldkey), stake_amount); + for (coldkey, stake_amount) in pallet_subtensor::Stake::::iter_prefix(old_hotkeys[i]) { + assert_eq!(pallet_subtensor::Stake::::get(new_hotkeys[i], coldkey), stake_amount); } let mut weight = Weight::zero(); @@ -236,8 +236,8 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( - Uids::::get(netuid, new_hotkeys[i]), - Uids::::get(netuid, old_hotkeys[i]) + pallet_subtensor::Uids::::get(netuid, new_hotkeys[i]), + pallet_subtensor::Uids::::get(netuid, old_hotkeys[i]) ); } @@ -246,8 +246,8 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( - Prometheus::::get(netuid, new_hotkeys[i]), - Prometheus::::get(netuid, old_hotkeys[i]) + pallet_subtensor::Prometheus::::get(netuid, new_hotkeys[i]), + pallet_subtensor::Prometheus::::get(netuid, old_hotkeys[i]) ); } @@ -256,8 +256,8 @@ fn test_do_swap_hotkey_ok_robust() { SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { assert_eq!( - LoadedEmission::::get(netuid).unwrap(), - LoadedEmission::::get(netuid).unwrap() + pallet_subtensor::LoadedEmission::::get(netuid).unwrap(), + pallet_subtensor::LoadedEmission::::get(netuid).unwrap() ); } @@ -265,23 +265,23 @@ fn test_do_swap_hotkey_ok_robust() { for netuid in SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) { - assert!(IsNetworkMember::::contains_key( + assert!(pallet_subtensor::IsNetworkMember::::contains_key( new_hotkeys[i], netuid )); - assert!(!IsNetworkMember::::contains_key( + assert!(!pallet_subtensor::IsNetworkMember::::contains_key( old_hotkeys[i], netuid )); } // Owner - assert_eq!(Owner::::get(new_hotkeys[i]), coldkeys[i]); + assert_eq!(pallet_subtensor::Owner::::get(new_hotkeys[i]), coldkeys[i]); // Keys - for (uid, hotkey) in Keys::::iter_prefix(netuid) { + for (uid, hotkey) in pallet_subtensor::Keys::::iter_prefix(netuid) { if hotkey == old_hotkeys[i] { - assert_eq!(Keys::::get(netuid, uid), new_hotkeys[i]); + assert_eq!(pallet_subtensor::Keys::::get(netuid, uid), new_hotkeys[i]); } } @@ -730,7 +730,7 @@ fn test_swap_axons_success() { // Initialize Axons for old_hotkey for netuid in &netuid_is_member { - Axons::::insert(netuid, old_hotkey, axon_info.clone()); + pallet_subtensor::Axons::::insert(netuid, old_hotkey, axon_info.clone()); } // Perform the swap @@ -738,8 +738,8 @@ fn test_swap_axons_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(Axons::::get(netuid, new_hotkey).unwrap(), axon_info); - assert!(!Axons::::contains_key(netuid, old_hotkey)); + assert_eq!(pallet_subtensor::Axons::::get(netuid, new_hotkey).unwrap(), axon_info); + assert!(!pallet_subtensor::Axons::::contains_key(netuid, old_hotkey)); } }); } @@ -764,7 +764,7 @@ fn test_swap_axons_weight_update() { // Initialize Axons for old_hotkey for netuid in &netuid_is_member { - Axons::::insert(netuid, old_hotkey, axon_info.clone()); + pallet_subtensor::Axons::::insert(netuid, old_hotkey, axon_info.clone()); } // Perform the swap @@ -789,7 +789,7 @@ fn test_swap_keys_success() { // Initialize Keys for old_hotkey for netuid in &netuid_is_member { log::info!("Inserting old_hotkey:{:?} netuid:{:?}", old_hotkey, netuid); - Keys::::insert(*netuid, uid, old_hotkey); + pallet_subtensor::Keys::::insert(*netuid, uid, old_hotkey); } // Perform the swap @@ -803,7 +803,7 @@ fn test_swap_keys_success() { uid, new_hotkey ); - assert_eq!(Keys::::get(netuid, uid), new_hotkey); + assert_eq!(pallet_subtensor::Keys::::get(netuid, uid), new_hotkey); } }); } @@ -819,7 +819,7 @@ fn test_swap_keys_weight_update() { // Initialize Keys for old_hotkey for netuid in &netuid_is_member { - Keys::::insert(*netuid, uid, old_hotkey); + pallet_subtensor::Keys::::insert(*netuid, uid, old_hotkey); } // Perform the swap @@ -843,7 +843,7 @@ fn test_swap_loaded_emission_success() { // Initialize LoadedEmission for old_hotkey for netuid in &netuid_is_member { - LoadedEmission::::mutate(netuid, |emission_exists| { + pallet_subtensor::LoadedEmission::::mutate(netuid, |emission_exists| { if let Some(emissions) = emission_exists { emissions.push((old_hotkey, se, ve)); } else { @@ -862,7 +862,7 @@ fn test_swap_loaded_emission_success() { // Verify the swap for netuid in &netuid_is_member { - let emissions = LoadedEmission::::get(netuid).unwrap(); + let emissions = pallet_subtensor::LoadedEmission::::get(netuid).unwrap(); assert!(emissions.iter().any(|(hk, _, _)| hk == &new_hotkey)); assert!(!emissions.iter().any(|(hk, _, _)| hk == &old_hotkey)); } @@ -882,7 +882,7 @@ fn test_swap_loaded_emission_weight_update() { // Initialize LoadedEmission for old_hotkey for netuid in &netuid_is_member { - LoadedEmission::::mutate(netuid, |emission_exists| { + pallet_subtensor::LoadedEmission::::mutate(netuid, |emission_exists| { if let Some(emissions) = emission_exists { emissions.push((old_hotkey, se, ve)); } else { @@ -916,7 +916,7 @@ fn test_swap_uids_success() { // Initialize Uids for old_hotkey for netuid in &netuid_is_member { - Uids::::insert(netuid, old_hotkey, uid); + pallet_subtensor::Uids::::insert(netuid, old_hotkey, uid); } // Perform the swap @@ -924,8 +924,8 @@ fn test_swap_uids_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(Uids::::get(netuid, new_hotkey).unwrap(), uid); - assert!(!Uids::::contains_key(netuid, old_hotkey)); + assert_eq!(pallet_subtensor::Uids::::get(netuid, new_hotkey).unwrap(), uid); + assert!(!pallet_subtensor::Uids::::contains_key(netuid, old_hotkey)); } }); } @@ -941,7 +941,7 @@ fn test_swap_uids_weight_update() { // Initialize Uids for old_hotkey for netuid in &netuid_is_member { - Uids::::insert(netuid, old_hotkey, uid); + pallet_subtensor::Uids::::insert(netuid, old_hotkey, uid); } // Perform the swap @@ -970,7 +970,7 @@ fn test_swap_prometheus_success() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); + pallet_subtensor::Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); } // Perform the swap @@ -979,10 +979,10 @@ fn test_swap_prometheus_success() { // Verify the swap for netuid in &netuid_is_member { assert_eq!( - Prometheus::::get(netuid, new_hotkey).unwrap(), + pallet_subtensor::Prometheus::::get(netuid, new_hotkey).unwrap(), prometheus_info ); - assert!(!Prometheus::::contains_key(netuid, old_hotkey)); + assert!(!pallet_subtensor::Prometheus::::contains_key(netuid, old_hotkey)); } }); } @@ -1004,7 +1004,7 @@ fn test_swap_prometheus_weight_update() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); + pallet_subtensor::Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); } // Perform the swap diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..0ed3cd10f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -880,7 +880,6 @@ parameter_types! { 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 SubtensorInitialBaseDifficulty: u64 = 10_000_000; // Base difficulty } impl pallet_subtensor::Config for Runtime { @@ -936,7 +935,6 @@ impl pallet_subtensor::Config for Runtime { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; - type InitialBaseDifficulty = SubtensorInitialBaseDifficulty; } use sp_runtime::BoundedVec; From 0f8d50b19b27a0664eef46526b6e3d5d7905b317 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 14:10:33 -0500 Subject: [PATCH 010/269] pre fix --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/migration.rs | 661 -------------- pallets/subtensor/src/staking.rs | 851 ------------------ pallets/subtensor/src/staking/add_stake.rs | 125 +++ .../subtensor/src/staking/become_delegate.rs | 96 ++ .../subtensor/src/staking/decrease_take.rs | 82 ++ pallets/subtensor/src/staking/helpers.rs | 397 ++++++++ .../subtensor/src/staking/increase_take.rs | 99 ++ pallets/subtensor/src/staking/mod.rs | 7 + pallets/subtensor/src/staking/remove_stake.rs | 120 +++ pallets/subtensor/src/swap/swap_coldkey.rs | 2 +- 11 files changed, 928 insertions(+), 1514 deletions(-) delete mode 100644 pallets/subtensor/src/migration.rs delete mode 100644 pallets/subtensor/src/staking.rs create mode 100644 pallets/subtensor/src/staking/add_stake.rs create mode 100644 pallets/subtensor/src/staking/become_delegate.rs create mode 100644 pallets/subtensor/src/staking/decrease_take.rs create mode 100644 pallets/subtensor/src/staking/helpers.rs create mode 100644 pallets/subtensor/src/staking/increase_take.rs create mode 100644 pallets/subtensor/src/staking/mod.rs create mode 100644 pallets/subtensor/src/staking/remove_stake.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 329cebcdc..7596029e0 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -39,13 +39,13 @@ mod rpc_info; mod coinbase; pub mod epoch; pub mod swap; +pub mod staking; mod macros; use macros::{events, errors, dispatches, genesis, hooks, config}; mod registration; mod root; mod serving; -mod staking; mod uids; mod utils; mod weights; diff --git a/pallets/subtensor/src/migration.rs b/pallets/subtensor/src/migration.rs deleted file mode 100644 index cd99f0128..000000000 --- a/pallets/subtensor/src/migration.rs +++ /dev/null @@ -1,661 +0,0 @@ -use super::*; -use frame_support::traits::DefensiveResult; -use frame_support::{ - pallet_prelude::{Identity, OptionQuery}, - storage_alias, - traits::{fungible::Inspect as _, Get, GetStorageVersion, StorageVersion}, - weights::Weight, -}; -use log::info; -use sp_runtime::Saturating; -use sp_std::vec::Vec; - -// TODO (camfairchild): TEST MIGRATION - -const LOG_TARGET: &str = "loadedemissionmigration"; - -pub mod deprecated_loaded_emission_format { - use super::*; - - type AccountIdOf = ::AccountId; - - #[storage_alias] - pub(super) type LoadedEmission = - StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; -} - -/// Migrates and fixes the total coldkey stake. -/// -/// This function iterates through all staking hotkeys, calculates the total stake for each coldkey, -/// and updates the `TotalColdkeyStake` storage accordingly. The migration is only performed if the -/// on-chain storage version is 6. -/// -/// # Returns -/// The weight of the migration process. -pub fn do_migrate_fix_total_coldkey_stake() -> Weight { - // Initialize the weight with one read operation. - let mut weight = T::DbWeight::get().reads(1); - - // Iterate through all staking hotkeys. - for (coldkey, hotkey_vec) in StakingHotkeys::::iter() { - // Init the zero value. - let mut coldkey_stake_sum: u64 = 0; - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - - // Calculate the total stake for the current coldkey. - for hotkey in hotkey_vec { - // Cant fail on retrieval. - coldkey_stake_sum = - coldkey_stake_sum.saturating_add(Stake::::get(hotkey, coldkey.clone())); - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - } - // Update the `TotalColdkeyStake` storage with the calculated stake sum. - // Cant fail on insert. - TotalColdkeyStake::::insert(coldkey.clone(), coldkey_stake_sum); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - } - weight -} -// Public migrate function to be called by Lib.rs on upgrade. -pub fn migrate_fix_total_coldkey_stake() -> Weight { - let current_storage_version: u16 = 7; - let next_storage_version: u16 = 8; - - // Initialize the weight with one read operation. - let mut weight = T::DbWeight::get().reads(1); - - // Grab the current on-chain storage version. - // Cant fail on retrieval. - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only run this migration on storage version 6. - if onchain_version == current_storage_version { - weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); - // Cant fail on insert. - StorageVersion::new(next_storage_version).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - // Return the migration weight. - weight -} - -/// Performs migration to update the total issuance based on the sum of stakes and total balances. -/// This migration is applicable only if the current storage version is 5, after which it updates the storage version to 6. -/// -/// # Returns -/// Weight of the migration process. -pub fn migrate_total_issuance(test: bool) -> Weight { - let mut weight = T::DbWeight::get().reads(1); // Initialize migration weight - - // Execute migration if the current storage version is 5 - if Pallet::::on_chain_storage_version() == StorageVersion::new(5) || test { - // Calculate the sum of all stake values - let stake_sum: u64 = Stake::::iter().fold(0, |accumulator, (_, _, stake_value)| { - accumulator.saturating_add(stake_value) - }); - weight = weight - .saturating_add(T::DbWeight::get().reads_writes(Stake::::iter().count() as u64, 0)); - - // Calculate the sum of all stake values - let locked_sum: u64 = SubnetLocked::::iter() - .fold(0, |accumulator, (_, locked_value)| { - accumulator.saturating_add(locked_value) - }); - weight = weight.saturating_add( - T::DbWeight::get().reads_writes(SubnetLocked::::iter().count() as u64, 0), - ); - - // Retrieve the total balance sum - let total_balance = T::Currency::total_issuance(); - match TryInto::::try_into(total_balance) { - Ok(total_balance_sum) => { - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - - // Compute the total issuance value - let total_issuance_value: u64 = stake_sum - .saturating_add(total_balance_sum) - .saturating_add(locked_sum); - - // Update the total issuance in storage - TotalIssuance::::put(total_issuance_value); - - // Update the storage version to 6 - StorageVersion::new(6).put::>(); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - } - Err(_) => { - log::error!("Failed to convert total balance to u64, bailing"); - } - } - } - - weight // Return the computed weight of the migration process -} - -pub fn migrate_transfer_ownership_to_foundation(coldkey: [u8; 32]) -> Weight { - let new_storage_version = 3; - - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version past above new_storage_version. - if onchain_version < new_storage_version { - info!(target: LOG_TARGET_1, ">>> Migrating subnet 1 and 11 to foundation control {:?}", onchain_version); - - // We have to decode this using a byte slice as we don't have crypto-std - let coldkey_account: ::AccountId = - ::AccountId::decode(&mut &coldkey[..]) - .expect("coldkey is 32-byte array; qed"); - info!("Foundation coldkey: {:?}", coldkey_account); - - let current_block = Pallet::::get_current_block_as_u64(); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - - // Migrate ownership and set creation time as now - SubnetOwner::::insert(1, coldkey_account.clone()); - SubnetOwner::::insert(11, coldkey_account); - - // We are setting the NetworkRegisteredAt storage to a future block to extend the immunity period to 2 weeks - NetworkRegisteredAt::::insert(1, current_block.saturating_add(13 * 7200)); - NetworkRegisteredAt::::insert(11, current_block); - - weight.saturating_accrue(T::DbWeight::get().writes(4)); - - // Update storage version. - StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_1, "Migration to v3 already done!"); - Weight::zero() - } -} - -pub fn migrate_create_root_network() -> Weight { - // Get the root network uid. - let root_netuid: u16 = 0; - - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - - // Check if root network already exists. - if NetworksAdded::::get(root_netuid) { - // Since we read from the database once to determine this - return weight; - } - - // Set the root network as added. - NetworksAdded::::insert(root_netuid, true); - - // Increment the number of total networks. - TotalNetworks::::mutate(|n| n.saturating_inc()); - - // Set the maximum number to the number of senate members. - MaxAllowedUids::::insert(root_netuid, 64); - - // Set the maximum number to the number of validators to all members. - MaxAllowedValidators::::insert(root_netuid, 64); - - // Set the min allowed weights to zero, no weights restrictions. - MinAllowedWeights::::insert(root_netuid, 0); - - // Set the max weight limit to infitiy, no weight restrictions. - MaxWeightsLimit::::insert(root_netuid, u16::MAX); - - // Add default root tempo. - Tempo::::insert(root_netuid, 100); - - // Set the root network as open. - NetworkRegistrationAllowed::::insert(root_netuid, true); - - // Set target registrations for validators as 1 per block. - TargetRegistrationsPerInterval::::insert(root_netuid, 1); - - // Set weight setting rate limit to 1 day - //WeightsSetRateLimit::::insert(root_netuid, 7200); - - // Add our weights for writing to database - weight.saturating_accrue(T::DbWeight::get().writes(8)); - - // Empty senate members entirely, they will be filled by by registrations - // on the subnet. - for hotkey_i in T::SenateMembers::members().iter() { - T::TriumvirateInterface::remove_votes(hotkey_i).defensive_ok(); - T::SenateMembers::remove_member(hotkey_i).defensive_ok(); - - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - - weight -} - -pub fn migrate_delete_subnet_3() -> Weight { - let new_storage_version = 5; - - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version past above new_storage_version. - if onchain_version < new_storage_version && Pallet::::if_subnet_exist(3) { - info!(target: LOG_TARGET_1, ">>> Removing subnet 3 {:?}", onchain_version); - - let netuid = 3; - - // We do this all manually as we don't want to call code related to giving subnet owner back their locked token cost. - // --- 2. Remove network count. - SubnetworkN::::remove(netuid); - - // --- 3. Remove network modality storage. - NetworkModality::::remove(netuid); - - // --- 4. Remove netuid from added networks. - NetworksAdded::::remove(netuid); - - // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| n.saturating_dec()); - - // --- 7. Remove various network-related storages. - NetworkRegisteredAt::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(5)); - - // --- 8. Remove incentive mechanism memory. - let _ = Uids::::clear_prefix(netuid, u32::MAX, None); - let _ = Keys::::clear_prefix(netuid, u32::MAX, None); - let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); - let _ = Weights::::clear_prefix(netuid, u32::MAX, None); - - weight.saturating_accrue(T::DbWeight::get().writes(4)); - - // --- 9. Remove various network-related parameters. - Rank::::remove(netuid); - Trust::::remove(netuid); - Active::::remove(netuid); - Emission::::remove(netuid); - Incentive::::remove(netuid); - Consensus::::remove(netuid); - Dividends::::remove(netuid); - PruningScores::::remove(netuid); - LastUpdate::::remove(netuid); - ValidatorPermit::::remove(netuid); - ValidatorTrust::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(11)); - - // --- 10. Erase network parameters. - Tempo::::remove(netuid); - Kappa::::remove(netuid); - Difficulty::::remove(netuid); - MaxAllowedUids::::remove(netuid); - ImmunityPeriod::::remove(netuid); - ActivityCutoff::::remove(netuid); - EmissionValues::::remove(netuid); - MaxWeightsLimit::::remove(netuid); - MinAllowedWeights::::remove(netuid); - RegistrationsThisInterval::::remove(netuid); - POWRegistrationsThisInterval::::remove(netuid); - BurnRegistrationsThisInterval::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(12)); - - // Update storage version. - StorageVersion::new(new_storage_version).put::>(); // Update version so we don't run this again. - // One write to storage version - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_1, "Migration to v3 already done!"); - Weight::zero() - } -} - -pub fn migrate_delete_subnet_21() -> Weight { - let new_storage_version = 4; - - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version past above new_storage_version. - if onchain_version < new_storage_version && Pallet::::if_subnet_exist(21) { - info!(target: LOG_TARGET_1, ">>> Removing subnet 21 {:?}", onchain_version); - - let netuid = 21; - - // We do this all manually as we don't want to call code related to giving subnet owner back their locked token cost. - // --- 2. Remove network count. - SubnetworkN::::remove(netuid); - - // --- 3. Remove network modality storage. - NetworkModality::::remove(netuid); - - // --- 4. Remove netuid from added networks. - NetworksAdded::::remove(netuid); - - // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| n.saturating_dec()); - - // --- 7. Remove various network-related storages. - NetworkRegisteredAt::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(5)); - - // --- 8. Remove incentive mechanism memory. - let _ = Uids::::clear_prefix(netuid, u32::MAX, None); - let _ = Keys::::clear_prefix(netuid, u32::MAX, None); - let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); - let _ = Weights::::clear_prefix(netuid, u32::MAX, None); - - weight.saturating_accrue(T::DbWeight::get().writes(4)); - - // --- 9. Remove various network-related parameters. - Rank::::remove(netuid); - Trust::::remove(netuid); - Active::::remove(netuid); - Emission::::remove(netuid); - Incentive::::remove(netuid); - Consensus::::remove(netuid); - Dividends::::remove(netuid); - PruningScores::::remove(netuid); - LastUpdate::::remove(netuid); - ValidatorPermit::::remove(netuid); - ValidatorTrust::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(11)); - - // --- 10. Erase network parameters. - Tempo::::remove(netuid); - Kappa::::remove(netuid); - Difficulty::::remove(netuid); - MaxAllowedUids::::remove(netuid); - ImmunityPeriod::::remove(netuid); - ActivityCutoff::::remove(netuid); - EmissionValues::::remove(netuid); - MaxWeightsLimit::::remove(netuid); - MinAllowedWeights::::remove(netuid); - RegistrationsThisInterval::::remove(netuid); - POWRegistrationsThisInterval::::remove(netuid); - BurnRegistrationsThisInterval::::remove(netuid); - - weight.saturating_accrue(T::DbWeight::get().writes(12)); - - // Update storage version. - StorageVersion::new(new_storage_version).put::>(); // Update version so we don't run this again. - // One write to storage version - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_1, "Migration to v4 already done!"); - Weight::zero() - } -} - -pub fn migrate_to_v1_separate_emission() -> Weight { - use deprecated_loaded_emission_format as old; - // Check storage version - let mut weight = T::DbWeight::get().reads_writes(1, 0); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version to 1. - if onchain_version < 1 { - info!(target: LOG_TARGET, ">>> Updating the LoadedEmission to a new format {:?}", onchain_version); - - // We transform the storage values from the old into the new format. - - // Start by removing any undecodable entries. - let curr_loaded_emission: Vec = old::LoadedEmission::::iter_keys().collect(); - for netuid in curr_loaded_emission { - // Iterates over the netuids - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if old::LoadedEmission::::try_get(netuid).is_err() { - weight.saturating_accrue(T::DbWeight::get().writes(1)); - old::LoadedEmission::::remove(netuid); - log::warn!( - "Was unable to decode old loaded_emisssion for netuid {}", - netuid - ); - } - } - - // Translate the old storage values into the new format. - LoadedEmission::::translate::, u64)>, _>( - |netuid: u16, - netuid_emissions: Vec<(AccountIdOf, u64)>| - -> Option, u64, u64)>> { - info!(target: LOG_TARGET, " Do migration of netuid: {:?}...", netuid); - - // We will assume all loaded emission is validator emissions, - // so this will get distributed over delegatees (nominators), if there are any - // This will NOT effect any servers that are not (also) a delegate validator. - // server_emission will be 0 for any alread loaded emission. - - let mut new_netuid_emissions = Vec::new(); - for (server, validator_emission) in netuid_emissions { - new_netuid_emissions.push((server, 0_u64, validator_emission)); - } - - // One read (old) and write (new) per netuid - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - Some(new_netuid_emissions) - }, - ); - - // Update storage version. - StorageVersion::new(1).put::>(); // Update to version 2 so we don't run this again. - // One write to storage version - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_1, "Migration to v2 already done!"); - Weight::zero() - } -} - -const LOG_TARGET_1: &str = "fixtotalstakestorage"; - -pub fn migrate_to_v2_fixed_total_stake() -> Weight { - let new_storage_version = 2; - - // Check storage version - let mut weight = T::DbWeight::get().reads(1); - - // Grab current version - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only runs if we haven't already updated version past above new_storage_version. - if onchain_version < new_storage_version { - info!(target: LOG_TARGET_1, ">>> Fixing the TotalStake and TotalColdkeyStake storage {:?}", onchain_version); - - // Stake and TotalHotkeyStake are known to be accurate - // TotalColdkeyStake is known to be inaccurate - // TotalStake is known to be inaccurate - - TotalStake::::put(0); // Set to 0 - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - // We iterate over TotalColdkeyStake keys and set them to 0 - let total_coldkey_stake_keys = TotalColdkeyStake::::iter_keys().collect::>(); - for coldkey in total_coldkey_stake_keys { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - TotalColdkeyStake::::insert(coldkey, 0); // Set to 0 - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - // Now we iterate over the entire stake map, and sum each coldkey stake - // We also track TotalStake - for (_, coldkey, stake) in Stake::::iter() { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - // Get the current coldkey stake - let mut total_coldkey_stake = TotalColdkeyStake::::get(coldkey.clone()); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - // Add the stake to the coldkey stake - total_coldkey_stake = total_coldkey_stake.saturating_add(stake); - // Update the coldkey stake - TotalColdkeyStake::::insert(coldkey, total_coldkey_stake); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - // Get the current total stake - let mut total_stake = TotalStake::::get(); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - // Add the stake to the total stake - total_stake = total_stake.saturating_add(stake); - // Update the total stake - TotalStake::::put(total_stake); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - - // Now both TotalStake and TotalColdkeyStake are accurate - - // Update storage version. - StorageVersion::new(new_storage_version).put::>(); // Update to version so we don't run this again. - // One write to storage version - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight - } else { - info!(target: LOG_TARGET_1, "Migration to v2 already done!"); - Weight::zero() - } -} - -/// Migrate the OwnedHotkeys map to the new storage format -pub fn migrate_populate_owned() -> Weight { - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - let migration_name = "Populate OwnedHotkeys map"; - - // Check if this migration is needed (if OwnedHotkeys map is empty) - let migrate = OwnedHotkeys::::iter().next().is_none(); - - // Only runs if the migration is needed - if migrate { - info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); - - let mut longest_hotkey_vector: usize = 0; - let mut longest_coldkey: Option = None; - let mut keys_touched: u64 = 0; - let mut storage_reads: u64 = 0; - let mut storage_writes: u64 = 0; - - // Iterate through all Owner entries - Owner::::iter().for_each(|(hotkey, coldkey)| { - storage_reads = storage_reads.saturating_add(1); // Read from Owner storage - let mut hotkeys = OwnedHotkeys::::get(&coldkey); - storage_reads = storage_reads.saturating_add(1); // Read from OwnedHotkeys storage - - // Add the hotkey if it's not already in the vector - if !hotkeys.contains(&hotkey) { - hotkeys.push(hotkey); - keys_touched = keys_touched.saturating_add(1); - - // Update longest hotkey vector info - if longest_hotkey_vector < hotkeys.len() { - longest_hotkey_vector = hotkeys.len(); - longest_coldkey = Some(coldkey.clone()); - } - - // Update the OwnedHotkeys storage - OwnedHotkeys::::insert(&coldkey, hotkeys); - storage_writes = storage_writes.saturating_add(1); // Write to OwnedHotkeys storage - } - - // Accrue weight for reads and writes - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); - }); - - // Log migration results - info!( - target: LOG_TARGET_1, - "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", - migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes - ); - if let Some(c) = longest_coldkey { - info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); - } - - weight - } else { - info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); - Weight::zero() - } -} - -/// Populate the StakingHotkeys map from Stake map -pub fn migrate_populate_staking_hotkeys() -> Weight { - // Setup migration weight - let mut weight = T::DbWeight::get().reads(1); - let migration_name = "Populate StakingHotkeys map"; - - // Check if this migration is needed (if StakingHotkeys map is empty) - let migrate = StakingHotkeys::::iter().next().is_none(); - - // Only runs if the migration is needed - if migrate { - info!(target: LOG_TARGET_1, ">>> Starting Migration: {}", migration_name); - - let mut longest_hotkey_vector: usize = 0; - let mut longest_coldkey: Option = None; - let mut keys_touched: u64 = 0; - let mut storage_reads: u64 = 0; - let mut storage_writes: u64 = 0; - - // Iterate through all Owner entries - Stake::::iter().for_each(|(hotkey, coldkey, stake)| { - storage_reads = storage_reads.saturating_add(1); // Read from Owner storage - if stake > 0 { - let mut hotkeys = StakingHotkeys::::get(&coldkey); - storage_reads = storage_reads.saturating_add(1); // Read from StakingHotkeys storage - - // Add the hotkey if it's not already in the vector - if !hotkeys.contains(&hotkey) { - hotkeys.push(hotkey); - keys_touched = keys_touched.saturating_add(1); - - // Update longest hotkey vector info - if longest_hotkey_vector < hotkeys.len() { - longest_hotkey_vector = hotkeys.len(); - longest_coldkey = Some(coldkey.clone()); - } - - // Update the StakingHotkeys storage - StakingHotkeys::::insert(&coldkey, hotkeys); - storage_writes = storage_writes.saturating_add(1); // Write to StakingHotkeys storage - } - - // Accrue weight for reads and writes - weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); - } - }); - - // Log migration results - info!( - target: LOG_TARGET_1, - "Migration {} finished. Keys touched: {}, Longest hotkey vector: {}, Storage reads: {}, Storage writes: {}", - migration_name, keys_touched, longest_hotkey_vector, storage_reads, storage_writes - ); - if let Some(c) = longest_coldkey { - info!(target: LOG_TARGET_1, "Longest hotkey vector is controlled by: {:?}", c); - } - - weight - } else { - info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); - Weight::zero() - } -} diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs deleted file mode 100644 index 2707d90ef..000000000 --- a/pallets/subtensor/src/staking.rs +++ /dev/null @@ -1,851 +0,0 @@ -use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; - -impl Pallet { - /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. - /// - /// # Args: - /// * 'origin': (RuntimeOrigin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'take' (u16): - /// - The stake proportion that this hotkey takes from delegations. - /// - /// # Event: - /// * DelegateAdded; - /// - On successfully setting a hotkey as a delegate. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldket. - /// - /// * 'TxRateLimitExceeded': - /// - Thrown if key has hit transaction rate limit - /// - pub fn do_become_delegate( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - take: u16, - ) -> dispatch::DispatchResult { - // --- 1. We check the coldkey signuture. - let coldkey = ensure_signed(origin)?; - log::info!( - "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - - // --- 2. Ensure we are delegating an known key. - // --- 3. Ensure that the coldkey is the owner. - Self::do_take_checks(&coldkey, &hotkey)?; - - // --- 4. Ensure we are not already a delegate (dont allow changing delegate take.) - ensure!( - !Self::hotkey_is_delegate(&hotkey), - Error::::HotKeyAlreadyDelegate - ); - - // --- 5. Ensure we don't exceed tx rate limit - let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), - Error::::DelegateTxRateLimitExceeded - ); - - // --- 5.1 Ensure take is within the min ..= InitialDefaultTake (18%) range - let min_take = MinTake::::get(); - let max_take = MaxTake::::get(); - ensure!(take >= min_take, Error::::DelegateTakeTooLow); - ensure!(take <= max_take, Error::::DelegateTakeTooHigh); - - // --- 6. Delegate the key. - Self::delegate_hotkey(&hotkey, take); - - // Set last block for rate limiting - Self::set_last_tx_block(&coldkey, block); - Self::set_last_tx_block_delegate_take(&coldkey, block); - - // --- 7. Emit the staking event. - log::info!( - "DelegateAdded( coldkey:{:?}, hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - Self::deposit_event(Event::DelegateAdded(coldkey, hotkey, take)); - - // --- 8. Ok and return. - Ok(()) - } - - /// ---- The implementation for the extrinsic decrease_take - /// - /// # Args: - /// * 'origin': (::RuntimeOrigin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'take' (u16): - /// - The stake proportion that this hotkey takes from delegations for subnet ID. - /// - /// # Event: - /// * TakeDecreased; - /// - On successfully setting a decreased take for this hotkey. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldket. - /// - /// * 'DelegateTakeTooLow': - /// - The delegate is setting a take which is not lower than the previous. - /// - pub fn do_decrease_take( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - take: u16, - ) -> dispatch::DispatchResult { - // --- 1. We check the coldkey signature. - let coldkey = ensure_signed(origin)?; - log::info!( - "do_decrease_take( origin:{:?} hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - - // --- 2. Ensure we are delegating a known key. - // Ensure that the coldkey is the owner. - Self::do_take_checks(&coldkey, &hotkey)?; - - // --- 3. Ensure we are always strictly decreasing, never increasing take - if let Ok(current_take) = Delegates::::try_get(&hotkey) { - ensure!(take < current_take, Error::::DelegateTakeTooLow); - } - - // --- 3.1 Ensure take is within the min ..= InitialDefaultTake (18%) range - let min_take = MinTake::::get(); - ensure!(take >= min_take, Error::::DelegateTakeTooLow); - - // --- 4. Set the new take value. - Delegates::::insert(hotkey.clone(), take); - - // --- 5. Emit the take value. - log::info!( - "TakeDecreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - Self::deposit_event(Event::TakeDecreased(coldkey, hotkey, take)); - - // --- 6. Ok and return. - Ok(()) - } - - /// ---- The implementation for the extrinsic increase_take - /// - /// # Args: - /// * 'origin': (::RuntimeOrigin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The hotkey we are delegating (must be owned by the coldkey.) - /// - /// * 'take' (u16): - /// - The stake proportion that this hotkey takes from delegations for subnet ID. - /// - /// # Event: - /// * TakeIncreased; - /// - On successfully setting a increased take for this hotkey. - /// - /// # Raises: - /// * 'NotRegistered': - /// - The hotkey we are delegating is not registered on the network. - /// - /// * 'NonAssociatedColdKey': - /// - The hotkey we are delegating is not owned by the calling coldket. - /// - /// * 'TxRateLimitExceeded': - /// - Thrown if key has hit transaction rate limit - /// - /// * 'DelegateTakeTooLow': - /// - The delegate is setting a take which is not greater than the previous. - /// - pub fn do_increase_take( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - take: u16, - ) -> dispatch::DispatchResult { - // --- 1. We check the coldkey signature. - let coldkey = ensure_signed(origin)?; - log::info!( - "do_increase_take( origin:{:?} hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - - // --- 2. Ensure we are delegating a known key. - // Ensure that the coldkey is the owner. - Self::do_take_checks(&coldkey, &hotkey)?; - - // --- 3. Ensure we are strinctly increasing take - if let Ok(current_take) = Delegates::::try_get(&hotkey) { - ensure!(take > current_take, Error::::DelegateTakeTooLow); - } - - // --- 4. Ensure take is within the min ..= InitialDefaultTake (18%) range - let max_take = MaxTake::::get(); - ensure!(take <= max_take, Error::::DelegateTakeTooHigh); - - // --- 5. Enforce the rate limit (independently on do_add_stake rate limits) - let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_delegate_take_rate_limit( - Self::get_last_tx_block_delegate_take(&coldkey), - block - ), - Error::::DelegateTxRateLimitExceeded - ); - - // Set last block for rate limiting - Self::set_last_tx_block_delegate_take(&coldkey, block); - - // --- 6. Set the new take value. - Delegates::::insert(hotkey.clone(), take); - - // --- 7. Emit the take value. - log::info!( - "TakeIncreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", - coldkey, - hotkey, - take - ); - Self::deposit_event(Event::TakeIncreased(coldkey, hotkey, take)); - - // --- 8. Ok and return. - Ok(()) - } - - /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. - /// - /// # Args: - /// * 'origin': (RuntimeOrigin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The associated hotkey account. - /// - /// * 'stake_to_be_added' (u64): - /// - The amount of stake to be added to the hotkey staking account. - /// - /// # Event: - /// * StakeAdded; - /// - On the successfully adding stake to a global account. - /// - /// # Raises: - /// * 'NotEnoughBalanceToStake': - /// - Not enough balance on the coldkey to add onto the global account. - /// - /// * 'NonAssociatedColdKey': - /// - The calling coldkey is not associated with this hotkey. - /// - /// * 'BalanceWithdrawalError': - /// - Errors stemming from transaction pallet. - /// - /// * 'TxRateLimitExceeded': - /// - Thrown if key has hit transaction rate limit - /// - pub fn do_add_stake( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - stake_to_be_added: u64, - ) -> dispatch::DispatchResult { - // We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. - let coldkey = ensure_signed(origin)?; - log::info!( - "do_add_stake( origin:{:?} hotkey:{:?}, stake_to_be_added:{:?} )", - coldkey, - hotkey, - stake_to_be_added - ); - - // Ensure the callers coldkey has enough stake to perform the transaction. - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, stake_to_be_added), - Error::::NotEnoughBalanceToStake - ); - - // Ensure that the hotkey account exists this is only possible through registration. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // Ensure that the hotkey allows delegation or that the hotkey is owned by the calling coldkey. - ensure!( - Self::hotkey_is_delegate(&hotkey) || Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::HotKeyNotDelegateAndSignerNotOwnHotKey - ); - - // Ensure we don't exceed stake rate limit - let stakes_this_interval = - Self::get_stakes_this_interval_for_coldkey_hotkey(&coldkey, &hotkey); - ensure!( - stakes_this_interval < Self::get_target_stakes_per_interval(), - Error::::StakeRateLimitExceeded - ); - - // If this is a nomination stake, check if total stake after adding will be above - // the minimum required stake. - - // If coldkey is not owner of the hotkey, it's a nomination stake. - if !Self::coldkey_owns_hotkey(&coldkey, &hotkey) { - let total_stake_after_add = - Stake::::get(&hotkey, &coldkey).saturating_add(stake_to_be_added); - - ensure!( - total_stake_after_add >= NominatorMinRequiredStake::::get(), - Error::::NomStakeBelowMinimumThreshold - ); - } - - // Ensure the remove operation from the coldkey is a success. - let actual_amount_to_stake = - Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?; - - // If we reach here, add the balance to the hotkey. - Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, actual_amount_to_stake); - - // Set last block for rate limiting - let block: u64 = Self::get_current_block_as_u64(); - Self::set_last_tx_block(&coldkey, block); - - // Emit the staking event. - Self::set_stakes_this_interval_for_coldkey_hotkey( - &coldkey, - &hotkey, - stakes_this_interval.saturating_add(1), - block, - ); - log::info!( - "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", - hotkey, - actual_amount_to_stake - ); - Self::deposit_event(Event::StakeAdded(hotkey, actual_amount_to_stake)); - - // Ok and return. - Ok(()) - } - - /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. - /// - /// # Args: - /// * 'origin': (RuntimeOrigin): - /// - The signature of the caller's coldkey. - /// - /// * 'hotkey' (T::AccountId): - /// - The associated hotkey account. - /// - /// * 'stake_to_be_added' (u64): - /// - The amount of stake to be added to the hotkey staking account. - /// - /// # Event: - /// * StakeRemoved; - /// - On the successfully removing stake from the hotkey account. - /// - /// # Raises: - /// * 'NotRegistered': - /// - Thrown if the account we are attempting to unstake from is non existent. - /// - /// * 'NonAssociatedColdKey': - /// - Thrown if the coldkey does not own the hotkey we are unstaking from. - /// - /// * 'NotEnoughStakeToWithdraw': - /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. - /// - /// * 'TxRateLimitExceeded': - /// - Thrown if key has hit transaction rate limit - /// - pub fn do_remove_stake( - origin: T::RuntimeOrigin, - hotkey: T::AccountId, - stake_to_be_removed: u64, - ) -> dispatch::DispatchResult { - // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. - let coldkey = ensure_signed(origin)?; - log::info!( - "do_remove_stake( origin:{:?} hotkey:{:?}, stake_to_be_removed:{:?} )", - coldkey, - hotkey, - stake_to_be_removed - ); - - // Ensure that the hotkey account exists this is only possible through registration. - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // Ensure that the hotkey allows delegation or that the hotkey is owned by the calling coldkey. - ensure!( - Self::hotkey_is_delegate(&hotkey) || Self::coldkey_owns_hotkey(&coldkey, &hotkey), - Error::::HotKeyNotDelegateAndSignerNotOwnHotKey - ); - - // Ensure that the stake amount to be removed is above zero. - ensure!(stake_to_be_removed > 0, Error::::StakeToWithdrawIsZero); - - // Ensure that the hotkey has enough stake to withdraw. - ensure!( - Self::has_enough_stake(&coldkey, &hotkey, stake_to_be_removed), - Error::::NotEnoughStakeToWithdraw - ); - - // Ensure we don't exceed stake rate limit - let unstakes_this_interval = - Self::get_stakes_this_interval_for_coldkey_hotkey(&coldkey, &hotkey); - ensure!( - unstakes_this_interval < Self::get_target_stakes_per_interval(), - Error::::UnstakeRateLimitExceeded - ); - - // We remove the balance from the hotkey. - Self::decrease_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_removed); - - // We add the balance to the coldkey. If the above fails we will not credit this coldkey. - Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_removed); - - // If the stake is below the minimum, we clear the nomination from storage. - // This only applies to nominator stakes. - // If the coldkey does not own the hotkey, it's a nominator stake. - let new_stake = Self::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); - Self::clear_small_nomination_if_required(&hotkey, &coldkey, new_stake); - - // Set last block for rate limiting - let block: u64 = Self::get_current_block_as_u64(); - Self::set_last_tx_block(&coldkey, block); - - // Emit the unstaking event. - Self::set_stakes_this_interval_for_coldkey_hotkey( - &coldkey, - &hotkey, - unstakes_this_interval.saturating_add(1), - block, - ); - log::info!( - "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", - hotkey, - stake_to_be_removed - ); - Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed)); - - // Done and ok. - Ok(()) - } - - // Returns true if the passed hotkey allow delegative staking. - // - pub fn hotkey_is_delegate(hotkey: &T::AccountId) -> bool { - Delegates::::contains_key(hotkey) - } - - // Sets the hotkey as a delegate with take. - // - pub fn delegate_hotkey(hotkey: &T::AccountId, take: u16) { - Delegates::::insert(hotkey, take); - } - - // Returns the total amount of stake in the staking table. - // - pub fn get_total_stake() -> u64 { - TotalStake::::get() - } - - // Increases the total amount of stake by the passed amount. - // - pub fn increase_total_stake(increment: u64) { - TotalStake::::put(Self::get_total_stake().saturating_add(increment)); - } - - // Decreases the total amount of stake by the passed amount. - // - pub fn decrease_total_stake(decrement: u64) { - TotalStake::::put(Self::get_total_stake().saturating_sub(decrement)); - } - - // Returns the total amount of stake under a hotkey (delegative or otherwise) - // - pub fn get_total_stake_for_hotkey(hotkey: &T::AccountId) -> u64 { - TotalHotkeyStake::::get(hotkey) - } - - // Returns the total amount of stake held by the coldkey (delegative or otherwise) - // - pub fn get_total_stake_for_coldkey(coldkey: &T::AccountId) -> u64 { - TotalColdkeyStake::::get(coldkey) - } - - // Returns the stake under the cold - hot pairing in the staking table. - // - pub fn get_stake_for_coldkey_and_hotkey(coldkey: &T::AccountId, hotkey: &T::AccountId) -> u64 { - Stake::::get(hotkey, coldkey) - } - - // Retrieves the total stakes for a given hotkey (account ID) for the current staking interval. - pub fn get_stakes_this_interval_for_coldkey_hotkey( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - ) -> u64 { - // Retrieve the configured stake interval duration from storage. - let stake_interval = StakeInterval::::get(); - - // Obtain the current block number as an unsigned 64-bit integer. - let current_block = Self::get_current_block_as_u64(); - - // Fetch the total stakes and the last block number when stakes were made for the hotkey. - let (stakes, block_last_staked_at) = - TotalHotkeyColdkeyStakesThisInterval::::get(coldkey, hotkey); - - // Calculate the block number after which the stakes for the hotkey should be reset. - let block_to_reset_after = block_last_staked_at.saturating_add(stake_interval); - - // If the current block number is beyond the reset point, - // it indicates the end of the staking interval for the hotkey. - if block_to_reset_after <= current_block { - // Reset the stakes for this hotkey for the current interval. - Self::set_stakes_this_interval_for_coldkey_hotkey( - coldkey, - hotkey, - 0, - block_last_staked_at, - ); - // Return 0 as the stake amount since we've just reset the stakes. - return 0; - } - - // If the staking interval has not yet ended, return the current stake amount. - stakes - } - - pub fn get_target_stakes_per_interval() -> u64 { - TargetStakesPerInterval::::get() - } - - // Creates a cold - hot pairing account if the hotkey is not already an active account. - // - pub fn create_account_if_non_existent(coldkey: &T::AccountId, hotkey: &T::AccountId) { - if !Self::hotkey_account_exists(hotkey) { - Stake::::insert(hotkey, coldkey, 0); - Owner::::insert(hotkey, coldkey); - - // Update OwnedHotkeys map - let mut hotkeys = OwnedHotkeys::::get(coldkey); - if !hotkeys.contains(hotkey) { - hotkeys.push(hotkey.clone()); - OwnedHotkeys::::insert(coldkey, hotkeys); - } - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(coldkey); - if !staking_hotkeys.contains(hotkey) { - staking_hotkeys.push(hotkey.clone()); - StakingHotkeys::::insert(coldkey, staking_hotkeys); - } - } - } - - // Returns the coldkey owning this hotkey. This function should only be called for active accounts. - // - pub fn get_owning_coldkey_for_hotkey(hotkey: &T::AccountId) -> T::AccountId { - Owner::::get(hotkey) - } - - // Returns the hotkey take - // - pub fn get_hotkey_take(hotkey: &T::AccountId) -> u16 { - Delegates::::get(hotkey) - } - - // Returns true if the hotkey account has been created. - // - pub fn hotkey_account_exists(hotkey: &T::AccountId) -> bool { - Owner::::contains_key(hotkey) - } - - // Return true if the passed coldkey owns the hotkey. - // - pub fn coldkey_owns_hotkey(coldkey: &T::AccountId, hotkey: &T::AccountId) -> bool { - if Self::hotkey_account_exists(hotkey) { - Owner::::get(hotkey) == *coldkey - } else { - false - } - } - - // Returns true if the cold-hot staking account has enough balance to fufil the decrement. - // - pub fn has_enough_stake(coldkey: &T::AccountId, hotkey: &T::AccountId, decrement: u64) -> bool { - Self::get_stake_for_coldkey_and_hotkey(coldkey, hotkey) >= decrement - } - - // Increases the stake on the hotkey account under its owning coldkey. - // - pub fn increase_stake_on_hotkey_account(hotkey: &T::AccountId, increment: u64) { - Self::increase_stake_on_coldkey_hotkey_account( - &Self::get_owning_coldkey_for_hotkey(hotkey), - hotkey, - increment, - ); - } - - // Decreases the stake on the hotkey account under its owning coldkey. - // - pub fn decrease_stake_on_hotkey_account(hotkey: &T::AccountId, decrement: u64) { - Self::decrease_stake_on_coldkey_hotkey_account( - &Self::get_owning_coldkey_for_hotkey(hotkey), - hotkey, - decrement, - ); - } - - // Increases the stake on the cold - hot pairing by increment while also incrementing other counters. - // This function should be called rather than set_stake under account. - // - pub fn increase_stake_on_coldkey_hotkey_account( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - increment: u64, - ) { - TotalColdkeyStake::::insert( - coldkey, - TotalColdkeyStake::::get(coldkey).saturating_add(increment), - ); - TotalHotkeyStake::::insert( - hotkey, - TotalHotkeyStake::::get(hotkey).saturating_add(increment), - ); - Stake::::insert( - hotkey, - coldkey, - Stake::::get(hotkey, coldkey).saturating_add(increment), - ); - TotalStake::::put(TotalStake::::get().saturating_add(increment)); - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(coldkey); - if !staking_hotkeys.contains(hotkey) { - staking_hotkeys.push(hotkey.clone()); - StakingHotkeys::::insert(coldkey, staking_hotkeys); - } - } - - // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. - // - pub fn decrease_stake_on_coldkey_hotkey_account( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - decrement: u64, - ) { - TotalColdkeyStake::::mutate(coldkey, |old| *old = old.saturating_sub(decrement)); - TotalHotkeyStake::::insert( - hotkey, - TotalHotkeyStake::::get(hotkey).saturating_sub(decrement), - ); - Stake::::insert( - hotkey, - coldkey, - Stake::::get(hotkey, coldkey).saturating_sub(decrement), - ); - TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); - - // TODO: Tech debt: Remove StakingHotkeys entry if stake goes to 0 - } - - /// Empties the stake associated with a given coldkey-hotkey account pairing. - /// This function retrieves the current stake for the specified coldkey-hotkey pairing, - /// then subtracts this stake amount from both the TotalColdkeyStake and TotalHotkeyStake. - /// It also removes the stake entry for the hotkey-coldkey pairing and adjusts the TotalStake - /// and TotalIssuance by subtracting the removed stake amount. - /// - /// Returns the amount of stake that was removed. - /// - /// # Arguments - /// - /// * `coldkey` - A reference to the AccountId of the coldkey involved in the staking. - /// * `hotkey` - A reference to the AccountId of the hotkey associated with the coldkey. - pub fn empty_stake_on_coldkey_hotkey_account( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - ) -> u64 { - let current_stake: u64 = Stake::::get(hotkey, coldkey); - TotalColdkeyStake::::mutate(coldkey, |old| *old = old.saturating_sub(current_stake)); - TotalHotkeyStake::::mutate(hotkey, |stake| *stake = stake.saturating_sub(current_stake)); - Stake::::remove(hotkey, coldkey); - TotalStake::::mutate(|stake| *stake = stake.saturating_sub(current_stake)); - TotalIssuance::::mutate(|issuance| *issuance = issuance.saturating_sub(current_stake)); - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(coldkey); - staking_hotkeys.retain(|h| h != hotkey); - StakingHotkeys::::insert(coldkey, staking_hotkeys); - - current_stake - } - - /// Clears the nomination for an account, if it is a nominator account and the stake is below the minimum required threshold. - pub fn clear_small_nomination_if_required( - hotkey: &T::AccountId, - coldkey: &T::AccountId, - stake: u64, - ) { - // Verify if the account is a nominator account by checking ownership of the hotkey by the coldkey. - if !Self::coldkey_owns_hotkey(coldkey, hotkey) { - // If the stake is below the minimum required, it's considered a small nomination and needs to be cleared. - if stake < Self::get_nominator_min_required_stake() { - // Remove the stake from the nominator account. (this is a more forceful unstake operation which ) - // Actually deletes the staking account. - let cleared_stake = Self::empty_stake_on_coldkey_hotkey_account(coldkey, hotkey); - // Add the stake to the coldkey account. - Self::add_balance_to_coldkey_account(coldkey, cleared_stake); - } - } - } - - /// Clears small nominations for all accounts. - /// - /// WARN: This is an O(N) operation, where N is the number of staking accounts. It should be - /// used with caution. - pub fn clear_small_nominations() { - // Loop through all staking accounts to identify and clear nominations below the minimum stake. - for (hotkey, coldkey, stake) in Stake::::iter() { - Self::clear_small_nomination_if_required(&hotkey, &coldkey, stake); - } - } - - pub fn add_balance_to_coldkey_account( - coldkey: &T::AccountId, - amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, - ) { - // infallible - let _ = T::Currency::deposit(coldkey, amount, Precision::BestEffort); - } - - pub fn set_balance_on_coldkey_account( - coldkey: &T::AccountId, - amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, - ) { - T::Currency::set_balance(coldkey, amount); - } - - pub fn can_remove_balance_from_coldkey_account( - coldkey: &T::AccountId, - amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, - ) -> bool { - let current_balance = Self::get_coldkey_balance(coldkey); - if amount > current_balance { - return false; - } - - // This bit is currently untested. @todo - - T::Currency::can_withdraw(coldkey, amount) - .into_result(false) - .is_ok() - } - - pub fn get_coldkey_balance( - coldkey: &T::AccountId, - ) -> <::Currency as fungible::Inspect<::AccountId>>::Balance - { - T::Currency::reducible_balance(coldkey, Preservation::Expendable, Fortitude::Polite) - } - - #[must_use = "Balance must be used to preserve total issuance of token"] - pub fn remove_balance_from_coldkey_account( - coldkey: &T::AccountId, - amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, - ) -> Result { - if amount == 0 { - return Ok(0); - } - - let credit = T::Currency::withdraw( - coldkey, - amount, - Precision::BestEffort, - Preservation::Preserve, - Fortitude::Polite, - ) - .map_err(|_| Error::::BalanceWithdrawalError)? - .peek(); - - if credit == 0 { - return Err(Error::::ZeroBalanceAfterWithdrawn.into()); - } - - Ok(credit) - } - - pub fn kill_coldkey_account( - coldkey: &T::AccountId, - amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, - ) -> Result { - if amount == 0 { - return Ok(0); - } - - let credit = T::Currency::withdraw( - coldkey, - amount, - Precision::Exact, - Preservation::Expendable, - Fortitude::Force, - ) - .map_err(|_| Error::::BalanceWithdrawalError)? - .peek(); - - if credit == 0 { - return Err(Error::::ZeroBalanceAfterWithdrawn.into()); - } - - Ok(credit) - } - - pub fn unstake_all_coldkeys_from_hotkey_account(hotkey: &T::AccountId) { - // Iterate through all coldkeys that have a stake on this hotkey account. - for (delegate_coldkey_i, stake_i) in - as IterableStorageDoubleMap>::iter_prefix( - hotkey, - ) - { - // Remove the stake from the coldkey - hotkey pairing. - Self::decrease_stake_on_coldkey_hotkey_account(&delegate_coldkey_i, hotkey, stake_i); - - // Add the balance to the coldkey account. - Self::add_balance_to_coldkey_account(&delegate_coldkey_i, stake_i); - } - } -} diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs new file mode 100644 index 000000000..89104de82 --- /dev/null +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -0,0 +1,125 @@ +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. + /// + /// # Args: + /// * 'origin': (RuntimeOrigin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The associated hotkey account. + /// + /// * 'stake_to_be_added' (u64): + /// - The amount of stake to be added to the hotkey staking account. + /// + /// # Event: + /// * StakeAdded; + /// - On the successfully adding stake to a global account. + /// + /// # Raises: + /// * 'NotEnoughBalanceToStake': + /// - Not enough balance on the coldkey to add onto the global account. + /// + /// * 'NonAssociatedColdKey': + /// - The calling coldkey is not associated with this hotkey. + /// + /// * 'BalanceWithdrawalError': + /// - Errors stemming from transaction pallet. + /// + /// * 'TxRateLimitExceeded': + /// - Thrown if key has hit transaction rate limit + /// + pub fn do_add_stake( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + stake_to_be_added: u64, + ) -> dispatch::DispatchResult { + // We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_add_stake( origin:{:?} hotkey:{:?}, stake_to_be_added:{:?} )", + coldkey, + hotkey, + stake_to_be_added + ); + + // Ensure the callers coldkey has enough stake to perform the transaction. + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, stake_to_be_added), + Error::::NotEnoughBalanceToStake + ); + + // Ensure that the hotkey account exists this is only possible through registration. + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + // Ensure that the hotkey allows delegation or that the hotkey is owned by the calling coldkey. + ensure!( + Self::hotkey_is_delegate(&hotkey) || Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::HotKeyNotDelegateAndSignerNotOwnHotKey + ); + + // Ensure we don't exceed stake rate limit + let stakes_this_interval = + Self::get_stakes_this_interval_for_coldkey_hotkey(&coldkey, &hotkey); + ensure!( + stakes_this_interval < Self::get_target_stakes_per_interval(), + Error::::StakeRateLimitExceeded + ); + + // If this is a nomination stake, check if total stake after adding will be above + // the minimum required stake. + + // If coldkey is not owner of the hotkey, it's a nomination stake. + if !Self::coldkey_owns_hotkey(&coldkey, &hotkey) { + let total_stake_after_add = + Stake::::get(&hotkey, &coldkey).saturating_add(stake_to_be_added); + + ensure!( + total_stake_after_add >= NominatorMinRequiredStake::::get(), + Error::::NomStakeBelowMinimumThreshold + ); + } + + // Ensure the remove operation from the coldkey is a success. + let actual_amount_to_stake = + Self::remove_balance_from_coldkey_account(&coldkey, stake_to_be_added)?; + + // If we reach here, add the balance to the hotkey. + Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, actual_amount_to_stake); + + // Set last block for rate limiting + let block: u64 = Self::get_current_block_as_u64(); + Self::set_last_tx_block(&coldkey, block); + + // Emit the staking event. + Self::set_stakes_this_interval_for_coldkey_hotkey( + &coldkey, + &hotkey, + stakes_this_interval.saturating_add(1), + block, + ); + log::info!( + "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", + hotkey, + actual_amount_to_stake + ); + Self::deposit_event(Event::StakeAdded(hotkey, actual_amount_to_stake)); + + // Ok and return. + Ok(()) + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/staking/become_delegate.rs b/pallets/subtensor/src/staking/become_delegate.rs new file mode 100644 index 000000000..28d3b25f4 --- /dev/null +++ b/pallets/subtensor/src/staking/become_delegate.rs @@ -0,0 +1,96 @@ +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. + /// + /// # Args: + /// * 'origin': (RuntimeOrigin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'take' (u16): + /// - The stake proportion that this hotkey takes from delegations. + /// + /// # Event: + /// * DelegateAdded; + /// - On successfully setting a hotkey as a delegate. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldket. + /// + /// * 'TxRateLimitExceeded': + /// - Thrown if key has hit transaction rate limit + /// + pub fn do_become_delegate( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + take: u16, + ) -> dispatch::DispatchResult { + // --- 1. We check the coldkey signuture. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + + // --- 2. Ensure we are delegating an known key. + // --- 3. Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 4. Ensure we are not already a delegate (dont allow changing delegate take.) + ensure!( + !Self::hotkey_is_delegate(&hotkey), + Error::::HotKeyAlreadyDelegate + ); + + // --- 5. Ensure we don't exceed tx rate limit + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), + Error::::DelegateTxRateLimitExceeded + ); + + // --- 5.1 Ensure take is within the min ..= InitialDefaultTake (18%) range + let min_take = MinTake::::get(); + let max_take = MaxTake::::get(); + ensure!(take >= min_take, Error::::DelegateTakeTooLow); + ensure!(take <= max_take, Error::::DelegateTakeTooHigh); + + // --- 6. Delegate the key. + Self::delegate_hotkey(&hotkey, take); + + // Set last block for rate limiting + Self::set_last_tx_block(&coldkey, block); + Self::set_last_tx_block_delegate_take(&coldkey, block); + + // --- 7. Emit the staking event. + log::info!( + "DelegateAdded( coldkey:{:?}, hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + Self::deposit_event(Event::DelegateAdded(coldkey, hotkey, take)); + + // --- 8. Ok and return. + Ok(()) + } +} diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs new file mode 100644 index 000000000..4290a1d24 --- /dev/null +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -0,0 +1,82 @@ +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + /// ---- The implementation for the extrinsic decrease_take + /// + /// # Args: + /// * 'origin': (::RuntimeOrigin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'take' (u16): + /// - The stake proportion that this hotkey takes from delegations for subnet ID. + /// + /// # Event: + /// * TakeDecreased; + /// - On successfully setting a decreased take for this hotkey. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldket. + /// + /// * 'DelegateTakeTooLow': + /// - The delegate is setting a take which is not lower than the previous. + /// + pub fn do_decrease_take( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + take: u16, + ) -> dispatch::DispatchResult { + // --- 1. We check the coldkey signature. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_decrease_take( origin:{:?} hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + + // --- 2. Ensure we are delegating a known key. + // Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 3. Ensure we are always strictly decreasing, never increasing take + if let Ok(current_take) = Delegates::::try_get(&hotkey) { + ensure!(take < current_take, Error::::DelegateTakeTooLow); + } + + // --- 3.1 Ensure take is within the min ..= InitialDefaultTake (18%) range + let min_take = MinTake::::get(); + ensure!(take >= min_take, Error::::DelegateTakeTooLow); + + // --- 4. Set the new take value. + Delegates::::insert(hotkey.clone(), take); + + // --- 5. Emit the take value. + log::info!( + "TakeDecreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + Self::deposit_event(Event::TakeDecreased(coldkey, hotkey, take)); + + // --- 6. Ok and return. + Ok(()) + } +} diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs new file mode 100644 index 000000000..6da16e833 --- /dev/null +++ b/pallets/subtensor/src/staking/helpers.rs @@ -0,0 +1,397 @@ +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + + // Returns true if the passed hotkey allow delegative staking. + // + pub fn hotkey_is_delegate(hotkey: &T::AccountId) -> bool { + Delegates::::contains_key(hotkey) + } + + // Sets the hotkey as a delegate with take. + // + pub fn delegate_hotkey(hotkey: &T::AccountId, take: u16) { + Delegates::::insert(hotkey, take); + } + + // Returns the total amount of stake in the staking table. + // + pub fn get_total_stake() -> u64 { + TotalStake::::get() + } + + // Increases the total amount of stake by the passed amount. + // + pub fn increase_total_stake(increment: u64) { + TotalStake::::put(Self::get_total_stake().saturating_add(increment)); + } + + // Decreases the total amount of stake by the passed amount. + // + pub fn decrease_total_stake(decrement: u64) { + TotalStake::::put(Self::get_total_stake().saturating_sub(decrement)); + } + + // Returns the total amount of stake under a hotkey (delegative or otherwise) + // + pub fn get_total_stake_for_hotkey(hotkey: &T::AccountId) -> u64 { + TotalHotkeyStake::::get(hotkey) + } + + // Returns the total amount of stake held by the coldkey (delegative or otherwise) + // + pub fn get_total_stake_for_coldkey(coldkey: &T::AccountId) -> u64 { + TotalColdkeyStake::::get(coldkey) + } + + // Returns the stake under the cold - hot pairing in the staking table. + // + pub fn get_stake_for_coldkey_and_hotkey(coldkey: &T::AccountId, hotkey: &T::AccountId) -> u64 { + Stake::::get(hotkey, coldkey) + } + + // Retrieves the total stakes for a given hotkey (account ID) for the current staking interval. + pub fn get_stakes_this_interval_for_coldkey_hotkey( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + ) -> u64 { + // Retrieve the configured stake interval duration from storage. + let stake_interval = StakeInterval::::get(); + + // Obtain the current block number as an unsigned 64-bit integer. + let current_block = Self::get_current_block_as_u64(); + + // Fetch the total stakes and the last block number when stakes were made for the hotkey. + let (stakes, block_last_staked_at) = + TotalHotkeyColdkeyStakesThisInterval::::get(coldkey, hotkey); + + // Calculate the block number after which the stakes for the hotkey should be reset. + let block_to_reset_after = block_last_staked_at.saturating_add(stake_interval); + + // If the current block number is beyond the reset point, + // it indicates the end of the staking interval for the hotkey. + if block_to_reset_after <= current_block { + // Reset the stakes for this hotkey for the current interval. + Self::set_stakes_this_interval_for_coldkey_hotkey( + coldkey, + hotkey, + 0, + block_last_staked_at, + ); + // Return 0 as the stake amount since we've just reset the stakes. + return 0; + } + + // If the staking interval has not yet ended, return the current stake amount. + stakes + } + + pub fn get_target_stakes_per_interval() -> u64 { + TargetStakesPerInterval::::get() + } + + // Creates a cold - hot pairing account if the hotkey is not already an active account. + // + pub fn create_account_if_non_existent(coldkey: &T::AccountId, hotkey: &T::AccountId) { + if !Self::hotkey_account_exists(hotkey) { + Stake::::insert(hotkey, coldkey, 0); + Owner::::insert(hotkey, coldkey); + + // Update OwnedHotkeys map + let mut hotkeys = OwnedHotkeys::::get(coldkey); + if !hotkeys.contains(hotkey) { + hotkeys.push(hotkey.clone()); + OwnedHotkeys::::insert(coldkey, hotkeys); + } + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } + } + } + + // Returns the coldkey owning this hotkey. This function should only be called for active accounts. + // + pub fn get_owning_coldkey_for_hotkey(hotkey: &T::AccountId) -> T::AccountId { + Owner::::get(hotkey) + } + + // Returns the hotkey take + // + pub fn get_hotkey_take(hotkey: &T::AccountId) -> u16 { + Delegates::::get(hotkey) + } + + // Returns true if the hotkey account has been created. + // + pub fn hotkey_account_exists(hotkey: &T::AccountId) -> bool { + Owner::::contains_key(hotkey) + } + + // Return true if the passed coldkey owns the hotkey. + // + pub fn coldkey_owns_hotkey(coldkey: &T::AccountId, hotkey: &T::AccountId) -> bool { + if Self::hotkey_account_exists(hotkey) { + Owner::::get(hotkey) == *coldkey + } else { + false + } + } + + // Returns true if the cold-hot staking account has enough balance to fufil the decrement. + // + pub fn has_enough_stake(coldkey: &T::AccountId, hotkey: &T::AccountId, decrement: u64) -> bool { + Self::get_stake_for_coldkey_and_hotkey(coldkey, hotkey) >= decrement + } + + // Increases the stake on the hotkey account under its owning coldkey. + // + pub fn increase_stake_on_hotkey_account(hotkey: &T::AccountId, increment: u64) { + Self::increase_stake_on_coldkey_hotkey_account( + &Self::get_owning_coldkey_for_hotkey(hotkey), + hotkey, + increment, + ); + } + + // Decreases the stake on the hotkey account under its owning coldkey. + // + pub fn decrease_stake_on_hotkey_account(hotkey: &T::AccountId, decrement: u64) { + Self::decrease_stake_on_coldkey_hotkey_account( + &Self::get_owning_coldkey_for_hotkey(hotkey), + hotkey, + decrement, + ); + } + + // Increases the stake on the cold - hot pairing by increment while also incrementing other counters. + // This function should be called rather than set_stake under account. + // + pub fn increase_stake_on_coldkey_hotkey_account( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + increment: u64, + ) { + TotalColdkeyStake::::insert( + coldkey, + TotalColdkeyStake::::get(coldkey).saturating_add(increment), + ); + TotalHotkeyStake::::insert( + hotkey, + TotalHotkeyStake::::get(hotkey).saturating_add(increment), + ); + Stake::::insert( + hotkey, + coldkey, + Stake::::get(hotkey, coldkey).saturating_add(increment), + ); + TotalStake::::put(TotalStake::::get().saturating_add(increment)); + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + } + } + + // Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. + // + pub fn decrease_stake_on_coldkey_hotkey_account( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + decrement: u64, + ) { + TotalColdkeyStake::::mutate(coldkey, |old| *old = old.saturating_sub(decrement)); + TotalHotkeyStake::::insert( + hotkey, + TotalHotkeyStake::::get(hotkey).saturating_sub(decrement), + ); + Stake::::insert( + hotkey, + coldkey, + Stake::::get(hotkey, coldkey).saturating_sub(decrement), + ); + TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); + + // TODO: Tech debt: Remove StakingHotkeys entry if stake goes to 0 + } + + /// Empties the stake associated with a given coldkey-hotkey account pairing. + /// This function retrieves the current stake for the specified coldkey-hotkey pairing, + /// then subtracts this stake amount from both the TotalColdkeyStake and TotalHotkeyStake. + /// It also removes the stake entry for the hotkey-coldkey pairing and adjusts the TotalStake + /// and TotalIssuance by subtracting the removed stake amount. + /// + /// Returns the amount of stake that was removed. + /// + /// # Arguments + /// + /// * `coldkey` - A reference to the AccountId of the coldkey involved in the staking. + /// * `hotkey` - A reference to the AccountId of the hotkey associated with the coldkey. + pub fn empty_stake_on_coldkey_hotkey_account( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + ) -> u64 { + let current_stake: u64 = Stake::::get(hotkey, coldkey); + TotalColdkeyStake::::mutate(coldkey, |old| *old = old.saturating_sub(current_stake)); + TotalHotkeyStake::::mutate(hotkey, |stake| *stake = stake.saturating_sub(current_stake)); + Stake::::remove(hotkey, coldkey); + TotalStake::::mutate(|stake| *stake = stake.saturating_sub(current_stake)); + TotalIssuance::::mutate(|issuance| *issuance = issuance.saturating_sub(current_stake)); + + // Update StakingHotkeys map + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + staking_hotkeys.retain(|h| h != hotkey); + StakingHotkeys::::insert(coldkey, staking_hotkeys); + + current_stake + } + + /// Clears the nomination for an account, if it is a nominator account and the stake is below the minimum required threshold. + pub fn clear_small_nomination_if_required( + hotkey: &T::AccountId, + coldkey: &T::AccountId, + stake: u64, + ) { + // Verify if the account is a nominator account by checking ownership of the hotkey by the coldkey. + if !Self::coldkey_owns_hotkey(coldkey, hotkey) { + // If the stake is below the minimum required, it's considered a small nomination and needs to be cleared. + if stake < Self::get_nominator_min_required_stake() { + // Remove the stake from the nominator account. (this is a more forceful unstake operation which ) + // Actually deletes the staking account. + let cleared_stake = Self::empty_stake_on_coldkey_hotkey_account(coldkey, hotkey); + // Add the stake to the coldkey account. + Self::add_balance_to_coldkey_account(coldkey, cleared_stake); + } + } + } + + /// Clears small nominations for all accounts. + /// + /// WARN: This is an O(N) operation, where N is the number of staking accounts. It should be + /// used with caution. + pub fn clear_small_nominations() { + // Loop through all staking accounts to identify and clear nominations below the minimum stake. + for (hotkey, coldkey, stake) in Stake::::iter() { + Self::clear_small_nomination_if_required(&hotkey, &coldkey, stake); + } + } + + pub fn add_balance_to_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) { + // infallible + let _ = T::Currency::deposit(coldkey, amount, Precision::BestEffort); + } + + pub fn set_balance_on_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) { + T::Currency::set_balance(coldkey, amount); + } + + pub fn can_remove_balance_from_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) -> bool { + let current_balance = Self::get_coldkey_balance(coldkey); + if amount > current_balance { + return false; + } + + // This bit is currently untested. @todo + + T::Currency::can_withdraw(coldkey, amount) + .into_result(false) + .is_ok() + } + + pub fn get_coldkey_balance( + coldkey: &T::AccountId, + ) -> <::Currency as fungible::Inspect<::AccountId>>::Balance + { + T::Currency::reducible_balance(coldkey, Preservation::Expendable, Fortitude::Polite) + } + + #[must_use = "Balance must be used to preserve total issuance of token"] + pub fn remove_balance_from_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) -> Result { + if amount == 0 { + return Ok(0); + } + + let credit = T::Currency::withdraw( + coldkey, + amount, + Precision::BestEffort, + Preservation::Preserve, + Fortitude::Polite, + ) + .map_err(|_| Error::::BalanceWithdrawalError)? + .peek(); + + if credit == 0 { + return Err(Error::::ZeroBalanceAfterWithdrawn.into()); + } + + Ok(credit) + } + + pub fn kill_coldkey_account( + coldkey: &T::AccountId, + amount: <::Currency as fungible::Inspect<::AccountId>>::Balance, + ) -> Result { + if amount == 0 { + return Ok(0); + } + + let credit = T::Currency::withdraw( + coldkey, + amount, + Precision::Exact, + Preservation::Expendable, + Fortitude::Force, + ) + .map_err(|_| Error::::BalanceWithdrawalError)? + .peek(); + + if credit == 0 { + return Err(Error::::ZeroBalanceAfterWithdrawn.into()); + } + + Ok(credit) + } + + pub fn unstake_all_coldkeys_from_hotkey_account(hotkey: &T::AccountId) { + // Iterate through all coldkeys that have a stake on this hotkey account. + for (delegate_coldkey_i, stake_i) in + as IterableStorageDoubleMap>::iter_prefix( + hotkey, + ) + { + // Remove the stake from the coldkey - hotkey pairing. + Self::decrease_stake_on_coldkey_hotkey_account(&delegate_coldkey_i, hotkey, stake_i); + + // Add the balance to the coldkey account. + Self::add_balance_to_coldkey_account(&delegate_coldkey_i, stake_i); + } + } +} diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs new file mode 100644 index 000000000..744027a9b --- /dev/null +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -0,0 +1,99 @@ + +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + /// ---- The implementation for the extrinsic increase_take + /// + /// # Args: + /// * 'origin': (::RuntimeOrigin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The hotkey we are delegating (must be owned by the coldkey.) + /// + /// * 'take' (u16): + /// - The stake proportion that this hotkey takes from delegations for subnet ID. + /// + /// # Event: + /// * TakeIncreased; + /// - On successfully setting a increased take for this hotkey. + /// + /// # Raises: + /// * 'NotRegistered': + /// - The hotkey we are delegating is not registered on the network. + /// + /// * 'NonAssociatedColdKey': + /// - The hotkey we are delegating is not owned by the calling coldket. + /// + /// * 'TxRateLimitExceeded': + /// - Thrown if key has hit transaction rate limit + /// + /// * 'DelegateTakeTooLow': + /// - The delegate is setting a take which is not greater than the previous. + /// + pub fn do_increase_take( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + take: u16, + ) -> dispatch::DispatchResult { + // --- 1. We check the coldkey signature. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_increase_take( origin:{:?} hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + + // --- 2. Ensure we are delegating a known key. + // Ensure that the coldkey is the owner. + Self::do_take_checks(&coldkey, &hotkey)?; + + // --- 3. Ensure we are strinctly increasing take + if let Ok(current_take) = Delegates::::try_get(&hotkey) { + ensure!(take > current_take, Error::::DelegateTakeTooLow); + } + + // --- 4. Ensure take is within the min ..= InitialDefaultTake (18%) range + let max_take = MaxTake::::get(); + ensure!(take <= max_take, Error::::DelegateTakeTooHigh); + + // --- 5. Enforce the rate limit (independently on do_add_stake rate limits) + let block: u64 = Self::get_current_block_as_u64(); + ensure!( + !Self::exceeds_tx_delegate_take_rate_limit( + Self::get_last_tx_block_delegate_take(&coldkey), + block + ), + Error::::DelegateTxRateLimitExceeded + ); + + // Set last block for rate limiting + Self::set_last_tx_block_delegate_take(&coldkey, block); + + // --- 6. Set the new take value. + Delegates::::insert(hotkey.clone(), take); + + // --- 7. Emit the take value. + log::info!( + "TakeIncreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", + coldkey, + hotkey, + take + ); + Self::deposit_event(Event::TakeIncreased(coldkey, hotkey, take)); + + // --- 8. Ok and return. + Ok(()) + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs new file mode 100644 index 000000000..7015b455a --- /dev/null +++ b/pallets/subtensor/src/staking/mod.rs @@ -0,0 +1,7 @@ +use super::*; +pub mod helpers; +pub mod add_stake; +pub mod remove_stake; +pub mod decrease_take; +pub mod increase_take; +pub mod become_delegate; \ No newline at end of file diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs new file mode 100644 index 000000000..5f467e009 --- /dev/null +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -0,0 +1,120 @@ + +use super::*; +use frame_support::{ + storage::IterableStorageDoubleMap, + traits::{ + tokens::{ + fungible::{Balanced as _, Inspect as _, Mutate as _}, + Fortitude, Precision, Preservation, + }, + Imbalance, + }, +}; + +impl Pallet { + /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. + /// + /// # Args: + /// * 'origin': (RuntimeOrigin): + /// - The signature of the caller's coldkey. + /// + /// * 'hotkey' (T::AccountId): + /// - The associated hotkey account. + /// + /// * 'stake_to_be_added' (u64): + /// - The amount of stake to be added to the hotkey staking account. + /// + /// # Event: + /// * StakeRemoved; + /// - On the successfully removing stake from the hotkey account. + /// + /// # Raises: + /// * 'NotRegistered': + /// - Thrown if the account we are attempting to unstake from is non existent. + /// + /// * 'NonAssociatedColdKey': + /// - Thrown if the coldkey does not own the hotkey we are unstaking from. + /// + /// * 'NotEnoughStakeToWithdraw': + /// - Thrown if there is not enough stake on the hotkey to withdwraw this amount. + /// + /// * 'TxRateLimitExceeded': + /// - Thrown if key has hit transaction rate limit + /// + pub fn do_remove_stake( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + stake_to_be_removed: u64, + ) -> dispatch::DispatchResult { + // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. + let coldkey = ensure_signed(origin)?; + log::info!( + "do_remove_stake( origin:{:?} hotkey:{:?}, stake_to_be_removed:{:?} )", + coldkey, + hotkey, + stake_to_be_removed + ); + + // Ensure that the hotkey account exists this is only possible through registration. + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + // Ensure that the hotkey allows delegation or that the hotkey is owned by the calling coldkey. + ensure!( + Self::hotkey_is_delegate(&hotkey) || Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::HotKeyNotDelegateAndSignerNotOwnHotKey + ); + + // Ensure that the stake amount to be removed is above zero. + ensure!(stake_to_be_removed > 0, Error::::StakeToWithdrawIsZero); + + // Ensure that the hotkey has enough stake to withdraw. + ensure!( + Self::has_enough_stake(&coldkey, &hotkey, stake_to_be_removed), + Error::::NotEnoughStakeToWithdraw + ); + + // Ensure we don't exceed stake rate limit + let unstakes_this_interval = + Self::get_stakes_this_interval_for_coldkey_hotkey(&coldkey, &hotkey); + ensure!( + unstakes_this_interval < Self::get_target_stakes_per_interval(), + Error::::UnstakeRateLimitExceeded + ); + + // We remove the balance from the hotkey. + Self::decrease_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_removed); + + // We add the balance to the coldkey. If the above fails we will not credit this coldkey. + Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_removed); + + // If the stake is below the minimum, we clear the nomination from storage. + // This only applies to nominator stakes. + // If the coldkey does not own the hotkey, it's a nominator stake. + let new_stake = Self::get_stake_for_coldkey_and_hotkey(&coldkey, &hotkey); + Self::clear_small_nomination_if_required(&hotkey, &coldkey, new_stake); + + // Set last block for rate limiting + let block: u64 = Self::get_current_block_as_u64(); + Self::set_last_tx_block(&coldkey, block); + + // Emit the unstaking event. + Self::set_stakes_this_interval_for_coldkey_hotkey( + &coldkey, + &hotkey, + unstakes_this_interval.saturating_add(1), + block, + ); + log::info!( + "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", + hotkey, + stake_to_be_removed + ); + Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed)); + + // Done and ok. + Ok(()) + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 83bad5134..78508f9c5 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,7 +1,7 @@ use super::*; use frame_support::traits::fungible::Mutate; use frame_support::traits::tokens::Preservation; -use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use frame_support::weights::Weight; use sp_core::Get; impl Pallet { From fcbf66235f0d2b358a8ef869bfd00eacaef5bcb2 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 14:10:49 -0500 Subject: [PATCH 011/269] fmt --- pallets/subtensor/src/coinbase/mod.rs | 2 +- pallets/subtensor/src/epoch/mod.rs | 2 +- pallets/subtensor/src/lib.rs | 1229 ++++++++++++----- pallets/subtensor/src/macros/config.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/macros/genesis.rs | 3 +- pallets/subtensor/src/macros/hooks.rs | 2 +- pallets/subtensor/src/macros/mod.rs | 6 +- .../migrations/migrate_create_root_network.rs | 4 +- .../migrations/migrate_delete_subnet_21.rs | 5 +- .../src/migrations/migrate_delete_subnet_3.rs | 5 +- .../migrate_populate_owned_hotkeys.rs | 5 +- .../migrate_populate_staking_hotkeys.rs | 4 +- .../migrate_to_v1_separate_emission.rs | 9 +- .../migrate_to_v2_fixed_total_stake.rs | 7 +- .../src/migrations/migrate_total_issuance.rs | 17 +- ...igrate_transfer_ownership_to_foundation.rs | 2 +- pallets/subtensor/src/migrations/mod.rs | 10 +- pallets/subtensor/src/root.rs | 3 +- pallets/subtensor/src/rpc_info/mod.rs | 2 +- pallets/subtensor/src/staking/add_stake.rs | 2 +- pallets/subtensor/src/staking/helpers.rs | 1 - .../subtensor/src/staking/increase_take.rs | 3 +- pallets/subtensor/src/staking/mod.rs | 6 +- pallets/subtensor/src/staking/remove_stake.rs | 3 +- pallets/subtensor/src/swap/mod.rs | 2 +- pallets/subtensor/src/swap/swap_coldkey.rs | 4 - pallets/subtensor/src/swap/swap_hotkey.rs | 3 +- pallets/subtensor/tests/staking.rs | 2 +- pallets/subtensor/tests/swap.rs | 69 +- 30 files changed, 985 insertions(+), 431 deletions(-) diff --git a/pallets/subtensor/src/coinbase/mod.rs b/pallets/subtensor/src/coinbase/mod.rs index e86c66b59..cc5b589f1 100644 --- a/pallets/subtensor/src/coinbase/mod.rs +++ b/pallets/subtensor/src/coinbase/mod.rs @@ -1,2 +1,2 @@ use super::*; -pub mod block_step; \ No newline at end of file +pub mod block_step; diff --git a/pallets/subtensor/src/epoch/mod.rs b/pallets/subtensor/src/epoch/mod.rs index 74f3b1094..723e68ee4 100644 --- a/pallets/subtensor/src/epoch/mod.rs +++ b/pallets/subtensor/src/epoch/mod.rs @@ -1,3 +1,3 @@ use super::*; +pub mod epoch; pub mod math; -pub mod epoch; \ No newline at end of file diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7596029e0..23adeee02 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -35,13 +35,13 @@ mod benchmarks; // ========================= // ==== Pallet Imports ===== // ========================= -mod rpc_info; mod coinbase; pub mod epoch; -pub mod swap; -pub mod staking; mod macros; -use macros::{events, errors, dispatches, genesis, hooks, config}; +mod rpc_info; +pub mod staking; +pub mod swap; +use macros::{config, dispatches, errors, events, genesis, hooks}; mod registration; mod root; @@ -64,12 +64,12 @@ pub mod migrations; #[frame_support::pallet] pub mod pallet { + use crate::migrations; use frame_support::{ dispatch::GetDispatchInfo, pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, traits::{tokens::fungible, UnfilteredDispatchable}, }; - use crate::migrations; use frame_system::pallet_prelude::*; use sp_core::H256; use sp_runtime::traits::TrailingZeroInput; @@ -137,178 +137,427 @@ pub mod pallet { pub ip_type: u8, } - /// ============================ /// ==== Staking + Accounts ==== /// ============================ - #[pallet::type_value] /// Total Rao in circulation. - pub fn TotalSupply() -> u64 { 21_000_000_000_000_000 } - #[pallet::type_value] /// Default total stake. - pub fn DefaultDefaultTake() -> u16 { T::InitialDefaultTake::get() } - #[pallet::type_value] /// Default minimum take. - pub fn DefaultMinTake() -> u16 { T::InitialMinTake::get() } - #[pallet::type_value] /// Default account take. - pub fn DefaultAccountTake() -> u64 { 0 } - #[pallet::type_value] /// Default stakes per interval. - pub fn DefaultStakesPerInterval() -> (u64, u64) { (0, 0) } - #[pallet::type_value] /// Default emission per block. - pub fn DefaultBlockEmission() -> u64 { 1_000_000_000 } - #[pallet::type_value] /// Default allowed delegation. - pub fn DefaultAllowsDelegation() -> bool { false } - #[pallet::type_value] /// Default total issuance. - pub fn DefaultTotalIssuance() -> u64 { T::InitialIssuance::get() } - #[pallet::type_value] /// Default account, derived from zero trailing bytes. - pub fn DefaultAccount() -> T::AccountId { T::AccountId::decode(&mut TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } - #[pallet::type_value] /// Default target stakes per interval. - pub fn DefaultTargetStakesPerInterval() -> u64 { T::InitialTargetStakesPerInterval::get() } - #[pallet::type_value] /// Default stake interval. - pub fn DefaultStakeInterval() -> u64 { 360 } - #[pallet::type_value] /// Default account linkage - pub fn DefaultAccountLinkage() -> Vec<(u64, T::AccountId)> { vec![] } - #[pallet::type_value] /// Default account linkage - pub fn DefaultProportion() -> u64 { 0 } - #[pallet::type_value] /// Default accumulated emission for a hotkey - pub fn DefaultAccumulatedEmission() -> u64 { 0 } - #[pallet::type_value] /// Default last adjustment block. - pub fn DefaultLastAdjustmentBlock() -> u64 { 0 } - #[pallet::type_value] /// Default last adjustment block. - pub fn DefaultRegistrationsThisBlock() -> u16 { 0 } - #[pallet::type_value] /// Default registrations this block. - pub fn DefaultBurn() -> u64 { T::InitialBurn::get() } - #[pallet::type_value] /// Default burn token. - pub fn DefaultMinBurn() -> u64 { T::InitialMinBurn::get() } - #[pallet::type_value] /// Default min burn token. - pub fn DefaultMaxBurn() -> u64 { T::InitialMaxBurn::get() } - #[pallet::type_value] /// Default max burn token. - pub fn DefaultDifficulty() -> u64 { T::InitialDifficulty::get() } - #[pallet::type_value] /// Default difficulty value. - pub fn DefaultMinDifficulty() -> u64 { T::InitialMinDifficulty::get() } - #[pallet::type_value] /// Default min difficulty value. - pub fn DefaultMaxDifficulty() -> u64 { T::InitialMaxDifficulty::get() } - #[pallet::type_value] /// Default max difficulty value. - pub fn DefaultMaxRegistrationsPerBlock() -> u16 { T::InitialMaxRegistrationsPerBlock::get() } - #[pallet::type_value] /// Default max registrations per block. - pub fn DefaultRAORecycledForRegistration() -> u64 { T::InitialRAORecycledForRegistration::get() } - #[pallet::type_value] /// Default number of networks. - pub fn DefaultN() -> u16 { 0 } - #[pallet::type_value] /// Default value for modality. - pub fn DefaultModality() -> u16 { 0 } - #[pallet::type_value] /// Default value for hotkeys. - pub fn DefaultHotkeys() -> Vec { vec![] } - #[pallet::type_value] /// Default value if network is added. - pub fn DefaultNeworksAdded() -> bool { false } - #[pallet::type_value] /// Default value for network member. - pub fn DefaultIsNetworkMember() -> bool { false } - #[pallet::type_value] /// Default value for registration allowed. - pub fn DefaultRegistrationAllowed() -> bool { false } - #[pallet::type_value] /// Default value for network registered at. - pub fn DefaultNetworkRegisteredAt() -> u64 { 0 } - #[pallet::type_value] /// Default value for network immunity period. - pub fn DefaultNetworkImmunityPeriod() -> u64 { T::InitialNetworkImmunityPeriod::get() } - #[pallet::type_value] /// Default value for network last registered. - pub fn DefaultNetworkLastRegistered() -> u64 { 0 } - #[pallet::type_value] /// Default value for nominator min required stake. - pub fn DefaultNominatorMinRequiredStake() -> u64 { 0 } - #[pallet::type_value] /// Default value for network min allowed UIDs. - pub fn DefaultNetworkMinAllowedUids() -> u16 { T::InitialNetworkMinAllowedUids::get() } - #[pallet::type_value] /// Default value for network min lock cost. - pub fn DefaultNetworkMinLockCost() -> u64 { T::InitialNetworkMinLockCost::get() } - #[pallet::type_value] /// Default value for network lock reduction interval. - pub fn DefaultNetworkLockReductionInterval() -> u64 { T::InitialNetworkLockReductionInterval::get() } - #[pallet::type_value] /// Default value for subnet owner cut. - pub fn DefaultSubnetOwnerCut() -> u16 { T::InitialSubnetOwnerCut::get() } - #[pallet::type_value] /// Default value for subnet limit. - pub fn DefaultSubnetLimit() -> u16 { T::InitialSubnetLimit::get() } - #[pallet::type_value] /// Default value for network rate limit. - pub fn DefaultNetworkRateLimit() -> u64 { if cfg!(feature = "pow-faucet") { return 0; } T::InitialNetworkRateLimit::get() } + #[pallet::type_value] + /// Total Rao in circulation. + pub fn TotalSupply() -> u64 { + 21_000_000_000_000_000 + } + #[pallet::type_value] + /// Default total stake. + pub fn DefaultDefaultTake() -> u16 { + T::InitialDefaultTake::get() + } + #[pallet::type_value] + /// Default minimum take. + pub fn DefaultMinTake() -> u16 { + T::InitialMinTake::get() + } + #[pallet::type_value] + /// Default account take. + pub fn DefaultAccountTake() -> u64 { + 0 + } + #[pallet::type_value] + /// Default stakes per interval. + pub fn DefaultStakesPerInterval() -> (u64, u64) { + (0, 0) + } + #[pallet::type_value] + /// Default emission per block. + pub fn DefaultBlockEmission() -> u64 { + 1_000_000_000 + } + #[pallet::type_value] + /// Default allowed delegation. + pub fn DefaultAllowsDelegation() -> bool { + false + } + #[pallet::type_value] + /// Default total issuance. + pub fn DefaultTotalIssuance() -> u64 { + T::InitialIssuance::get() + } + #[pallet::type_value] + /// Default account, derived from zero trailing bytes. + pub fn DefaultAccount() -> T::AccountId { + T::AccountId::decode(&mut TrailingZeroInput::zeroes()) + .expect("trailing zeroes always produce a valid account ID; qed") + } + #[pallet::type_value] + /// Default target stakes per interval. + pub fn DefaultTargetStakesPerInterval() -> u64 { + T::InitialTargetStakesPerInterval::get() + } + #[pallet::type_value] + /// Default stake interval. + pub fn DefaultStakeInterval() -> u64 { + 360 + } + #[pallet::type_value] + /// Default account linkage + pub fn DefaultAccountLinkage() -> Vec<(u64, T::AccountId)> { + vec![] + } + #[pallet::type_value] + /// Default account linkage + pub fn DefaultProportion() -> u64 { + 0 + } + #[pallet::type_value] + /// Default accumulated emission for a hotkey + pub fn DefaultAccumulatedEmission() -> u64 { + 0 + } + #[pallet::type_value] + /// Default last adjustment block. + pub fn DefaultLastAdjustmentBlock() -> u64 { + 0 + } + #[pallet::type_value] + /// Default last adjustment block. + pub fn DefaultRegistrationsThisBlock() -> u16 { + 0 + } + #[pallet::type_value] + /// Default registrations this block. + pub fn DefaultBurn() -> u64 { + T::InitialBurn::get() + } + #[pallet::type_value] + /// Default burn token. + pub fn DefaultMinBurn() -> u64 { + T::InitialMinBurn::get() + } + #[pallet::type_value] + /// Default min burn token. + pub fn DefaultMaxBurn() -> u64 { + T::InitialMaxBurn::get() + } + #[pallet::type_value] + /// Default max burn token. + pub fn DefaultDifficulty() -> u64 { + T::InitialDifficulty::get() + } + #[pallet::type_value] + /// Default difficulty value. + pub fn DefaultMinDifficulty() -> u64 { + T::InitialMinDifficulty::get() + } + #[pallet::type_value] + /// Default min difficulty value. + pub fn DefaultMaxDifficulty() -> u64 { + T::InitialMaxDifficulty::get() + } + #[pallet::type_value] + /// Default max difficulty value. + pub fn DefaultMaxRegistrationsPerBlock() -> u16 { + T::InitialMaxRegistrationsPerBlock::get() + } + #[pallet::type_value] + /// Default max registrations per block. + pub fn DefaultRAORecycledForRegistration() -> u64 { + T::InitialRAORecycledForRegistration::get() + } + #[pallet::type_value] + /// Default number of networks. + pub fn DefaultN() -> u16 { + 0 + } + #[pallet::type_value] + /// Default value for modality. + pub fn DefaultModality() -> u16 { + 0 + } + #[pallet::type_value] + /// Default value for hotkeys. + pub fn DefaultHotkeys() -> Vec { + vec![] + } + #[pallet::type_value] + /// Default value if network is added. + pub fn DefaultNeworksAdded() -> bool { + false + } + #[pallet::type_value] + /// Default value for network member. + pub fn DefaultIsNetworkMember() -> bool { + false + } + #[pallet::type_value] + /// Default value for registration allowed. + pub fn DefaultRegistrationAllowed() -> bool { + false + } + #[pallet::type_value] + /// Default value for network registered at. + pub fn DefaultNetworkRegisteredAt() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for network immunity period. + pub fn DefaultNetworkImmunityPeriod() -> u64 { + T::InitialNetworkImmunityPeriod::get() + } + #[pallet::type_value] + /// Default value for network last registered. + pub fn DefaultNetworkLastRegistered() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for nominator min required stake. + pub fn DefaultNominatorMinRequiredStake() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for network min allowed UIDs. + pub fn DefaultNetworkMinAllowedUids() -> u16 { + T::InitialNetworkMinAllowedUids::get() + } + #[pallet::type_value] + /// Default value for network min lock cost. + pub fn DefaultNetworkMinLockCost() -> u64 { + T::InitialNetworkMinLockCost::get() + } + #[pallet::type_value] + /// Default value for network lock reduction interval. + pub fn DefaultNetworkLockReductionInterval() -> u64 { + T::InitialNetworkLockReductionInterval::get() + } + #[pallet::type_value] + /// Default value for subnet owner cut. + pub fn DefaultSubnetOwnerCut() -> u16 { + T::InitialSubnetOwnerCut::get() + } + #[pallet::type_value] + /// Default value for subnet limit. + pub fn DefaultSubnetLimit() -> u16 { + T::InitialSubnetLimit::get() + } + #[pallet::type_value] + /// Default value for network rate limit. + pub fn DefaultNetworkRateLimit() -> u64 { + if cfg!(feature = "pow-faucet") { + return 0; + } + T::InitialNetworkRateLimit::get() + } // #[pallet::type_value] /// Default value for network max stake. // pub fn DefaultNetworkMaxStake() -> u64 { T::InitialNetworkMaxStake::get() } - #[pallet::type_value] /// Default value for emission values. - pub fn DefaultEmissionValues() -> u64 { 0 } - #[pallet::type_value] /// Default value for pending emission. - pub fn DefaultPendingEmission() -> u64 { 0 } - #[pallet::type_value] /// Default value for blocks since last step. - pub fn DefaultBlocksSinceLastStep() -> u64 { 0 } - #[pallet::type_value] /// Default value for last mechanism step block. - pub fn DefaultLastMechanismStepBlock() -> u64 { 0 } - #[pallet::type_value] /// Default value for subnet owner. - pub fn DefaultSubnetOwner() -> T::AccountId { T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } - #[pallet::type_value] /// Default value for subnet locked. - pub fn DefaultSubnetLocked() -> u64 { 0 } - #[pallet::type_value] /// Default value for network tempo - pub fn DefaultTempo() -> u16 { T::InitialTempo::get() } - #[pallet::type_value] /// Default value for weights set rate limit. - pub fn DefaultWeightsSetRateLimit() -> u64 { 100 } - #[pallet::type_value] /// Default block number at registration. - pub fn DefaultBlockAtRegistration() -> u64 { 0 } - #[pallet::type_value] /// Default value for rho parameter. - pub fn DefaultRho() -> u16 { T::InitialRho::get() } - #[pallet::type_value] /// Default value for kappa parameter. - pub fn DefaultKappa() -> u16 { T::InitialKappa::get() } - #[pallet::type_value] /// Default maximum allowed UIDs. - pub fn DefaultMaxAllowedUids() -> u16 { T::InitialMaxAllowedUids::get() } - #[pallet::type_value] /// Default immunity period. - pub fn DefaultImmunityPeriod() -> u16 { T::InitialImmunityPeriod::get() } - #[pallet::type_value] /// Default activity cutoff. - pub fn DefaultActivityCutoff() -> u16 { T::InitialActivityCutoff::get() } - #[pallet::type_value] /// Default maximum weights limit. - pub fn DefaultMaxWeightsLimit() -> u16 { T::InitialMaxWeightsLimit::get() } - #[pallet::type_value] /// Default weights version key. - pub fn DefaultWeightsVersionKey() -> u64 { T::InitialWeightsVersionKey::get() } - #[pallet::type_value] /// Default minimum allowed weights. - pub fn DefaultMinAllowedWeights() -> u16 { T::InitialMinAllowedWeights::get() } - #[pallet::type_value] /// Default maximum allowed validators. - pub fn DefaultMaxAllowedValidators() -> u16 { T::InitialMaxAllowedValidators::get() } - #[pallet::type_value] /// Default adjustment interval. - pub fn DefaultAdjustmentInterval() -> u16 { T::InitialAdjustmentInterval::get() } - #[pallet::type_value] /// Default bonds moving average. - pub fn DefaultBondsMovingAverage() -> u64 { T::InitialBondsMovingAverage::get() } - #[pallet::type_value] /// Default validator prune length. - pub fn DefaultValidatorPruneLen() -> u64 { T::InitialValidatorPruneLen::get() } - #[pallet::type_value] /// Default scaling law power. - pub fn DefaultScalingLawPower() -> u16 { T::InitialScalingLawPower::get() } - #[pallet::type_value] /// Default target registrations per interval. - pub fn DefaultTargetRegistrationsPerInterval() -> u16 { T::InitialTargetRegistrationsPerInterval::get() } - #[pallet::type_value] /// Default adjustment alpha. - pub fn DefaultAdjustmentAlpha() -> u64 { T::InitialAdjustmentAlpha::get() } - #[pallet::type_value] /// Default minimum stake for weights. - pub fn DefaultWeightsMinStake() -> u64 { 0 } - #[pallet::type_value] /// Value definition for vector of u16. - pub fn EmptyU16Vec() -> Vec { vec![] } - #[pallet::type_value] /// Value definition for vector of u64. - pub fn EmptyU64Vec() -> Vec { vec![] } - #[pallet::type_value] /// Value definition for vector of bool. - pub fn EmptyBoolVec() -> Vec { vec![] } - #[pallet::type_value] /// Value definition for bonds with type vector of (u16, u16). - pub fn DefaultBonds() -> Vec<(u16, u16)> { vec![] } - #[pallet::type_value] /// Value definition for weights with vector of (u16, u16). - pub fn DefaultWeights() -> Vec<(u16, u16)> { vec![] } - #[pallet::type_value] /// Default value for key with type T::AccountId derived from trailing zeroes. - pub fn DefaultKey() -> T::AccountId { T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).expect("trailing zeroes always produce a valid account ID; qed") } - #[pallet::type_value] /// Default value for network immunity period. - pub fn DefaultHotkeyEmissionTempo() -> u64 { 7200 } - #[pallet::type_value] /// Default value for rate limiting - pub fn DefaultTxRateLimit() -> u64 { T::InitialTxRateLimit::get() } - #[pallet::type_value] /// Default value for delegate take rate limiting - pub fn DefaultTxDelegateTakeRateLimit() -> u64 { T::InitialTxDelegateTakeRateLimit::get() } - #[pallet::type_value] /// Default value for last extrinsic block. - pub fn DefaultLastTxBlock() -> u64 { 0 } - #[pallet::type_value] /// Default value for serving rate limit. - pub fn DefaultServingRateLimit() -> u64 { T::InitialServingRateLimit::get() } - #[pallet::type_value] /// Default value for weight commit reveal interval. - pub fn DefaultWeightCommitRevealInterval() -> u64 { 1000 } - #[pallet::type_value] /// Default value for weight commit/reveal enabled. - pub fn DefaultCommitRevealWeightsEnabled() -> bool { false } - #[pallet::type_value] /// Senate requirements - pub fn DefaultSenateRequiredStakePercentage() -> u64 { T::InitialSenateRequiredStakePercentage::get() } - #[pallet::type_value] /// -- ITEM (switches liquid alpha on) - pub fn DefaultLiquidAlpha() -> bool {false} - #[pallet::type_value] /// (alpha_low: 0.7, alpha_high: 0.9) - pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } - - #[pallet::storage] - pub type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; + #[pallet::type_value] + /// Default value for emission values. + pub fn DefaultEmissionValues() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for pending emission. + pub fn DefaultPendingEmission() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for blocks since last step. + pub fn DefaultBlocksSinceLastStep() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for last mechanism step block. + pub fn DefaultLastMechanismStepBlock() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for subnet owner. + pub fn DefaultSubnetOwner() -> T::AccountId { + T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("trailing zeroes always produce a valid account ID; qed") + } + #[pallet::type_value] + /// Default value for subnet locked. + pub fn DefaultSubnetLocked() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for network tempo + pub fn DefaultTempo() -> u16 { + T::InitialTempo::get() + } + #[pallet::type_value] + /// Default value for weights set rate limit. + pub fn DefaultWeightsSetRateLimit() -> u64 { + 100 + } + #[pallet::type_value] + /// Default block number at registration. + pub fn DefaultBlockAtRegistration() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for rho parameter. + pub fn DefaultRho() -> u16 { + T::InitialRho::get() + } + #[pallet::type_value] + /// Default value for kappa parameter. + pub fn DefaultKappa() -> u16 { + T::InitialKappa::get() + } + #[pallet::type_value] + /// Default maximum allowed UIDs. + pub fn DefaultMaxAllowedUids() -> u16 { + T::InitialMaxAllowedUids::get() + } + #[pallet::type_value] + /// Default immunity period. + pub fn DefaultImmunityPeriod() -> u16 { + T::InitialImmunityPeriod::get() + } + #[pallet::type_value] + /// Default activity cutoff. + pub fn DefaultActivityCutoff() -> u16 { + T::InitialActivityCutoff::get() + } + #[pallet::type_value] + /// Default maximum weights limit. + pub fn DefaultMaxWeightsLimit() -> u16 { + T::InitialMaxWeightsLimit::get() + } + #[pallet::type_value] + /// Default weights version key. + pub fn DefaultWeightsVersionKey() -> u64 { + T::InitialWeightsVersionKey::get() + } + #[pallet::type_value] + /// Default minimum allowed weights. + pub fn DefaultMinAllowedWeights() -> u16 { + T::InitialMinAllowedWeights::get() + } + #[pallet::type_value] + /// Default maximum allowed validators. + pub fn DefaultMaxAllowedValidators() -> u16 { + T::InitialMaxAllowedValidators::get() + } + #[pallet::type_value] + /// Default adjustment interval. + pub fn DefaultAdjustmentInterval() -> u16 { + T::InitialAdjustmentInterval::get() + } + #[pallet::type_value] + /// Default bonds moving average. + pub fn DefaultBondsMovingAverage() -> u64 { + T::InitialBondsMovingAverage::get() + } + #[pallet::type_value] + /// Default validator prune length. + pub fn DefaultValidatorPruneLen() -> u64 { + T::InitialValidatorPruneLen::get() + } + #[pallet::type_value] + /// Default scaling law power. + pub fn DefaultScalingLawPower() -> u16 { + T::InitialScalingLawPower::get() + } + #[pallet::type_value] + /// Default target registrations per interval. + pub fn DefaultTargetRegistrationsPerInterval() -> u16 { + T::InitialTargetRegistrationsPerInterval::get() + } + #[pallet::type_value] + /// Default adjustment alpha. + pub fn DefaultAdjustmentAlpha() -> u64 { + T::InitialAdjustmentAlpha::get() + } + #[pallet::type_value] + /// Default minimum stake for weights. + pub fn DefaultWeightsMinStake() -> u64 { + 0 + } + #[pallet::type_value] + /// Value definition for vector of u16. + pub fn EmptyU16Vec() -> Vec { + vec![] + } + #[pallet::type_value] + /// Value definition for vector of u64. + pub fn EmptyU64Vec() -> Vec { + vec![] + } + #[pallet::type_value] + /// Value definition for vector of bool. + pub fn EmptyBoolVec() -> Vec { + vec![] + } + #[pallet::type_value] + /// Value definition for bonds with type vector of (u16, u16). + pub fn DefaultBonds() -> Vec<(u16, u16)> { + vec![] + } + #[pallet::type_value] + /// Value definition for weights with vector of (u16, u16). + pub fn DefaultWeights() -> Vec<(u16, u16)> { + vec![] + } + #[pallet::type_value] + /// Default value for key with type T::AccountId derived from trailing zeroes. + pub fn DefaultKey() -> T::AccountId { + T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) + .expect("trailing zeroes always produce a valid account ID; qed") + } + #[pallet::type_value] + /// Default value for network immunity period. + pub fn DefaultHotkeyEmissionTempo() -> u64 { + 7200 + } + #[pallet::type_value] + /// Default value for rate limiting + pub fn DefaultTxRateLimit() -> u64 { + T::InitialTxRateLimit::get() + } + #[pallet::type_value] + /// Default value for delegate take rate limiting + pub fn DefaultTxDelegateTakeRateLimit() -> u64 { + T::InitialTxDelegateTakeRateLimit::get() + } + #[pallet::type_value] + /// Default value for last extrinsic block. + pub fn DefaultLastTxBlock() -> u64 { + 0 + } + #[pallet::type_value] + /// Default value for serving rate limit. + pub fn DefaultServingRateLimit() -> u64 { + T::InitialServingRateLimit::get() + } + #[pallet::type_value] + /// Default value for weight commit reveal interval. + pub fn DefaultWeightCommitRevealInterval() -> u64 { + 1000 + } + #[pallet::type_value] + /// Default value for weight commit/reveal enabled. + pub fn DefaultCommitRevealWeightsEnabled() -> bool { + false + } + #[pallet::type_value] + /// Senate requirements + pub fn DefaultSenateRequiredStakePercentage() -> u64 { + T::InitialSenateRequiredStakePercentage::get() + } + #[pallet::type_value] + /// -- ITEM (switches liquid alpha on) + pub fn DefaultLiquidAlpha() -> bool { + false + } + #[pallet::type_value] + /// (alpha_low: 0.7, alpha_high: 0.9) + pub fn DefaultAlphaValues() -> (u16, u16) { + (45875, 58982) + } + + #[pallet::storage] + pub type SenateRequiredStakePercentage = + StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; /// ============================ /// ==== Staking Variables ==== @@ -324,227 +573,499 @@ pub mod pallet { #[pallet::storage] // --- ITEM ( global_block_emission ) pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; #[pallet::storage] // --- ITEM (target_stakes_per_interval) - pub type TargetStakesPerInterval = StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; + pub type TargetStakesPerInterval = + StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; #[pallet::storage] // --- ITEM (default_stake_interval) pub type StakeInterval = StorageValue<_, u64, ValueQuery, DefaultStakeInterval>; #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. - pub type TotalHotkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + pub type TotalHotkeyStake = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey. - pub type TotalColdkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] /// MAP (hot, cold) --> stake | Returns a tuple (u64: stakes, u64: block_number) - pub type TotalHotkeyColdkeyStakesThisInterval = StorageDoubleMap<_, Identity, T::AccountId, Identity, T::AccountId, (u64, u64), ValueQuery, DefaultStakesPerInterval>; - #[pallet::storage] /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. - pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; - #[pallet::storage] /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. - pub type Delegates = StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; - #[pallet::storage] /// DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. - pub type Stake = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] /// Map ( hot ) --> last_hotkey_emission_drain | Last block we drained this hotkey's emission. - pub type LastHotkeyEmissionDrain = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery, DefaultAccumulatedEmission>; - #[pallet::storage] /// ITEM ( hotkey_emission_tempo ) - pub type HotkeyEmissionTempo = StorageValue<_, u64, ValueQuery, DefaultHotkeyEmissionTempo>; - #[pallet::storage] /// Map ( hot ) --> emission | Accumulated hotkey emission. - pub type PendingdHotkeyEmission = StorageMap<_, Blake2_128Concat, T::AccountId, u64, ValueQuery, DefaultAccumulatedEmission>; - #[pallet::storage] /// Map ( hot, cold ) --> block_number | Last add stake increase. - pub type LastAddStakeIncrease = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> - pub type ChildKeys = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, Vec<(u64, T::AccountId)>, ValueQuery, DefaultAccountLinkage>; - #[pallet::storage] /// DMAP ( child, netuid ) --> Vec<(proportion,parent)> - pub type ParentKeys = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, Vec<(u64, T::AccountId)>, ValueQuery, DefaultAccountLinkage>; + pub type TotalColdkeyStake = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] + /// MAP (hot, cold) --> stake | Returns a tuple (u64: stakes, u64: block_number) + pub type TotalHotkeyColdkeyStakesThisInterval = StorageDoubleMap< + _, + Identity, + T::AccountId, + Identity, + T::AccountId, + (u64, u64), + ValueQuery, + DefaultStakesPerInterval, + >; + #[pallet::storage] + /// MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. + pub type Owner = + StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; + #[pallet::storage] + /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. + pub type Delegates = + StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; + #[pallet::storage] + /// DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. + pub type Stake = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + T::AccountId, + u64, + ValueQuery, + DefaultAccountTake, + >; + #[pallet::storage] + /// Map ( hot ) --> last_hotkey_emission_drain | Last block we drained this hotkey's emission. + pub type LastHotkeyEmissionDrain = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u64, + ValueQuery, + DefaultAccumulatedEmission, + >; + #[pallet::storage] + /// ITEM ( hotkey_emission_tempo ) + pub type HotkeyEmissionTempo = + StorageValue<_, u64, ValueQuery, DefaultHotkeyEmissionTempo>; + #[pallet::storage] + /// Map ( hot ) --> emission | Accumulated hotkey emission. + pub type PendingdHotkeyEmission = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u64, + ValueQuery, + DefaultAccumulatedEmission, + >; + #[pallet::storage] + /// Map ( hot, cold ) --> block_number | Last add stake increase. + pub type LastAddStakeIncrease = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + T::AccountId, + u64, + ValueQuery, + DefaultAccountTake, + >; + #[pallet::storage] + /// DMAP ( parent, netuid ) --> Vec<(proportion,child)> + pub type ChildKeys = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + u16, + Vec<(u64, T::AccountId)>, + ValueQuery, + DefaultAccountLinkage, + >; + #[pallet::storage] + /// DMAP ( child, netuid ) --> Vec<(proportion,parent)> + pub type ParentKeys = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + u16, + Vec<(u64, T::AccountId)>, + ValueQuery, + DefaultAccountLinkage, + >; #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it - pub type StakingHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + pub type StakingHotkeys = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; #[pallet::storage] // --- MAP ( cold ) --> Vec | Returns the vector of hotkeys controlled by this coldkey. - pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; - + pub type OwnedHotkeys = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; /// ============================ /// ==== Global Parameters ===== /// ============================ - #[pallet::storage] /// --- StorageItem Global Used Work. + #[pallet::storage] + /// --- StorageItem Global Used Work. pub type UsedWork = StorageMap<_, Identity, Vec, u64, ValueQuery>; - #[pallet::storage] /// --- ITEM( global_max_registrations_per_block ) - pub type MaxRegistrationsPerBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; - #[pallet::storage] /// --- ITEM( maximum_number_of_networks ) + #[pallet::storage] + /// --- ITEM( global_max_registrations_per_block ) + pub type MaxRegistrationsPerBlock = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxRegistrationsPerBlock>; + #[pallet::storage] + /// --- ITEM( maximum_number_of_networks ) pub type SubnetLimit = StorageValue<_, u16, ValueQuery, DefaultSubnetLimit>; - #[pallet::storage] /// --- ITEM( total_number_of_existing_networks ) + #[pallet::storage] + /// --- ITEM( total_number_of_existing_networks ) pub type TotalNetworks = StorageValue<_, u16, ValueQuery>; - #[pallet::storage] /// ITEM( network_immunity_period ) - pub type NetworkImmunityPeriod = StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; - #[pallet::storage] /// ITEM( network_last_registered_block ) - pub type NetworkLastRegistered = StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; - #[pallet::storage] /// ITEM( network_min_allowed_uids ) - pub type NetworkMinAllowedUids = StorageValue<_, u16, ValueQuery, DefaultNetworkMinAllowedUids>; - #[pallet::storage] /// ITEM( min_network_lock_cost ) + #[pallet::storage] + /// ITEM( network_immunity_period ) + pub type NetworkImmunityPeriod = + StorageValue<_, u64, ValueQuery, DefaultNetworkImmunityPeriod>; + #[pallet::storage] + /// ITEM( network_last_registered_block ) + pub type NetworkLastRegistered = + StorageValue<_, u64, ValueQuery, DefaultNetworkLastRegistered>; + #[pallet::storage] + /// ITEM( network_min_allowed_uids ) + pub type NetworkMinAllowedUids = + StorageValue<_, u16, ValueQuery, DefaultNetworkMinAllowedUids>; + #[pallet::storage] + /// ITEM( min_network_lock_cost ) pub type NetworkMinLockCost = StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] /// ITEM( last_network_lock_cost ) - pub type NetworkLastLockCost = StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; - #[pallet::storage] /// ITEM( network_lock_reduction_interval ) - pub type NetworkLockReductionInterval = StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; - #[pallet::storage] /// ITEM( subnet_owner_cut ) + #[pallet::storage] + /// ITEM( last_network_lock_cost ) + pub type NetworkLastLockCost = + StorageValue<_, u64, ValueQuery, DefaultNetworkMinLockCost>; + #[pallet::storage] + /// ITEM( network_lock_reduction_interval ) + pub type NetworkLockReductionInterval = + StorageValue<_, u64, ValueQuery, DefaultNetworkLockReductionInterval>; + #[pallet::storage] + /// ITEM( subnet_owner_cut ) pub type SubnetOwnerCut = StorageValue<_, u16, ValueQuery, DefaultSubnetOwnerCut>; - #[pallet::storage] /// ITEM( network_rate_limit ) + #[pallet::storage] + /// ITEM( network_rate_limit ) pub type NetworkRateLimit = StorageValue<_, u64, ValueQuery, DefaultNetworkRateLimit>; - #[pallet::storage] /// ITEM( nominator_min_required_stake ) - pub type NominatorMinRequiredStake = StorageValue<_, u64, ValueQuery, DefaultNominatorMinRequiredStake>; + #[pallet::storage] + /// ITEM( nominator_min_required_stake ) + pub type NominatorMinRequiredStake = + StorageValue<_, u64, ValueQuery, DefaultNominatorMinRequiredStake>; /// ============================ /// ==== Subnet Parameters ===== /// ============================ - #[pallet::storage] /// --- MAP ( netuid ) --> subnetwork_n (Number of UIDs in the network). + #[pallet::storage] + /// --- MAP ( netuid ) --> subnetwork_n (Number of UIDs in the network). pub type SubnetworkN = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultN>; - #[pallet::storage] /// --- MAP ( netuid ) --> modality TEXT: 0, IMAGE: 1, TENSOR: 2 + #[pallet::storage] + /// --- MAP ( netuid ) --> modality TEXT: 0, IMAGE: 1, TENSOR: 2 pub type NetworkModality = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultModality>; - #[pallet::storage] /// --- MAP ( netuid ) --> network_is_added - pub type NetworksAdded = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultNeworksAdded>; - #[pallet::storage] /// --- DMAP ( hotkey, netuid ) --> bool - pub type IsNetworkMember = StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Identity, u16, bool, ValueQuery, DefaultIsNetworkMember>; - #[pallet::storage] /// --- MAP ( netuid ) --> network_registration_allowed - pub type NetworkRegistrationAllowed = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] /// --- MAP ( netuid ) --> network_pow_allowed - pub type NetworkPowRegistrationAllowed = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; - #[pallet::storage] /// --- MAP ( netuid ) --> block_created - pub type NetworkRegisteredAt = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkRegisteredAt>; - #[pallet::storage] /// --- MAP ( netuid ) --> tempo + #[pallet::storage] + /// --- MAP ( netuid ) --> network_is_added + pub type NetworksAdded = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultNeworksAdded>; + #[pallet::storage] + /// --- DMAP ( hotkey, netuid ) --> bool + pub type IsNetworkMember = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Identity, + u16, + bool, + ValueQuery, + DefaultIsNetworkMember, + >; + #[pallet::storage] + /// --- MAP ( netuid ) --> network_registration_allowed + pub type NetworkRegistrationAllowed = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; + #[pallet::storage] + /// --- MAP ( netuid ) --> network_pow_allowed + pub type NetworkPowRegistrationAllowed = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultRegistrationAllowed>; + #[pallet::storage] + /// --- MAP ( netuid ) --> block_created + pub type NetworkRegisteredAt = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkRegisteredAt>; + #[pallet::storage] + /// --- MAP ( netuid ) --> tempo pub type Tempo = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTempo>; - #[pallet::storage] /// --- MAP ( netuid ) --> emission_values - pub type EmissionValues = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEmissionValues>; - #[pallet::storage] /// --- MAP ( netuid ) --> pending_emission - pub type PendingEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultPendingEmission>; - #[pallet::storage] /// --- MAP ( netuid ) --> blocks_since_last_step - pub type BlocksSinceLastStep = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBlocksSinceLastStep>; - #[pallet::storage] /// --- MAP ( netuid ) --> last_mechanism_step_block - pub type LastMechansimStepBlock = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastMechanismStepBlock>; - #[pallet::storage] /// --- MAP ( netuid ) --> subnet_owner - pub type SubnetOwner = StorageMap<_, Identity, u16, T::AccountId, ValueQuery, DefaultSubnetOwner>; - #[pallet::storage] /// --- MAP ( netuid ) --> subnet_locked - pub type SubnetLocked = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultSubnetLocked>; - #[pallet::storage] /// --- MAP ( netuid ) --> serving_rate_limit - pub type ServingRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultServingRateLimit>; - #[pallet::storage] /// --- MAP ( netuid ) --> Rho + #[pallet::storage] + /// --- MAP ( netuid ) --> emission_values + pub type EmissionValues = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultEmissionValues>; + #[pallet::storage] + /// --- MAP ( netuid ) --> pending_emission + pub type PendingEmission = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultPendingEmission>; + #[pallet::storage] + /// --- MAP ( netuid ) --> blocks_since_last_step + pub type BlocksSinceLastStep = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBlocksSinceLastStep>; + #[pallet::storage] + /// --- MAP ( netuid ) --> last_mechanism_step_block + pub type LastMechansimStepBlock = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastMechanismStepBlock>; + #[pallet::storage] + /// --- MAP ( netuid ) --> subnet_owner + pub type SubnetOwner = + StorageMap<_, Identity, u16, T::AccountId, ValueQuery, DefaultSubnetOwner>; + #[pallet::storage] + /// --- MAP ( netuid ) --> subnet_locked + pub type SubnetLocked = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultSubnetLocked>; + #[pallet::storage] + /// --- MAP ( netuid ) --> serving_rate_limit + pub type ServingRateLimit = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultServingRateLimit>; + #[pallet::storage] + /// --- MAP ( netuid ) --> Rho pub type Rho = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRho>; - #[pallet::storage] /// --- MAP ( netuid ) --> Kappa + #[pallet::storage] + /// --- MAP ( netuid ) --> Kappa pub type Kappa = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultKappa>; - #[pallet::storage] /// --- MAP ( netuid ) --> uid, we use to record uids to prune at next epoch. + #[pallet::storage] + /// --- MAP ( netuid ) --> uid, we use to record uids to prune at next epoch. pub type NeuronsToPruneAtNextEpoch = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] /// --- MAP ( netuid ) --> registrations_this_interval + #[pallet::storage] + /// --- MAP ( netuid ) --> registrations_this_interval pub type RegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] /// --- MAP ( netuid ) --> pow_registrations_this_interval - pub type POWRegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] /// --- MAP ( netuid ) --> burn_registrations_this_interval - pub type BurnRegistrationsThisInterval = StorageMap<_, Identity, u16, u16, ValueQuery>; - #[pallet::storage] /// --- MAP ( netuid ) --> max_allowed_uids - pub type MaxAllowedUids = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedUids>; - #[pallet::storage] /// --- MAP ( netuid ) --> immunity_period - pub type ImmunityPeriod = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultImmunityPeriod>; - #[pallet::storage] /// --- MAP ( netuid ) --> activity_cutoff - pub type ActivityCutoff = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultActivityCutoff>; - #[pallet::storage] /// --- MAP ( netuid ) --> max_weight_limit - pub type MaxWeightsLimit = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxWeightsLimit>; - #[pallet::storage] /// --- MAP ( netuid ) --> weights_version_key - pub type WeightsVersionKey = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsVersionKey>; - #[pallet::storage] /// --- MAP ( netuid ) --> min_allowed_weights - pub type MinAllowedWeights = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMinAllowedWeights>; - #[pallet::storage] /// --- MAP ( netuid ) --> max_allowed_validators - pub type MaxAllowedValidators = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedValidators>; - #[pallet::storage] /// --- MAP ( netuid ) --> adjustment_interval - pub type AdjustmentInterval = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultAdjustmentInterval>; - #[pallet::storage] /// --- MAP ( netuid ) --> bonds_moving_average - pub type BondsMovingAverage = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; - #[pallet::storage] /// --- MAP ( netuid ) --> weights_set_rate_limit - pub type WeightsSetRateLimit = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsSetRateLimit>; - #[pallet::storage] /// --- MAP ( netuid ) --> validator_prune_len - pub type ValidatorPruneLen = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultValidatorPruneLen>; - #[pallet::storage] /// --- MAP ( netuid ) --> scaling_law_power - pub type ScalingLawPower = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultScalingLawPower>; - #[pallet::storage] /// --- MAP ( netuid ) --> target_registrations_this_interval - pub type TargetRegistrationsPerInterval = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTargetRegistrationsPerInterval>; - #[pallet::storage] /// --- MAP ( netuid ) --> adjustment_alpha - pub type AdjustmentAlpha = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultAdjustmentAlpha>; - #[pallet::storage] /// --- MAP ( netuid ) --> interval - pub type WeightCommitRevealInterval = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightCommitRevealInterval>; - #[pallet::storage] /// --- MAP ( netuid ) --> interval - pub type CommitRevealWeightsEnabled = StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; - #[pallet::storage] /// --- MAP ( netuid ) --> Burn + #[pallet::storage] + /// --- MAP ( netuid ) --> pow_registrations_this_interval + pub type POWRegistrationsThisInterval = + StorageMap<_, Identity, u16, u16, ValueQuery>; + #[pallet::storage] + /// --- MAP ( netuid ) --> burn_registrations_this_interval + pub type BurnRegistrationsThisInterval = + StorageMap<_, Identity, u16, u16, ValueQuery>; + #[pallet::storage] + /// --- MAP ( netuid ) --> max_allowed_uids + pub type MaxAllowedUids = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedUids>; + #[pallet::storage] + /// --- MAP ( netuid ) --> immunity_period + pub type ImmunityPeriod = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultImmunityPeriod>; + #[pallet::storage] + /// --- MAP ( netuid ) --> activity_cutoff + pub type ActivityCutoff = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultActivityCutoff>; + #[pallet::storage] + /// --- MAP ( netuid ) --> max_weight_limit + pub type MaxWeightsLimit = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxWeightsLimit>; + #[pallet::storage] + /// --- MAP ( netuid ) --> weights_version_key + pub type WeightsVersionKey = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsVersionKey>; + #[pallet::storage] + /// --- MAP ( netuid ) --> min_allowed_weights + pub type MinAllowedWeights = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMinAllowedWeights>; + #[pallet::storage] + /// --- MAP ( netuid ) --> max_allowed_validators + pub type MaxAllowedValidators = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultMaxAllowedValidators>; + #[pallet::storage] + /// --- MAP ( netuid ) --> adjustment_interval + pub type AdjustmentInterval = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultAdjustmentInterval>; + #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_moving_average + pub type BondsMovingAverage = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; + #[pallet::storage] + /// --- MAP ( netuid ) --> weights_set_rate_limit + pub type WeightsSetRateLimit = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightsSetRateLimit>; + #[pallet::storage] + /// --- MAP ( netuid ) --> validator_prune_len + pub type ValidatorPruneLen = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultValidatorPruneLen>; + #[pallet::storage] + /// --- MAP ( netuid ) --> scaling_law_power + pub type ScalingLawPower = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultScalingLawPower>; + #[pallet::storage] + /// --- MAP ( netuid ) --> target_registrations_this_interval + pub type TargetRegistrationsPerInterval = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultTargetRegistrationsPerInterval>; + #[pallet::storage] + /// --- MAP ( netuid ) --> adjustment_alpha + pub type AdjustmentAlpha = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultAdjustmentAlpha>; + #[pallet::storage] + /// --- MAP ( netuid ) --> interval + pub type WeightCommitRevealInterval = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultWeightCommitRevealInterval>; + #[pallet::storage] + /// --- MAP ( netuid ) --> interval + pub type CommitRevealWeightsEnabled = + StorageMap<_, Identity, u16, bool, ValueQuery, DefaultCommitRevealWeightsEnabled>; + #[pallet::storage] + /// --- MAP ( netuid ) --> Burn pub type Burn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBurn>; - #[pallet::storage] /// --- MAP ( netuid ) --> Difficulty + #[pallet::storage] + /// --- MAP ( netuid ) --> Difficulty pub type Difficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultDifficulty>; - #[pallet::storage] /// --- MAP ( netuid ) --> MinBurn + #[pallet::storage] + /// --- MAP ( netuid ) --> MinBurn pub type MinBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinBurn>; - #[pallet::storage] /// --- MAP ( netuid ) --> MaxBurn + #[pallet::storage] + /// --- MAP ( netuid ) --> MaxBurn pub type MaxBurn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxBurn>; - #[pallet::storage] /// --- MAP ( netuid ) --> MinDifficulty - pub type MinDifficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinDifficulty>; - #[pallet::storage] /// --- MAP ( netuid ) --> MaxDifficulty - pub type MaxDifficulty = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxDifficulty>; - #[pallet::storage] /// --- MAP ( netuid ) --> Block at last adjustment. - pub type LastAdjustmentBlock = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastAdjustmentBlock>; - #[pallet::storage] /// --- MAP ( netuid ) --> Registrations of this Block. - pub type RegistrationsThisBlock = StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRegistrationsThisBlock>; - #[pallet::storage] /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration - pub type RAORecycledForRegistration = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; - #[pallet::storage] /// --- ITEM ( tx_rate_limit ) + #[pallet::storage] + /// --- MAP ( netuid ) --> MinDifficulty + pub type MinDifficulty = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMinDifficulty>; + #[pallet::storage] + /// --- MAP ( netuid ) --> MaxDifficulty + pub type MaxDifficulty = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultMaxDifficulty>; + #[pallet::storage] + /// --- MAP ( netuid ) --> Block at last adjustment. + pub type LastAdjustmentBlock = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastAdjustmentBlock>; + #[pallet::storage] + /// --- MAP ( netuid ) --> Registrations of this Block. + pub type RegistrationsThisBlock = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultRegistrationsThisBlock>; + #[pallet::storage] + /// --- MAP ( netuid ) --> global_RAO_recycled_for_registration + pub type RAORecycledForRegistration = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultRAORecycledForRegistration>; + #[pallet::storage] + /// --- ITEM ( tx_rate_limit ) pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; - #[pallet::storage] /// --- ITEM ( tx_rate_limit ) - pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; - #[pallet::storage] /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled - pub type LiquidAlphaOn = StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; - #[pallet::storage] /// MAP ( netuid ) --> (alpha_low, alpha_high) - pub type AlphaValues = StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; - + #[pallet::storage] + /// --- ITEM ( tx_rate_limit ) + pub type TxDelegateTakeRateLimit = + StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; + #[pallet::storage] + /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled + pub type LiquidAlphaOn = + StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; + #[pallet::storage] + /// MAP ( netuid ) --> (alpha_low, alpha_high) + pub type AlphaValues = + StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; /// ======================================= /// ==== Subnetwork Consensus Storage ==== /// ======================================= - #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid - pub type Uids = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; - #[pallet::storage] /// --- DMAP ( netuid, uid ) --> hotkey - pub type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; - #[pallet::storage] /// --- DMAP ( netuid ) --> (hotkey, se, ve) - pub type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; - #[pallet::storage] /// --- DMAP ( netuid ) --> active - pub type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> rank + #[pallet::storage] + /// --- DMAP ( netuid, hotkey ) --> uid + pub type Uids = + StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, u16, OptionQuery>; + #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> hotkey + pub type Keys = + StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> (hotkey, se, ve) + pub type LoadedEmission = + StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> active + pub type Active = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> rank pub type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> trust + #[pallet::storage] + /// --- DMAP ( netuid ) --> trust pub type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> consensus - pub type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> incentive - pub type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> dividends - pub type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> emission - pub type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> last_update - pub type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> validator_trust - pub type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> pruning_scores - pub type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; - #[pallet::storage] /// --- DMAP ( netuid ) --> validator_permit - pub type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; - #[pallet::storage] /// --- DMAP ( netuid, uid ) --> weights - pub type Weights = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultWeights>; - #[pallet::storage] /// --- DMAP ( netuid, uid ) --> bonds - pub type Bonds = StorageDoubleMap<_, Identity, u16, Identity, u16, Vec<(u16, u16)>, ValueQuery, DefaultBonds>; - #[pallet::storage] /// --- DMAP ( netuid, uid ) --> block_at_registration - pub type BlockAtRegistration = StorageDoubleMap<_, Identity, u16, Identity, u16, u64, ValueQuery, DefaultBlockAtRegistration>; - #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> axon_info - pub type Axons = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; - #[pallet::storage] /// --- MAP ( netuid, hotkey ) --> prometheus_info - pub type Prometheus = StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, PrometheusInfoOf, OptionQuery>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> consensus + pub type Consensus = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> incentive + pub type Incentive = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> dividends + pub type Dividends = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> emission + pub type Emission = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> last_update + pub type LastUpdate = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> validator_trust + pub type ValidatorTrust = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> pruning_scores + pub type PruningScores = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + #[pallet::storage] + /// --- DMAP ( netuid ) --> validator_permit + pub type ValidatorPermit = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; + #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> weights + pub type Weights = StorageDoubleMap< + _, + Identity, + u16, + Identity, + u16, + Vec<(u16, u16)>, + ValueQuery, + DefaultWeights, + >; + #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> bonds + pub type Bonds = StorageDoubleMap< + _, + Identity, + u16, + Identity, + u16, + Vec<(u16, u16)>, + ValueQuery, + DefaultBonds, + >; + #[pallet::storage] + /// --- DMAP ( netuid, uid ) --> block_at_registration + pub type BlockAtRegistration = StorageDoubleMap< + _, + Identity, + u16, + Identity, + u16, + u64, + ValueQuery, + DefaultBlockAtRegistration, + >; + #[pallet::storage] + /// --- MAP ( netuid, hotkey ) --> axon_info + pub type Axons = + StorageDoubleMap<_, Identity, u16, Blake2_128Concat, T::AccountId, AxonInfoOf, OptionQuery>; + #[pallet::storage] + /// --- MAP ( netuid, hotkey ) --> prometheus_info + pub type Prometheus = StorageDoubleMap< + _, + Identity, + u16, + Blake2_128Concat, + T::AccountId, + PrometheusInfoOf, + OptionQuery, + >; /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= - #[pallet::storage] /// --- MAP ( key ) --> last_block - pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; - #[pallet::storage] /// --- MAP ( key ) --> last_block - pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; - #[pallet::storage] /// ITEM( weights_min_stake ) + #[pallet::storage] + /// --- MAP ( key ) --> last_block + pub type LastTxBlock = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] + /// --- MAP ( key ) --> last_block + pub type LastTxBlockDelegateTake = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] + /// ITEM( weights_min_stake ) pub type WeightsMinStake = StorageValue<_, u64, ValueQuery, DefaultWeightsMinStake>; - #[pallet::storage] /// --- MAP (netuid, who) --> (hash, weight) | Returns the hash and weight committed by an account for a given netuid. - pub type WeightCommits = StorageDoubleMap<_, Twox64Concat, u16, Twox64Concat, T::AccountId, (H256, u64), OptionQuery>; + #[pallet::storage] + /// --- MAP (netuid, who) --> (hash, weight) | Returns the hash and weight committed by an account for a given netuid. + pub type WeightCommits = StorageDoubleMap< + _, + Twox64Concat, + u16, + Twox64Concat, + T::AccountId, + (H256, u64), + OptionQuery, + >; /// ================== /// ==== Genesis ===== /// ================== @@ -773,12 +1294,10 @@ where priority: Self::get_priority_vanilla(), ..Default::default() }), - Some(Call::dissolve_network { .. }) => { - Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }) - } + Some(Call::dissolve_network { .. }) => Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }), _ => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 4e3cf5d2a..a640ecbb6 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -169,4 +169,4 @@ mod config { #[pallet::constant] type LiquidAlphaOn: Get; } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 6d4c588e9..228bec6e4 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -843,4 +843,4 @@ mod dispatches { Ok(()) } } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index 5fbf613d6..949667807 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -159,5 +159,4 @@ mod genesis { TargetRegistrationsPerInterval::::insert(root_netuid, 1); } } - -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 7e612fb4e..b04a29ff6 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -72,4 +72,4 @@ mod hooks { weight } } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/macros/mod.rs b/pallets/subtensor/src/macros/mod.rs index d64983d52..e491ec8c4 100644 --- a/pallets/subtensor/src/macros/mod.rs +++ b/pallets/subtensor/src/macros/mod.rs @@ -1,6 +1,6 @@ -pub mod events; -pub mod errors; +pub mod config; pub mod dispatches; +pub mod errors; +pub mod events; pub mod genesis; pub mod hooks; -pub mod config; \ No newline at end of file diff --git a/pallets/subtensor/src/migrations/migrate_create_root_network.rs b/pallets/subtensor/src/migrations/migrate_create_root_network.rs index 20ee82f0a..b4b003404 100644 --- a/pallets/subtensor/src/migrations/migrate_create_root_network.rs +++ b/pallets/subtensor/src/migrations/migrate_create_root_network.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, storage_alias, - traits::{Get, DefensiveResult}, + traits::{DefensiveResult, Get}, weights::Weight, }; use sp_std::vec::Vec; @@ -96,4 +96,4 @@ pub fn migrate_create_root_network() -> Weight { } weight -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs index 23fbe122f..c26c917a4 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::{ - pallet_prelude::*, storage_alias, + pallet_prelude::*, + storage_alias, traits::{Get, GetStorageVersion, StorageVersion}, weights::Weight, }; @@ -124,4 +125,4 @@ pub fn migrate_delete_subnet_21() -> Weight { // TODO: Add unit tests for this migration // TODO: Consider adding error handling for storage operations -// TODO: Verify that all relevant storage items for subnet 21 are removed \ No newline at end of file +// TODO: Verify that all relevant storage items for subnet 21 are removed diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs index 257752c19..bbe36db80 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::{ - pallet_prelude::*, storage_alias, + pallet_prelude::*, + storage_alias, traits::{Get, GetStorageVersion, StorageVersion}, weights::Weight, }; @@ -127,4 +128,4 @@ pub fn migrate_delete_subnet_3() -> Weight { // TODO: Add unit tests for this migration // TODO: Consider adding error handling for storage operations -// TODO: Verify that all relevant storage items for subnet 3 are removed \ No newline at end of file +// TODO: Verify that all relevant storage items for subnet 3 are removed diff --git a/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs b/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs index 23cf90d33..e8fd212ec 100644 --- a/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs +++ b/pallets/subtensor/src/migrations/migrate_populate_owned_hotkeys.rs @@ -2,7 +2,7 @@ use super::*; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, storage_alias, - traits::{Get}, + traits::Get, weights::Weight, }; use log::info; @@ -19,7 +19,6 @@ pub mod deprecated_loaded_emission_format { StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; } - /// Migrate the OwnedHotkeys map to the new storage format pub fn migrate_populate_owned() -> Weight { // Setup migration weight @@ -80,4 +79,4 @@ pub fn migrate_populate_owned() -> Weight { info!(target: LOG_TARGET_1, "Migration {} already done!", migration_name); Weight::zero() } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs b/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs index 2c0988bbd..0245ae3c9 100644 --- a/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs +++ b/pallets/subtensor/src/migrations/migrate_populate_staking_hotkeys.rs @@ -1,9 +1,8 @@ - use super::*; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, storage_alias, - traits::{Get}, + traits::Get, weights::Weight, }; use log::info; @@ -19,7 +18,6 @@ pub mod deprecated_loaded_emission_format { StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; } - /// Populate the StakingHotkeys map from Stake map pub fn migrate_populate_staking_hotkeys() -> Weight { // Setup migration weight diff --git a/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs index 5db04f0bc..5d28337dc 100644 --- a/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs +++ b/pallets/subtensor/src/migrations/migrate_to_v1_separate_emission.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::{ - pallet_prelude::*, storage_alias, + pallet_prelude::*, + storage_alias, traits::{Get, GetStorageVersion, StorageVersion}, weights::Weight, }; @@ -39,7 +40,7 @@ pub mod deprecated_loaded_emission_format { /// ``` pub fn migrate_to_v1_separate_emission() -> Weight { use deprecated_loaded_emission_format as old; - + // Initialize weight counter let mut weight = T::DbWeight::get().reads_writes(1, 0); @@ -55,7 +56,7 @@ pub fn migrate_to_v1_separate_emission() -> Weight { // Collect all network IDs (netuids) from old LoadedEmission storage let curr_loaded_emission: Vec = old::LoadedEmission::::iter_keys().collect(); - + // Remove any undecodable entries for netuid in curr_loaded_emission { weight.saturating_accrue(T::DbWeight::get().reads(1)); @@ -103,4 +104,4 @@ pub fn migrate_to_v1_separate_emission() -> Weight { // TODO: Add unit tests for this migration // TODO: Consider adding error handling for edge cases -// TODO: Verify that all possible states of the old format are handled correctly \ No newline at end of file +// TODO: Verify that all possible states of the old format are handled correctly diff --git a/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs index ef9fe8880..f3e63b6fd 100644 --- a/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs +++ b/pallets/subtensor/src/migrations/migrate_to_v2_fixed_total_stake.rs @@ -1,6 +1,7 @@ use super::*; use frame_support::{ - pallet_prelude::*, storage_alias, + pallet_prelude::*, + storage_alias, traits::{Get, GetStorageVersion, StorageVersion}, weights::Weight, }; @@ -71,7 +72,7 @@ pub fn migrate_to_v2_fixed_total_stake() -> Weight { // Recalculate TotalStake and TotalColdkeyStake based on the Stake map for (_, coldkey, stake) in Stake::::iter() { weight.saturating_accrue(T::DbWeight::get().reads(1)); - + // Update TotalColdkeyStake let mut total_coldkey_stake = TotalColdkeyStake::::get(coldkey.clone()); weight.saturating_accrue(T::DbWeight::get().reads(1)); @@ -100,4 +101,4 @@ pub fn migrate_to_v2_fixed_total_stake() -> Weight { // TODO: Add unit tests for this migration function // TODO: Consider adding error handling for potential arithmetic overflow -// TODO: Optimize the iteration over Stake map if possible to reduce database reads \ No newline at end of file +// TODO: Optimize the iteration over Stake map if possible to reduce database reads diff --git a/pallets/subtensor/src/migrations/migrate_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_total_issuance.rs index 187967da3..9a4085379 100644 --- a/pallets/subtensor/src/migrations/migrate_total_issuance.rs +++ b/pallets/subtensor/src/migrations/migrate_total_issuance.rs @@ -1,4 +1,5 @@ use super::*; +use frame_support::pallet_prelude::OptionQuery; use frame_support::{ pallet_prelude::Identity, storage_alias, @@ -6,7 +7,6 @@ use frame_support::{ weights::Weight, }; use sp_std::vec::Vec; -use frame_support::pallet_prelude::OptionQuery; // TODO: Implement comprehensive tests for this migration @@ -43,14 +43,17 @@ pub fn migrate_total_issuance(test: bool) -> Weight { // Execute migration if the current storage version is 5 or if in test mode if Pallet::::on_chain_storage_version() == StorageVersion::new(5) || test { // Calculate the sum of all stake values - let stake_sum: u64 = Stake::::iter().fold(0, |acc, (_, _, stake)| acc.saturating_add(stake)); + let stake_sum: u64 = + Stake::::iter().fold(0, |acc, (_, _, stake)| acc.saturating_add(stake)); // Add weight for reading all stake entries weight = weight.saturating_add(T::DbWeight::get().reads(Stake::::iter().count() as u64)); // Calculate the sum of all locked subnet values - let locked_sum: u64 = SubnetLocked::::iter().fold(0, |acc, (_, locked)| acc.saturating_add(locked)); + let locked_sum: u64 = + SubnetLocked::::iter().fold(0, |acc, (_, locked)| acc.saturating_add(locked)); // Add weight for reading all subnet locked entries - weight = weight.saturating_add(T::DbWeight::get().reads(SubnetLocked::::iter().count() as u64)); + weight = weight + .saturating_add(T::DbWeight::get().reads(SubnetLocked::::iter().count() as u64)); // Retrieve the total balance sum let total_balance = T::Currency::total_issuance(); @@ -61,7 +64,9 @@ pub fn migrate_total_issuance(test: bool) -> Weight { match TryInto::::try_into(total_balance) { Ok(total_balance_sum) => { // Compute the total issuance value - let total_issuance_value: u64 = stake_sum.saturating_add(total_balance_sum).saturating_add(locked_sum); + let total_issuance_value: u64 = stake_sum + .saturating_add(total_balance_sum) + .saturating_add(locked_sum); // Update the total issuance in storage TotalIssuance::::put(total_issuance_value); @@ -81,4 +86,4 @@ pub fn migrate_total_issuance(test: bool) -> Weight { // Return the computed weight of the migration process weight -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs index 0ca4a7fa5..8d1bd437c 100644 --- a/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs +++ b/pallets/subtensor/src/migrations/migrate_transfer_ownership_to_foundation.rs @@ -84,4 +84,4 @@ pub fn migrate_transfer_ownership_to_foundation(coldkey: [u8; 32]) -> info!(target: LOG_TARGET, "Migration to v3 already completed"); Weight::zero() } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index cdc512d63..df0f7ce06 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -1,11 +1,11 @@ use super::*; -pub mod migrate_delete_subnet_21; pub mod migrate_create_root_network; +pub mod migrate_delete_subnet_21; pub mod migrate_delete_subnet_3; +pub mod migrate_fix_total_coldkey_stake; +pub mod migrate_populate_owned_hotkeys; +pub mod migrate_populate_staking_hotkeys; pub mod migrate_to_v1_separate_emission; pub mod migrate_to_v2_fixed_total_stake; -pub mod migrate_transfer_ownership_to_foundation; pub mod migrate_total_issuance; -pub mod migrate_populate_owned_hotkeys; -pub mod migrate_populate_staking_hotkeys; -pub mod migrate_fix_total_coldkey_stake; \ No newline at end of file +pub mod migrate_transfer_ownership_to_foundation; diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index b3ac32f55..948c91f20 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -905,7 +905,6 @@ impl Pallet { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; - // --- 1. Rate limit for network registrations. let current_block = Self::get_current_block_as_u64(); let last_lock_block = Self::get_network_last_lock_block(); @@ -993,7 +992,7 @@ impl Pallet { pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. let coldkey = ensure_signed(origin)?; - + // --- 2. Ensure this subnet exists. ensure!( Self::if_subnet_exist(netuid), diff --git a/pallets/subtensor/src/rpc_info/mod.rs b/pallets/subtensor/src/rpc_info/mod.rs index 70dc816cc..7d050b601 100644 --- a/pallets/subtensor/src/rpc_info/mod.rs +++ b/pallets/subtensor/src/rpc_info/mod.rs @@ -1,5 +1,5 @@ use super::*; +pub mod delegate_info; pub mod neuron_info; pub mod stake_info; pub mod subnet_info; -pub mod delegate_info; \ No newline at end of file diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 89104de82..a2e7ccdc4 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -122,4 +122,4 @@ impl Pallet { // Ok and return. Ok(()) } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 6da16e833..92aa394ed 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -11,7 +11,6 @@ use frame_support::{ }; impl Pallet { - // Returns true if the passed hotkey allow delegative staking. // pub fn hotkey_is_delegate(hotkey: &T::AccountId) -> bool { diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index 744027a9b..d5140b656 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -1,4 +1,3 @@ - use super::*; use frame_support::{ storage::IterableStorageDoubleMap, @@ -96,4 +95,4 @@ impl Pallet { // --- 8. Ok and return. Ok(()) } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs index 7015b455a..5e1b5f6bf 100644 --- a/pallets/subtensor/src/staking/mod.rs +++ b/pallets/subtensor/src/staking/mod.rs @@ -1,7 +1,7 @@ use super::*; -pub mod helpers; pub mod add_stake; -pub mod remove_stake; +pub mod become_delegate; pub mod decrease_take; +pub mod helpers; pub mod increase_take; -pub mod become_delegate; \ No newline at end of file +pub mod remove_stake; diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5f467e009..59e96777b 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,4 +1,3 @@ - use super::*; use frame_support::{ storage::IterableStorageDoubleMap, @@ -117,4 +116,4 @@ impl Pallet { // Done and ok. Ok(()) } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/swap/mod.rs b/pallets/subtensor/src/swap/mod.rs index 0e71b1b1d..4e4b92907 100644 --- a/pallets/subtensor/src/swap/mod.rs +++ b/pallets/subtensor/src/swap/mod.rs @@ -1,3 +1,3 @@ use super::*; pub mod swap_coldkey; -pub mod swap_hotkey; \ No newline at end of file +pub mod swap_hotkey; diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 78508f9c5..8651f0076 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -5,8 +5,6 @@ use frame_support::weights::Weight; use sp_core::Get; impl Pallet { - - /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. /// /// # Arguments @@ -130,7 +128,6 @@ impl Pallet { Ok(weight) } - /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. /// /// # Arguments @@ -387,5 +384,4 @@ impl Pallet { } weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - } diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 2755f1c4c..cf279ca10 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -92,7 +92,7 @@ impl Pallet { Ok(Some(weight).into()) } - + /// Retrieves the network membership status for a given hotkey. /// /// # Arguments @@ -422,7 +422,6 @@ impl Pallet { } } - pub fn swap_senate_member( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index a923a7010..ece30391d 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -3383,4 +3383,4 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { expected_delegated_stake, actual_delegated_stake ); }); -} \ No newline at end of file +} diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 1d05b1c51..7b32a947b 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -88,8 +88,12 @@ fn test_do_swap_hotkey_ok() { // IsNetworkMember for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert!(pallet_subtensor::IsNetworkMember::::contains_key(new_hotkey, netuid)); - assert!(!pallet_subtensor::IsNetworkMember::::contains_key(old_hotkey, netuid)); + assert!(pallet_subtensor::IsNetworkMember::::contains_key( + new_hotkey, netuid + )); + assert!(!pallet_subtensor::IsNetworkMember::::contains_key( + old_hotkey, netuid + )); } // Owner @@ -123,8 +127,12 @@ fn test_do_swap_hotkey_ok() { // TotalHotkeyColdkeyStakesThisInterval assert_eq!( - pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), - pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get(old_hotkey, coldkey) + pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get( + new_hotkey, coldkey + ), + pallet_subtensor::TotalHotkeyColdkeyStakesThisInterval::::get( + old_hotkey, coldkey + ) ); }); } @@ -226,8 +234,13 @@ fn test_do_swap_hotkey_ok_robust() { // Verify raw storage maps // Stake - for (coldkey, stake_amount) in pallet_subtensor::Stake::::iter_prefix(old_hotkeys[i]) { - assert_eq!(pallet_subtensor::Stake::::get(new_hotkeys[i], coldkey), stake_amount); + for (coldkey, stake_amount) in + pallet_subtensor::Stake::::iter_prefix(old_hotkeys[i]) + { + assert_eq!( + pallet_subtensor::Stake::::get(new_hotkeys[i], coldkey), + stake_amount + ); } let mut weight = Weight::zero(); @@ -276,12 +289,18 @@ fn test_do_swap_hotkey_ok_robust() { } // Owner - assert_eq!(pallet_subtensor::Owner::::get(new_hotkeys[i]), coldkeys[i]); + assert_eq!( + pallet_subtensor::Owner::::get(new_hotkeys[i]), + coldkeys[i] + ); // Keys for (uid, hotkey) in pallet_subtensor::Keys::::iter_prefix(netuid) { if hotkey == old_hotkeys[i] { - assert_eq!(pallet_subtensor::Keys::::get(netuid, uid), new_hotkeys[i]); + assert_eq!( + pallet_subtensor::Keys::::get(netuid, uid), + new_hotkeys[i] + ); } } @@ -738,8 +757,13 @@ fn test_swap_axons_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(pallet_subtensor::Axons::::get(netuid, new_hotkey).unwrap(), axon_info); - assert!(!pallet_subtensor::Axons::::contains_key(netuid, old_hotkey)); + assert_eq!( + pallet_subtensor::Axons::::get(netuid, new_hotkey).unwrap(), + axon_info + ); + assert!(!pallet_subtensor::Axons::::contains_key( + netuid, old_hotkey + )); } }); } @@ -924,8 +948,13 @@ fn test_swap_uids_success() { // Verify the swap for netuid in &netuid_is_member { - assert_eq!(pallet_subtensor::Uids::::get(netuid, new_hotkey).unwrap(), uid); - assert!(!pallet_subtensor::Uids::::contains_key(netuid, old_hotkey)); + assert_eq!( + pallet_subtensor::Uids::::get(netuid, new_hotkey).unwrap(), + uid + ); + assert!(!pallet_subtensor::Uids::::contains_key( + netuid, old_hotkey + )); } }); } @@ -970,7 +999,11 @@ fn test_swap_prometheus_success() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - pallet_subtensor::Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); + pallet_subtensor::Prometheus::::insert( + netuid, + old_hotkey, + prometheus_info.clone(), + ); } // Perform the swap @@ -982,7 +1015,9 @@ fn test_swap_prometheus_success() { pallet_subtensor::Prometheus::::get(netuid, new_hotkey).unwrap(), prometheus_info ); - assert!(!pallet_subtensor::Prometheus::::contains_key(netuid, old_hotkey)); + assert!(!pallet_subtensor::Prometheus::::contains_key( + netuid, old_hotkey + )); } }); } @@ -1004,7 +1039,11 @@ fn test_swap_prometheus_weight_update() { // Initialize Prometheus for old_hotkey for netuid in &netuid_is_member { - pallet_subtensor::Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); + pallet_subtensor::Prometheus::::insert( + netuid, + old_hotkey, + prometheus_info.clone(), + ); } // Perform the swap From 4270cc4b816a86c75981da5a7504f0890c5494c8 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 14:20:33 -0500 Subject: [PATCH 012/269] clippy fmt --- pallets/subtensor/src/epoch/mod.rs | 2 +- .../src/epoch/{epoch.rs => run_epoch.rs} | 0 pallets/subtensor/src/macros/genesis.rs | 8 +-- .../migrations/migrate_create_root_network.rs | 2 +- .../migrations/migrate_delete_subnet_21.rs | 2 +- .../src/migrations/migrate_delete_subnet_3.rs | 2 +- pallets/subtensor/src/staking/add_stake.rs | 11 +--- .../subtensor/src/staking/become_delegate.rs | 11 +--- .../subtensor/src/staking/decrease_take.rs | 11 +--- pallets/subtensor/src/staking/helpers.rs | 62 ++++++++++++++----- .../subtensor/src/staking/increase_take.rs | 11 +--- pallets/subtensor/src/staking/remove_stake.rs | 11 +--- 12 files changed, 61 insertions(+), 72 deletions(-) rename pallets/subtensor/src/epoch/{epoch.rs => run_epoch.rs} (100%) diff --git a/pallets/subtensor/src/epoch/mod.rs b/pallets/subtensor/src/epoch/mod.rs index 723e68ee4..3b22f940e 100644 --- a/pallets/subtensor/src/epoch/mod.rs +++ b/pallets/subtensor/src/epoch/mod.rs @@ -1,3 +1,3 @@ use super::*; -pub mod epoch; pub mod math; +pub mod run_epoch; diff --git a/pallets/subtensor/src/epoch/epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs similarity index 100% rename from pallets/subtensor/src/epoch/epoch.rs rename to pallets/subtensor/src/epoch/run_epoch.rs diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index 949667807..34a888f28 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -77,7 +77,7 @@ mod genesis { // Set max allowed uids MaxAllowedUids::::insert(netuid, max_uids); - let mut next_uid = 0; + let mut next_uid: u16 = 0; for (coldkey, hotkeys) in self.stakes.iter() { for (hotkey, stake_uid) in hotkeys.iter() { @@ -116,7 +116,7 @@ mod genesis { Stake::::insert(hotkey.clone(), coldkey.clone(), stake); - next_uid += 1; + next_uid = next_uid.saturating_add(1); } } @@ -124,7 +124,7 @@ mod genesis { SubnetworkN::::insert(netuid, next_uid); // --- Increase total network count. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_add( 1 )); // Get the root network uid. let root_netuid: u16 = 0; @@ -133,7 +133,7 @@ mod genesis { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_add(1)); // Set the number of validators to 1. SubnetworkN::::insert(root_netuid, 0); diff --git a/pallets/subtensor/src/migrations/migrate_create_root_network.rs b/pallets/subtensor/src/migrations/migrate_create_root_network.rs index b4b003404..fa9f34301 100644 --- a/pallets/subtensor/src/migrations/migrate_create_root_network.rs +++ b/pallets/subtensor/src/migrations/migrate_create_root_network.rs @@ -55,7 +55,7 @@ pub fn migrate_create_root_network() -> Weight { NetworksAdded::::insert(root_netuid, true); // Increment the total number of networks - TotalNetworks::::mutate(|n| *n += 1); + TotalNetworks::::mutate(|n| n.saturating_add( 1 )); // Set the maximum number of UIDs to the number of senate members MaxAllowedUids::::insert(root_netuid, 64); diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs index c26c917a4..011d79de1 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -66,7 +66,7 @@ pub fn migrate_delete_subnet_21() -> Weight { NetworksAdded::::remove(netuid); // Decrement the network counter - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_sub( 1 )); // Remove network registration time NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs index bbe36db80..a07434bcb 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -69,7 +69,7 @@ pub fn migrate_delete_subnet_3() -> Weight { NetworksAdded::::remove(netuid); // Decrement the network counter - TotalNetworks::::mutate(|n| *n -= 1); + TotalNetworks::::mutate(|n| n.saturating_sub(1)); // Remove network registration time NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index a2e7ccdc4..422dfa2e4 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,14 +1,5 @@ use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; + impl Pallet { /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. diff --git a/pallets/subtensor/src/staking/become_delegate.rs b/pallets/subtensor/src/staking/become_delegate.rs index 28d3b25f4..4df8adf2d 100644 --- a/pallets/subtensor/src/staking/become_delegate.rs +++ b/pallets/subtensor/src/staking/become_delegate.rs @@ -1,14 +1,5 @@ use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; + impl Pallet { /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index 4290a1d24..6aac33651 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -1,14 +1,5 @@ use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; + impl Pallet { /// ---- The implementation for the extrinsic decrease_take diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 92aa394ed..486577712 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -122,26 +122,47 @@ impl Pallet { } } - // Returns the coldkey owning this hotkey. This function should only be called for active accounts. - // + /// Returns the coldkey owning this hotkey. This function should only be called for active accounts. + /// + /// # Arguments + /// * `hotkey` - The hotkey account ID. + /// + /// # Returns + /// The coldkey account ID that owns the hotkey. pub fn get_owning_coldkey_for_hotkey(hotkey: &T::AccountId) -> T::AccountId { Owner::::get(hotkey) } - // Returns the hotkey take - // + /// Returns the hotkey take. + /// + /// # Arguments + /// * `hotkey` - The hotkey account ID. + /// + /// # Returns + /// The take value of the hotkey. pub fn get_hotkey_take(hotkey: &T::AccountId) -> u16 { Delegates::::get(hotkey) } - // Returns true if the hotkey account has been created. - // + /// Returns true if the hotkey account has been created. + /// + /// # Arguments + /// * `hotkey` - The hotkey account ID. + /// + /// # Returns + /// True if the hotkey account exists, false otherwise. pub fn hotkey_account_exists(hotkey: &T::AccountId) -> bool { Owner::::contains_key(hotkey) } - // Return true if the passed coldkey owns the hotkey. - // + /// Returns true if the passed coldkey owns the hotkey. + /// + /// # Arguments + /// * `coldkey` - The coldkey account ID. + /// * `hotkey` - The hotkey account ID. + /// + /// # Returns + /// True if the coldkey owns the hotkey, false otherwise. pub fn coldkey_owns_hotkey(coldkey: &T::AccountId, hotkey: &T::AccountId) -> bool { if Self::hotkey_account_exists(hotkey) { Owner::::get(hotkey) == *coldkey @@ -150,14 +171,24 @@ impl Pallet { } } - // Returns true if the cold-hot staking account has enough balance to fufil the decrement. - // + /// Returns true if the cold-hot staking account has enough balance to fulfill the decrement. + /// + /// # Arguments + /// * `coldkey` - The coldkey account ID. + /// * `hotkey` - The hotkey account ID. + /// * `decrement` - The amount to be decremented. + /// + /// # Returns + /// True if the account has enough balance, false otherwise. pub fn has_enough_stake(coldkey: &T::AccountId, hotkey: &T::AccountId, decrement: u64) -> bool { Self::get_stake_for_coldkey_and_hotkey(coldkey, hotkey) >= decrement } - // Increases the stake on the hotkey account under its owning coldkey. - // + /// Increases the stake on the hotkey account under its owning coldkey. + /// + /// # Arguments + /// * `hotkey` - The hotkey account ID. + /// * `increment` - The amount to be incremented. pub fn increase_stake_on_hotkey_account(hotkey: &T::AccountId, increment: u64) { Self::increase_stake_on_coldkey_hotkey_account( &Self::get_owning_coldkey_for_hotkey(hotkey), @@ -166,8 +197,11 @@ impl Pallet { ); } - // Decreases the stake on the hotkey account under its owning coldkey. - // + /// Decreases the stake on the hotkey account under its owning coldkey. + /// + /// # Arguments + /// * `hotkey` - The hotkey account ID. + /// * `decrement` - The amount to be decremented. pub fn decrease_stake_on_hotkey_account(hotkey: &T::AccountId, decrement: u64) { Self::decrease_stake_on_coldkey_hotkey_account( &Self::get_owning_coldkey_for_hotkey(hotkey), diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index d5140b656..7e215905a 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -1,14 +1,5 @@ use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; + impl Pallet { /// ---- The implementation for the extrinsic increase_take diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 59e96777b..e93d63f11 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,14 +1,5 @@ use super::*; -use frame_support::{ - storage::IterableStorageDoubleMap, - traits::{ - tokens::{ - fungible::{Balanced as _, Inspect as _, Mutate as _}, - Fortitude, Precision, Preservation, - }, - Imbalance, - }, -}; + impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. From 752f53c8c6400be9d28b0dc78e433255862e316e Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 14:27:27 -0500 Subject: [PATCH 013/269] merge clean --- pallets/subtensor/src/coinbase/mod.rs | 1 + pallets/subtensor/src/{ => coinbase}/root.rs | 0 pallets/subtensor/src/lib.rs | 17 ++++++----------- pallets/subtensor/src/subnets/mod.rs | 5 +++++ .../subtensor/src/{ => subnets}/registration.rs | 0 pallets/subtensor/src/{ => subnets}/serving.rs | 0 pallets/subtensor/src/{ => subnets}/uids.rs | 0 pallets/subtensor/src/{ => subnets}/weights.rs | 0 8 files changed, 12 insertions(+), 11 deletions(-) rename pallets/subtensor/src/{ => coinbase}/root.rs (100%) create mode 100644 pallets/subtensor/src/subnets/mod.rs rename pallets/subtensor/src/{ => subnets}/registration.rs (100%) rename pallets/subtensor/src/{ => subnets}/serving.rs (100%) rename pallets/subtensor/src/{ => subnets}/uids.rs (100%) rename pallets/subtensor/src/{ => subnets}/weights.rs (100%) diff --git a/pallets/subtensor/src/coinbase/mod.rs b/pallets/subtensor/src/coinbase/mod.rs index cc5b589f1..f8918ff1f 100644 --- a/pallets/subtensor/src/coinbase/mod.rs +++ b/pallets/subtensor/src/coinbase/mod.rs @@ -1,2 +1,3 @@ use super::*; pub mod block_step; +pub mod root; diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/coinbase/root.rs similarity index 100% rename from pallets/subtensor/src/root.rs rename to pallets/subtensor/src/coinbase/root.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 23adeee02..e59f5a987 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -35,24 +35,19 @@ mod benchmarks; // ========================= // ==== Pallet Imports ===== // ========================= -mod coinbase; +pub mod coinbase; pub mod epoch; -mod macros; -mod rpc_info; +pub mod macros; +pub mod rpc_info; pub mod staking; pub mod swap; +pub mod subnets; +pub mod utils; +pub mod migrations; use macros::{config, dispatches, errors, events, genesis, hooks}; -mod registration; -mod root; -mod serving; -mod uids; -mod utils; -mod weights; - // apparently this is stabilized since rust 1.36 extern crate alloc; -pub mod migrations; #[deny(missing_docs)] #[import_section(errors::errors)] diff --git a/pallets/subtensor/src/subnets/mod.rs b/pallets/subtensor/src/subnets/mod.rs new file mode 100644 index 000000000..1738b7401 --- /dev/null +++ b/pallets/subtensor/src/subnets/mod.rs @@ -0,0 +1,5 @@ +use super::*; +pub mod registration; +pub mod weights; +pub mod serving; +pub mod uids; diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/subnets/registration.rs similarity index 100% rename from pallets/subtensor/src/registration.rs rename to pallets/subtensor/src/subnets/registration.rs diff --git a/pallets/subtensor/src/serving.rs b/pallets/subtensor/src/subnets/serving.rs similarity index 100% rename from pallets/subtensor/src/serving.rs rename to pallets/subtensor/src/subnets/serving.rs diff --git a/pallets/subtensor/src/uids.rs b/pallets/subtensor/src/subnets/uids.rs similarity index 100% rename from pallets/subtensor/src/uids.rs rename to pallets/subtensor/src/subnets/uids.rs diff --git a/pallets/subtensor/src/weights.rs b/pallets/subtensor/src/subnets/weights.rs similarity index 100% rename from pallets/subtensor/src/weights.rs rename to pallets/subtensor/src/subnets/weights.rs From ad694b0e9dec8c0d087b2d96a55c8887aab43b1a Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 15:31:32 -0500 Subject: [PATCH 014/269] refactor everything to make it cleaner --- pallets/subtensor/src/coinbase/root.rs | 7 +++++-- pallets/subtensor/src/macros/genesis.rs | 5 +++-- .../src/migrations/migrate_create_root_network.rs | 3 ++- .../subtensor/src/migrations/migrate_delete_subnet_21.rs | 2 +- .../subtensor/src/migrations/migrate_delete_subnet_3.rs | 2 +- pallets/subtensor/tests/root.rs | 5 +++++ 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 948c91f20..d1b4bf47a 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -391,6 +391,9 @@ impl Pallet { // --- 9. Calculates the trust of networks. Trust is a sum of all stake with weights > 0. // Trust will have shape k, a score for each subnet. + log::debug!("Subnets:\n{:?}\n", Self::get_all_subnet_netuids()); + log::debug!("N Subnets:\n{:?}\n", Self::get_num_subnets()); + let total_networks = Self::get_num_subnets(); let mut trust = vec![I64F64::from_num(0); total_networks as usize]; let mut total_stake: I64F64 = I64F64::from_num(0); @@ -1031,7 +1034,7 @@ impl Pallet { NetworkModality::::insert(netuid, 0); // --- 5. Increase total network count. - TotalNetworks::::mutate(|n| n.saturating_inc()); + TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); // --- 6. Set all default values **explicitly**. Self::set_network_registration_allowed(netuid, true); @@ -1123,7 +1126,7 @@ impl Pallet { NetworksAdded::::remove(netuid); // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| n.saturating_dec()); + TotalNetworks::::mutate(|n| *n = n.saturating_sub(1)); // --- 7. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/macros/genesis.rs b/pallets/subtensor/src/macros/genesis.rs index 34a888f28..7d3768a81 100644 --- a/pallets/subtensor/src/macros/genesis.rs +++ b/pallets/subtensor/src/macros/genesis.rs @@ -124,7 +124,7 @@ mod genesis { SubnetworkN::::insert(netuid, next_uid); // --- Increase total network count. - TotalNetworks::::mutate(|n| n.saturating_add( 1 )); + TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); // Get the root network uid. let root_netuid: u16 = 0; @@ -133,7 +133,8 @@ mod genesis { NetworksAdded::::insert(root_netuid, true); // Increment the number of total networks. - TotalNetworks::::mutate(|n| n.saturating_add(1)); + TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); + // Set the number of validators to 1. SubnetworkN::::insert(root_netuid, 0); diff --git a/pallets/subtensor/src/migrations/migrate_create_root_network.rs b/pallets/subtensor/src/migrations/migrate_create_root_network.rs index fa9f34301..c413d1f07 100644 --- a/pallets/subtensor/src/migrations/migrate_create_root_network.rs +++ b/pallets/subtensor/src/migrations/migrate_create_root_network.rs @@ -55,7 +55,7 @@ pub fn migrate_create_root_network() -> Weight { NetworksAdded::::insert(root_netuid, true); // Increment the total number of networks - TotalNetworks::::mutate(|n| n.saturating_add( 1 )); + TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); // Set the maximum number of UIDs to the number of senate members MaxAllowedUids::::insert(root_netuid, 64); @@ -95,5 +95,6 @@ pub fn migrate_create_root_network() -> Weight { weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } + log::info!("Migrated create root network"); weight } diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs index 011d79de1..c917c7cab 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_21.rs @@ -66,7 +66,7 @@ pub fn migrate_delete_subnet_21() -> Weight { NetworksAdded::::remove(netuid); // Decrement the network counter - TotalNetworks::::mutate(|n| n.saturating_sub( 1 )); + TotalNetworks::::mutate(|n| *n = n.saturating_sub(1)); // Remove network registration time NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs index a07434bcb..217696357 100644 --- a/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs +++ b/pallets/subtensor/src/migrations/migrate_delete_subnet_3.rs @@ -69,7 +69,7 @@ pub fn migrate_delete_subnet_3() -> Weight { NetworksAdded::::remove(netuid); // Decrement the network counter - TotalNetworks::::mutate(|n| n.saturating_sub(1)); + TotalNetworks::::mutate(|n| *n = n.saturating_sub(1)); // Remove network registration time NetworkRegisteredAt::::remove(netuid); diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index d4e8448a1..84df71d83 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -32,6 +32,7 @@ fn test_root_register_network_exist() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test root -- test_set_weights_not_root_error --exact --nocapture #[test] fn test_set_weights_not_root_error() { new_test_ext(0).execute_with(|| { @@ -60,6 +61,7 @@ fn test_set_weights_not_root_error() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test root -- test_root_register_normal_on_root_fails --exact --nocapture #[test] fn test_root_register_normal_on_root_fails() { new_test_ext(1).execute_with(|| { @@ -104,6 +106,7 @@ fn test_root_register_normal_on_root_fails() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test root -- test_root_register_stake_based_pruning_works --exact --nocapture #[test] fn test_root_register_stake_based_pruning_works() { new_test_ext(1).execute_with(|| { @@ -192,6 +195,7 @@ fn test_root_register_stake_based_pruning_works() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test root -- test_root_set_weights --exact --nocapture #[test] fn test_root_set_weights() { new_test_ext(1).execute_with(|| { @@ -334,6 +338,7 @@ fn test_root_set_weights() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test root -- test_root_set_weights --exact --nocapture #[test] fn test_root_set_weights_out_of_order_netuids() { new_test_ext(1).execute_with(|| { From 5e291123d727bcd75336777e00a14d29dfaa09ad Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 15:32:18 -0500 Subject: [PATCH 015/269] lint --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/subtensor/src/lib.rs | 4 ++-- pallets/subtensor/src/staking/add_stake.rs | 1 - pallets/subtensor/src/staking/become_delegate.rs | 1 - pallets/subtensor/src/staking/decrease_take.rs | 1 - pallets/subtensor/src/staking/increase_take.rs | 1 - pallets/subtensor/src/staking/remove_stake.rs | 1 - pallets/subtensor/src/subnets/mod.rs | 2 +- 8 files changed, 4 insertions(+), 9 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index d1b4bf47a..974931e8f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -1034,7 +1034,7 @@ impl Pallet { NetworkModality::::insert(netuid, 0); // --- 5. Increase total network count. - TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); + TotalNetworks::::mutate(|n| *n = n.saturating_add(1)); // --- 6. Set all default values **explicitly**. Self::set_network_registration_allowed(netuid, true); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index e59f5a987..c28da051d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -38,12 +38,12 @@ mod benchmarks; pub mod coinbase; pub mod epoch; pub mod macros; +pub mod migrations; pub mod rpc_info; pub mod staking; -pub mod swap; pub mod subnets; +pub mod swap; pub mod utils; -pub mod migrations; use macros::{config, dispatches, errors, events, genesis, hooks}; // apparently this is stabilized since rust 1.36 diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 422dfa2e4..eb7762bec 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,6 +1,5 @@ use super::*; - impl Pallet { /// ---- The implementation for the extrinsic add_stake: Adds stake to a hotkey account. /// diff --git a/pallets/subtensor/src/staking/become_delegate.rs b/pallets/subtensor/src/staking/become_delegate.rs index 4df8adf2d..064f47c12 100644 --- a/pallets/subtensor/src/staking/become_delegate.rs +++ b/pallets/subtensor/src/staking/become_delegate.rs @@ -1,6 +1,5 @@ use super::*; - impl Pallet { /// ---- The implementation for the extrinsic become_delegate: signals that this hotkey allows delegated stake. /// diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index 6aac33651..9e48bac91 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -1,6 +1,5 @@ use super::*; - impl Pallet { /// ---- The implementation for the extrinsic decrease_take /// diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index 7e215905a..aa6dd443c 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -1,6 +1,5 @@ use super::*; - impl Pallet { /// ---- The implementation for the extrinsic increase_take /// diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index e93d63f11..a6f3db08d 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,6 +1,5 @@ use super::*; - impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. /// diff --git a/pallets/subtensor/src/subnets/mod.rs b/pallets/subtensor/src/subnets/mod.rs index 1738b7401..43bdfec43 100644 --- a/pallets/subtensor/src/subnets/mod.rs +++ b/pallets/subtensor/src/subnets/mod.rs @@ -1,5 +1,5 @@ use super::*; pub mod registration; -pub mod weights; pub mod serving; pub mod uids; +pub mod weights; From 8941385a5f4bac21ddc6b3c91f80d75bb82529bd Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 16:24:49 -0500 Subject: [PATCH 016/269] add set sudo hotkey drain tempo --- pallets/admin-utils/src/benchmarking.rs | 8 + pallets/admin-utils/src/lib.rs | 30 + pallets/admin-utils/src/weights.rs | 11 + pallets/admin-utils/tests/mock.rs | 3 + pallets/subtensor/src/coinbase/block_step.rs | 333 +---- pallets/subtensor/src/coinbase/mod.rs | 1 + .../subtensor/src/coinbase/run_coinbase.rs | 373 +++++ pallets/subtensor/src/epoch/run_epoch.rs | 99 +- pallets/subtensor/src/lib.rs | 3 + pallets/subtensor/src/macros/dispatches.rs | 59 + pallets/subtensor/src/macros/errors.rs | 8 + pallets/subtensor/src/macros/events.rs | 1 + pallets/subtensor/src/rpc_info/neuron_info.rs | 12 +- pallets/subtensor/src/staking/mod.rs | 1 + pallets/subtensor/src/staking/set_children.rs | 185 +++ pallets/subtensor/src/subnets/registration.rs | 11 + pallets/subtensor/src/utils.rs | 16 + pallets/subtensor/tests/children.rs | 1261 +++++++++++++++++ pallets/subtensor/tests/coinbase.rs | 154 ++ runtime/src/lib.rs | 4 + 20 files changed, 2232 insertions(+), 341 deletions(-) create mode 100644 pallets/subtensor/src/coinbase/run_coinbase.rs create mode 100644 pallets/subtensor/src/staking/set_children.rs create mode 100644 pallets/subtensor/tests/children.rs create mode 100644 pallets/subtensor/tests/coinbase.rs diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 0158311f7..3165e907f 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -240,5 +240,13 @@ mod benchmarks { _(RawOrigin::Root, 1u16/*netuid*/, true/*enabled*/)/*set_commit_reveal_weights_enabled*/; } + #[benchmark] + fn sudo_set_hotkey_emission_tempo() { + T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + + #[extrinsic_call] + _(RawOrigin::Root, 1u64/*emission_tempo*/)/*set_hotkey_emission_tempo*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 9a8744dc6..2aa45cbc9 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1035,6 +1035,35 @@ pub mod pallet { T::Subtensor::ensure_subnet_owner_or_root(origin.clone(), netuid)?; T::Subtensor::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) } + + /// Sets the hotkey emission tempo. + /// + /// This extrinsic allows the root account to set the hotkey emission tempo, which determines + /// the number of blocks before a hotkey drains accumulated emissions through to nominator staking accounts. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `emission_tempo` - The new emission tempo value to set. + /// + /// # Emits + /// * `Event::HotkeyEmissionTempoSet` - When the hotkey emission tempo is successfully set. + /// + /// # Errors + /// * `DispatchError::BadOrigin` - If the origin is not the root account. + #[pallet::call_index(52)] + #[pallet::weight(T::WeightInfo::sudo_set_hotkey_emission_tempo())] + pub fn sudo_set_hotkey_emission_tempo( + origin: OriginFor, + emission_tempo: u64, + ) -> DispatchResult { + ensure_root(origin)?; + T::Subtensor::set_hotkey_emission_tempo(emission_tempo); + log::info!( + "HotkeyEmissionTempoSet( emission_tempo: {:?} )", + emission_tempo + ); + Ok(()) + } } } @@ -1137,4 +1166,5 @@ pub trait SubtensorInterface { alpha_low: u16, alpha_high: u16, ) -> Result<(), DispatchError>; + fn set_hotkey_emission_tempo(emission_tempo: u64); } diff --git a/pallets/admin-utils/src/weights.rs b/pallets/admin-utils/src/weights.rs index ace123b14..0c0259ef2 100644 --- a/pallets/admin-utils/src/weights.rs +++ b/pallets/admin-utils/src/weights.rs @@ -62,6 +62,7 @@ pub trait WeightInfo { fn sudo_set_tempo() -> Weight; fn sudo_set_commit_reveal_weights_interval() -> Weight; fn sudo_set_commit_reveal_weights_enabled() -> Weight; + fn sudo_set_hotkey_emission_tempo() -> Weight; } @@ -806,4 +807,14 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `SubtensorModule::HotkeyEmissionTempo` (r:0 w:1) + /// Proof: `SubtensorModule::HotkeyEmissionTempo` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn sudo_set_hotkey_emission_tempo() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } \ No newline at end of file diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index dee8e742f..c1f55fece 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -475,6 +475,9 @@ impl pallet_admin_utils::SubtensorInterface f fn set_liquid_alpha_enabled(netuid: u16, enabled: bool) { SubtensorModule::set_liquid_alpha_enabled(netuid, enabled); } + fn set_hotkey_emission_tempo(emission_tempo: u64) { + SubtensorModule::set_hotkey_emission_tempo(emission_tempo) + } fn do_set_alpha_values( origin: RuntimeOrigin, netuid: u16, diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index 784332e4e..f4df51456 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -13,341 +13,12 @@ impl Pallet { log::debug!("block_step for block: {:?} ", block_number); // --- 1. Adjust difficulties. Self::adjust_registration_terms_for_networks(); - // --- 2. Calculate per-subnet emissions - match Self::root_epoch(block_number) { - Ok(_) => (), - Err(e) => { - log::trace!("Error while running root epoch: {:?}", e); - } - } - // --- 3. Drains emission tuples ( hotkey, amount ). - Self::drain_emission(block_number); - // --- 4. Generates emission tuples from epoch functions. - Self::generate_emission(block_number); + // --- 2. Run emission through network. + Self::run_coinbase(); // Return ok. Ok(()) } - #[allow(clippy::arithmetic_side_effects)] - /// Helper function which returns the number of blocks remaining before we will run the epoch on this - /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 - /// - pub fn blocks_until_next_epoch(netuid: u16, tempo: u16, block_number: u64) -> u64 { - // tempo | netuid | # first epoch block - // 1 0 0 - // 1 1 1 - // 2 0 1 - // 2 1 0 - // 100 0 99 - // 100 1 98 - // Special case: tempo = 0, the network never runs. - if tempo == 0 { - return 1000; - } - (tempo as u64).saturating_sub( - block_number.saturating_add(netuid as u64).saturating_add(1) - % (tempo as u64).saturating_add(1), - ) - } - - #[allow(clippy::arithmetic_side_effects)] - /// Helper function returns the number of tuples to drain on a particular step based on - /// the remaining tuples to sink and the block number - /// - pub fn tuples_to_drain_this_block( - netuid: u16, - tempo: u16, - block_number: u64, - n_remaining: usize, - ) -> usize { - let blocks_until_epoch: u64 = Self::blocks_until_next_epoch(netuid, tempo, block_number); - if blocks_until_epoch.saturating_div(2) == 0 { - return n_remaining; - } // drain all. - if tempo.saturating_div(2) == 0 { - return n_remaining; - } // drain all - if n_remaining == 0 { - return 0; - } // nothing to drain at all. - // Else return enough tuples to drain all within half the epoch length. - let to_sink_via_tempo: usize = - n_remaining.saturating_div((tempo as usize).saturating_div(2)); - let to_sink_via_blocks_until_epoch: usize = - n_remaining.saturating_div((blocks_until_epoch as usize).saturating_div(2)); - if to_sink_via_tempo > to_sink_via_blocks_until_epoch { - to_sink_via_tempo - } else { - to_sink_via_blocks_until_epoch - } - } - - pub fn get_loaded_emission_tuples(netuid: u16) -> Option> { - LoadedEmission::::get(netuid) - } - - /// Reads from the loaded emission storage which contains lists of pending emission tuples ( hotkey, amount ) - /// and distributes small chunks of them at a time. - /// - pub fn drain_emission(_: u64) { - // --- 1. We iterate across each network. - for (netuid, _) in as IterableStorageMap>::iter() { - let Some(tuples_to_drain) = Self::get_loaded_emission_tuples(netuid) else { - // There are no tuples to emit. - continue; - }; - let mut total_emitted: u64 = 0; - for (hotkey, server_amount, validator_amount) in tuples_to_drain.iter() { - Self::emit_inflation_through_hotkey_account( - hotkey, - *server_amount, - *validator_amount, - ); - total_emitted.saturating_accrue((*server_amount).saturating_add(*validator_amount)); - } - LoadedEmission::::remove(netuid); - TotalIssuance::::put(TotalIssuance::::get().saturating_add(total_emitted)); - } - } - - /// Iterates through networks queues more emission onto their pending storage. - /// If a network has no blocks left until tempo, we run the epoch function and generate - /// more token emission tuples for later draining onto accounts. - /// - pub fn generate_emission(block_number: u64) { - // --- 1. Iterate across each network and add pending emission into stash. - for (netuid, tempo) in as IterableStorageMap>::iter() { - // Skip the root network or subnets with registrations turned off - if netuid == Self::get_root_netuid() { - // Root emission or subnet emission is burned - continue; - } - - // --- 2. Queue the emission due to this network. - let mut new_queued_emission: u64 = Self::get_subnet_emission_value(netuid); - if !Self::is_registration_allowed(netuid) { - new_queued_emission = 0; // No emission for this network if registration is off. - } - - log::debug!( - "generate_emission for netuid: {:?} with tempo: {:?} and emission: {:?}", - netuid, - tempo, - new_queued_emission, - ); - - let subnet_has_owner = SubnetOwner::::contains_key(netuid); - let mut remaining = I96F32::from_num(new_queued_emission); - if subnet_has_owner { - let cut = remaining - .saturating_mul(I96F32::from_num(Self::get_subnet_owner_cut())) - .saturating_div(I96F32::from_num(u16::MAX)); - - remaining = remaining.saturating_sub(cut); - - Self::add_balance_to_coldkey_account( - &Self::get_subnet_owner(netuid), - cut.to_num::(), - ); - - // We are creating tokens here from the coinbase. - Self::coinbase(cut.to_num::()); - } - // --- 5. Add remaining amount to the network's pending emission. - PendingEmission::::mutate(netuid, |queued| { - queued.saturating_accrue(remaining.to_num::()) - }); - log::debug!( - "netuid_i: {:?} queued_emission: +{:?} ", - netuid, - new_queued_emission - ); - - // --- 6. Check to see if this network has reached tempo. - if Self::blocks_until_next_epoch(netuid, tempo, block_number) != 0 { - // --- 3.1 No epoch, increase blocks since last step and continue, - Self::set_blocks_since_last_step( - netuid, - Self::get_blocks_since_last_step(netuid).saturating_add(1), - ); - continue; - } - - // --- 7 This network is at tempo and we are running its epoch. - // First drain the queued emission. - let emission_to_drain: u64 = PendingEmission::::get(netuid); - PendingEmission::::insert(netuid, 0); - - // --- 8. Run the epoch mechanism and return emission tuples for hotkeys in the network. - let emission_tuples_this_block: Vec<(T::AccountId, u64, u64)> = - Self::epoch(netuid, emission_to_drain); - log::debug!( - "netuid_i: {:?} emission_to_drain: {:?} ", - netuid, - emission_to_drain - ); - - // --- 9. Check that the emission does not exceed the allowed total. - let emission_sum: u128 = emission_tuples_this_block - .iter() - .map(|(_account_id, ve, se)| (*ve as u128).saturating_add(*se as u128)) - .sum(); - if emission_sum > emission_to_drain as u128 { - continue; - } // Saftey check. - - // --- 10. Sink the emission tuples onto the already loaded. - let mut concat_emission_tuples: Vec<(T::AccountId, u64, u64)> = - emission_tuples_this_block.clone(); - if let Some(mut current_emission_tuples) = Self::get_loaded_emission_tuples(netuid) { - // 10.a We already have loaded emission tuples, so we concat the new ones. - concat_emission_tuples.append(&mut current_emission_tuples); - } - LoadedEmission::::insert(netuid, concat_emission_tuples); - - // --- 11 Set counters. - Self::set_blocks_since_last_step(netuid, 0); - Self::set_last_mechanism_step_block(netuid, block_number); - } - } - /// Distributes token inflation through the hotkey based on emission. The call ensures that the inflation - /// is distributed onto the accounts in proportion of the stake delegated minus the take. This function - /// is called after an epoch to distribute the newly minted stake according to delegation. - /// - pub fn emit_inflation_through_hotkey_account( - hotkey: &T::AccountId, - server_emission: u64, - validator_emission: u64, - ) { - // --- 1. Check if the hotkey is a delegate. If not, we simply pass the stake through to the - // coldkey - hotkey account as normal. - if !Self::hotkey_is_delegate(hotkey) { - Self::increase_stake_on_hotkey_account( - hotkey, - server_emission.saturating_add(validator_emission), - ); - return; - } - // Then this is a delegate, we distribute validator_emission, then server_emission. - - // --- 2. The hotkey is a delegate. We first distribute a proportion of the validator_emission to the hotkey - // directly as a function of its 'take' - let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); - let delegate_take: u64 = - Self::calculate_delegate_proportional_take(hotkey, validator_emission); - let validator_emission_minus_take: u64 = validator_emission.saturating_sub(delegate_take); - let mut remaining_validator_emission: u64 = validator_emission_minus_take; - - // 3. -- The remaining emission goes to the owners in proportion to the stake delegated. - for (owning_coldkey_i, stake_i) in - as IterableStorageDoubleMap>::iter_prefix( - hotkey, - ) - { - // --- 4. The emission proportion is remaining_emission * ( stake / total_stake ). - let stake_proportion: u64 = Self::calculate_stake_proportional_emission( - stake_i, - total_hotkey_stake, - validator_emission_minus_take, - ); - Self::increase_stake_on_coldkey_hotkey_account( - &owning_coldkey_i, - hotkey, - stake_proportion, - ); - log::debug!( - "owning_coldkey_i: {:?} hotkey: {:?} emission: +{:?} ", - owning_coldkey_i, - hotkey, - stake_proportion - ); - remaining_validator_emission.saturating_reduce(stake_proportion); - } - - // --- 5. Last increase final account balance of delegate after 4, since 5 will change the stake proportion of - // the delegate and effect calculation in 4. - Self::increase_stake_on_hotkey_account( - hotkey, - delegate_take.saturating_add(remaining_validator_emission), - ); - log::debug!("delkey: {:?} delegate_take: +{:?} ", hotkey, delegate_take); - // Also emit the server_emission to the hotkey - // The server emission is distributed in-full to the delegate owner. - // We do this after 4. for the same reason as above. - Self::increase_stake_on_hotkey_account(hotkey, server_emission); - } - - /// Increases the stake on the cold - hot pairing by increment while also incrementing other counters. - /// This function should be called rather than set_stake under account. - /// - pub fn block_step_increase_stake_on_coldkey_hotkey_account( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - increment: u64, - ) { - TotalColdkeyStake::::mutate(coldkey, |old| old.saturating_add(increment)); - TotalHotkeyStake::::insert( - hotkey, - TotalHotkeyStake::::get(hotkey).saturating_add(increment), - ); - Stake::::insert( - hotkey, - coldkey, - Stake::::get(hotkey, coldkey).saturating_add(increment), - ); - TotalStake::::put(TotalStake::::get().saturating_add(increment)); - } - - /// Decreases the stake on the cold - hot pairing by the decrement while decreasing other counters. - /// - pub fn block_step_decrease_stake_on_coldkey_hotkey_account( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - decrement: u64, - ) { - TotalColdkeyStake::::mutate(coldkey, |old| old.saturating_sub(decrement)); - TotalHotkeyStake::::insert( - hotkey, - TotalHotkeyStake::::get(hotkey).saturating_sub(decrement), - ); - Stake::::insert( - hotkey, - coldkey, - Stake::::get(hotkey, coldkey).saturating_sub(decrement), - ); - TotalStake::::put(TotalStake::::get().saturating_sub(decrement)); - } - - /// Returns emission awarded to a hotkey as a function of its proportion of the total stake. - /// - pub fn calculate_stake_proportional_emission( - stake: u64, - total_stake: u64, - emission: u64, - ) -> u64 { - if total_stake == 0 { - return 0; - }; - let stake_proportion: I64F64 = - I64F64::from_num(stake).saturating_div(I64F64::from_num(total_stake)); - let proportional_emission: I64F64 = - I64F64::from_num(emission).saturating_mul(stake_proportion); - proportional_emission.to_num::() - } - - /// Returns the delegated stake 'take' assigned to this key. (If exists, otherwise 0) - /// - pub fn calculate_delegate_proportional_take(hotkey: &T::AccountId, emission: u64) -> u64 { - if Self::hotkey_is_delegate(hotkey) { - let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) - .saturating_div(I64F64::from_num(u16::MAX)); - let take_emission: I64F64 = take_proportion.saturating_mul(I64F64::from_num(emission)); - take_emission.to_num::() - } else { - 0 - } - } - /// Adjusts the network difficulties/burns of every active network. Resetting state parameters. /// pub fn adjust_registration_terms_for_networks() { diff --git a/pallets/subtensor/src/coinbase/mod.rs b/pallets/subtensor/src/coinbase/mod.rs index f8918ff1f..ec989d258 100644 --- a/pallets/subtensor/src/coinbase/mod.rs +++ b/pallets/subtensor/src/coinbase/mod.rs @@ -1,3 +1,4 @@ use super::*; pub mod block_step; pub mod root; +pub mod run_coinbase; diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs new file mode 100644 index 000000000..7dd829853 --- /dev/null +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -0,0 +1,373 @@ +use super::*; +use frame_support::storage::IterableStorageDoubleMap; +use frame_support::storage::IterableStorageMap; +use sp_runtime::Saturating; +use substrate_fixed::types::I110F18; +use substrate_fixed::types::I64F64; +use substrate_fixed::types::I96F32; + +impl Pallet { + /// The `coinbase` function performs a four-part emission distribution process involving + /// subnets, epochs, hotkeys, and nominators. + // It is divided into several steps, each handling a specific part of the distribution: + + // Step 1: Compute the block-wise emission for each subnet. + // This involves calculating how much (TAO) should be emitted into each subnet using the + // root epoch function. + + // Step 2: Accumulate the subnet block emission. + // After calculating the block-wise emission, these values are accumulated to keep track + // of how much each subnet should emit before the next distribution phase. This accumulation + // is a running total that gets updated each block. + + // Step 3: Distribute the accumulated emissions through epochs. + // Subnets periodically distribute their accumulated emissions to hotkeys (active validators/miners) + // in the network on a `tempo` --- the time between epochs. This step runs Yuma consensus to + // determine how emissions are split among hotkeys based on their contributions and roles. + // The accumulation of hotkey emissions is done through the `accumulate_hotkey_emission` function. + // The function splits the rewards for a hotkey amongst itself and its `parents`. The parents are + // the hotkeys that are delegating their stake to the hotkey. + + // Step 4: Further distribute emissions from hotkeys to nominators. + // Finally, the emissions received by hotkeys are further distributed to their nominators, + // who are stakeholders that support the hotkeys. + pub fn run_coinbase() { + // --- 0. Get current block. + let current_block: u64 = Self::get_current_block_as_u64(); + log::debug!("Current block: {:?}", current_block); + + // --- 1. Get all netuids. + let subnets: Vec = Self::get_all_subnet_netuids(); + log::debug!("All subnet netuids: {:?}", subnets); + + // --- 2. Run the root epoch function which computes the block emission for each subnet. + // coinbase --> root() --> subnet_block_emission + match Self::root_epoch(current_block) { + Ok(_) => log::debug!("Root epoch run successfully for block: {:?}", current_block), + Err(e) => { + log::trace!("Did not run epoch with: {:?}", e); + } + } + + // --- 3. Drain the subnet block emission and accumulate it as subnet emission, which increases until the tempo is reached in #4. + // subnet_blockwise_emission -> subnet_pending_emission + for netuid in subnets.clone().iter() { + // --- 3.1 Get the network's block-wise emission amount. + // This value is newly minted TAO which has not reached staking accounts yet. + let subnet_blockwise_emission: u64 = EmissionValues::::get(*netuid); + log::debug!( + "Subnet block-wise emission for netuid {:?}: {:?}", + *netuid, + subnet_blockwise_emission + ); + + // --- 3.2 Accumulate the subnet emission on the subnet. + PendingEmission::::mutate(*netuid, |subnet_emission| { + *subnet_emission = subnet_emission.saturating_add(subnet_blockwise_emission); + log::debug!( + "Updated subnet emission for netuid {:?}: {:?}", + *netuid, + *subnet_emission + ); + }); + } + + // --- 4. Drain the accumulated subnet emissions, pass them through the epoch(). + // Before accumulating on the hotkeys the function redistributes the emission towards hotkey parents. + // subnet_emission --> epoch() --> hotkey_emission --> (hotkey + parent hotkeys) + for netuid in subnets.clone().iter() { + // --- 4.1 Check to see if the subnet should run its epoch. + if Self::should_run_epoch(*netuid, current_block) { + // --- 4.2 Drain the subnet emission. + let mut subnet_emission: u64 = PendingEmission::::get(*netuid); + PendingEmission::::insert(*netuid, 0); + log::debug!( + "Drained subnet emission for netuid {:?}: {:?}", + *netuid, + subnet_emission + ); + + // --- 4.3 Set last step counter. + Self::set_blocks_since_last_step(*netuid, 0); + Self::set_last_mechanism_step_block(*netuid, current_block); + + // --- 4.4 Distribute owner take. + if SubnetOwner::::contains_key(netuid) { + // Does the subnet have an owner? + + // --- 4.4.1 Compute the subnet owner cut. + let owner_cut: I96F32 = I96F32::from_num(subnet_emission).saturating_mul( + I96F32::from_num(Self::get_subnet_owner_cut()) + .saturating_div(I96F32::from_num(u16::MAX)), + ); + + // --- 4.4.2 Remove the cut from the subnet emission + subnet_emission = subnet_emission.saturating_sub(owner_cut.to_num::()); + + // --- 4.4.3 Add the cut to the balance of the owner + Self::add_balance_to_coldkey_account( + &Self::get_subnet_owner(*netuid), + owner_cut.to_num::(), + ); + + // --- 4.4.4 Increase total issuance on the chain. + Self::coinbase(owner_cut.to_num::()); + } + + // 4.3 Pass emission through epoch() --> hotkey emission. + let hotkey_emission: Vec<(T::AccountId, u64, u64)> = + Self::epoch(*netuid, subnet_emission); + log::debug!( + "Hotkey emission results for netuid {:?}: {:?}", + *netuid, + hotkey_emission + ); + + // 4.4 Accumulate the tuples on hotkeys: + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + // 4.5 Accumulate the emission on the hotkey and parent hotkeys. + Self::accumulate_hotkey_emission( + &hotkey, + *netuid, + validator_emission, // Amount received from validating + mining_emission, // Amount recieved from mining. + ); + log::debug!("Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", hotkey, *netuid, mining_emission, validator_emission); + } + } else { + log::debug!("Tempo not reached for subnet: {:?}", *netuid); + } + } + + // --- 5. Drain the accumulated hotkey emissions through to the nominators. + // The hotkey takes a proportion of the emission, the remainder is drained through to the nominators. + // We keep track of the last stake increase event for accounting purposes. + // hotkeys --> nominators. + let emission_tempo: u64 = Self::get_hotkey_emission_tempo(); + for (hotkey, hotkey_emission) in PendingdHotkeyEmission::::iter() { + // Check for zeros. + // remove zero values. + if hotkey_emission == 0 { + continue; + } + + // --- 5.1 Check if we should drain the hotkey emission on this block. + if Self::should_drain_hotkey(&hotkey, current_block, emission_tempo) { + // --- 5.2 Drain the hotkey emission and distribute it to nominators. + let total_new_tao: u64 = + Self::drain_hotkey_emission(&hotkey, hotkey_emission, current_block); + log::debug!( + "Drained hotkey emission for hotkey {:?} on block {:?}: {:?}", + hotkey, + current_block, + hotkey_emission + ); + + // --- 5.3 Increase total issuance on the chain. + Self::coinbase(total_new_tao); + log::debug!("Increased total issuance by {:?}", total_new_tao); + } + } + } + + /// Accumulates the mining and validator emissions on a hotkey and distributes the validator emission among its parents. + /// + /// This function is responsible for accumulating the mining and validator emissions associated with a hotkey onto a hotkey. + /// It first calculates the total stake of the hotkey, considering the stakes contributed by its parents and reduced by its children. + /// It then retrieves the list of parents of the hotkey and distributes the validator emission proportionally based on the stake contributed by each parent. + /// The remaining validator emission, after distribution to the parents, along with the mining emission, is then added to the hotkey's own accumulated emission. + /// + /// # Arguments + /// * `hotkey` - The account ID of the hotkey for which emissions are being calculated. + /// * `netuid` - The unique identifier of the network to which the hotkey belongs. + /// * `mining_emission` - The amount of mining emission allocated to the hotkey. + /// * `validator_emission` - The amount of validator emission allocated to the hotkey. + /// + pub fn accumulate_hotkey_emission( + hotkey: &T::AccountId, + netuid: u16, + validating_emission: u64, + mining_emission: u64, + ) { + // --- 1. First, calculate the hotkey's share of the emission. + let take_proportion: I64F64 = + I64F64::from_num(Delegates::::get(hotkey)).saturating_div(I64F64::from_num(u16::MAX)); + let hotkey_take: u64 = take_proportion + .saturating_mul(I64F64::from_num(validating_emission)) + .to_num::(); + // NOTE: Only the validation emission should be split amongst parents. + + // --- 2. Compute the remaining emission after the hotkey's share is deducted. + let emission_minus_take: u64 = validating_emission.saturating_sub(hotkey_take); + + // --- 3. Track the remaining emission for accounting purposes. + let mut remaining_emission: u64 = emission_minus_take; + + // --- 4. Calculate the total stake of the hotkey, adjusted by the stakes of parents and children. + // Parents contribute to the stake, while children reduce it. + // If this value is zero, no distribution to anyone is necessary. + let total_hotkey_stake: u64 = Self::get_stake_for_hotkey_on_subnet(hotkey, netuid); + if total_hotkey_stake != 0 { + // --- 5. If the total stake is not zero, iterate over each parent to determine their contribution to the hotkey's stake, + // and calculate their share of the emission accordingly. + for (proportion, parent) in Self::get_parents(hotkey, netuid) { + // --- 5.1 Retrieve the parent's stake. This is the raw stake value including nominators. + let parent_stake: u64 = Self::get_total_stake_for_hotkey(&parent); + + // --- 5.2 Calculate the portion of the hotkey's total stake contributed by this parent. + // Then, determine the parent's share of the remaining emission. + let stake_from_parent: I96F32 = I96F32::from_num(parent_stake).saturating_mul( + I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)), + ); + let proportion_from_parent: I96F32 = + stake_from_parent.saturating_div(I96F32::from_num(total_hotkey_stake)); + let parent_emission_take: u64 = proportion_from_parent + .saturating_mul(I96F32::from_num(emission_minus_take)) + .to_num::(); + + // --- 5.5. Accumulate emissions for the parent hotkey. + PendingdHotkeyEmission::::mutate(parent, |parent_accumulated| { + *parent_accumulated = parent_accumulated.saturating_add(parent_emission_take) + }); + + // --- 5.6. Subtract the parent's share from the remaining emission for this hotkey. + remaining_emission = remaining_emission.saturating_sub(parent_emission_take); + } + } + + // --- 6. Add the remaining emission plus the hotkey's initial take to the pending emission for this hotkey. + PendingdHotkeyEmission::::mutate(hotkey, |hotkey_pending| { + *hotkey_pending = hotkey_pending.saturating_add( + remaining_emission + .saturating_add(hotkey_take) + .saturating_add(mining_emission), + ) + }); + } + + //. --- 4. Drains the accumulated hotkey emission through to the nominators. The hotkey takes a proportion of the emission. + /// The remainder is drained through to the nominators keeping track of the last stake increase event to ensure that the hotkey does not + /// gain more emission than it's stake since the last drain. + /// hotkeys --> nominators. + /// + /// 1. It resets the accumulated emissions for the hotkey to zero. + /// 4. It calculates the total stake for the hotkey and determines the hotkey's own take from the emissions based on its delegation status. + /// 5. It then calculates the remaining emissions after the hotkey's take and distributes this remaining amount proportionally among the hotkey's nominators. + /// 6. Each nominator's share of the emissions is added to their stake, but only if their stake was not manually increased since the last emission drain. + /// 7. Finally, the hotkey's own take and any undistributed emissions are added to the hotkey's total stake. + /// + /// This function ensures that emissions are fairly distributed according to stake proportions and delegation agreements, and it updates the necessary records to reflect these changes. + pub fn drain_hotkey_emission(hotkey: &T::AccountId, emission: u64, block_number: u64) -> u64 { + // --- 0. For accounting purposes record the total new added stake. + let mut total_new_tao: u64 = 0; + + // --- 1.0 Drain the hotkey emission. + PendingdHotkeyEmission::::insert(hotkey, 0); + + // --- 2 Retrieve the last time this hotkey's emissions were drained. + let last_hotkey_emission_drain: u64 = LastHotkeyEmissionDrain::::get(hotkey); + + // --- 3 Update the block value to the current block number. + LastHotkeyEmissionDrain::::insert(hotkey, block_number); + + // --- 4 Retrieve the total stake for the hotkey from all nominations. + let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); + + // --- 5 Calculate the emission take for the hotkey. + let take_proportion: I64F64 = + I64F64::from_num(Delegates::::get(hotkey)).saturating_div(I64F64::from_num(u16::MAX)); + let hotkey_take: u64 = + (take_proportion.saturating_mul(I64F64::from_num(emission))).to_num::(); + + // --- 6 Compute the remaining emission after deducting the hotkey's take. + let emission_minus_take: u64 = emission.saturating_sub(hotkey_take); + + // --- 7 Calculate the remaining emission after the hotkey's take. + let mut remainder: u64 = emission_minus_take; + + // --- 8 Iterate over each nominator. + for (nominator, nominator_stake) in + as IterableStorageDoubleMap>::iter_prefix(hotkey) + { + // --- 9 Check if the stake was manually increased by the user since the last emission drain for this hotkey. + // If it was, skip this nominator as they will not receive their proportion of the emission. + if LastAddStakeIncrease::::get(hotkey, nominator.clone()) > last_hotkey_emission_drain { + continue; + } + + // --- 10 Calculate this nominator's share of the emission. + let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) + .saturating_mul(I64F64::from_num(nominator_stake)) + .saturating_div(I64F64::from_num(total_hotkey_stake)); + + // --- 11 Increase the stake for the nominator. + Self::increase_stake_on_coldkey_hotkey_account( + &nominator, + hotkey, + nominator_emission.to_num::(), + ); + + // --- 11* Record event and Subtract the nominator's emission from the remainder. + total_new_tao = total_new_tao.saturating_add(nominator_emission.to_num::()); + remainder = remainder.saturating_sub(nominator_emission.to_num::()); + } + + // --- 13 Finally, add the stake to the hotkey itself, including its take and the remaining emission. + let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder); + Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao); + + // --- 14 Record new tao creation event and return the amount created. + total_new_tao = total_new_tao.saturating_add(hotkey_new_tao); + total_new_tao + } + + /////////////// + /// Helpers /// + /////////////// + /// Determines whether the hotkey emission should be drained based on the current block and index. + /// + /// # Arguments + /// * `hotkey_i` - The hotkey identifier. + /// * `index` - The index of the hotkey in the iterable storage. + /// * `block` - The current block number. + /// + /// # Returns + /// * `bool` - True if the hotkey emission should be drained, false otherwise. + pub fn should_drain_hotkey(hotkey: &T::AccountId, block: u64, emit_tempo: u64) -> bool { + let hotkey_idx: u64 = Self::hash_hotkey_to_u64(hotkey); + block.rem_euclid(emit_tempo.saturating_add(1)) + == hotkey_idx.rem_euclid(emit_tempo.saturating_add(1)) + } + + /// Checks if the epoch should run for a given subnet based on the current block. + /// + /// # Arguments + /// * `netuid` - The unique identifier of the subnet. + /// + /// # Returns + /// * `bool` - True if the epoch should run, false otherwise. + pub fn should_run_epoch(netuid: u16, current_block: u64) -> bool { + Self::blocks_until_next_epoch(netuid, Self::get_tempo(netuid), current_block) == 0 + } + + /// Helper function which returns the number of blocks remaining before we will run the epoch on this + /// network. Networks run their epoch when (block_number + netuid + 1 ) % (tempo + 1) = 0 + /// tempo | netuid | # first epoch block + /// 1 0 0 + /// 1 1 1 + /// 2 0 1 + /// 2 1 0 + /// 100 0 99 + /// 100 1 98 + /// Special case: tempo = 0, the network never runs. + /// + pub fn blocks_until_next_epoch(netuid: u16, tempo: u16, block_number: u64) -> u64 { + if tempo == 0 { + return u64::MAX; + } + (tempo as u64).saturating_sub( + (block_number.saturating_add((netuid as u64).saturating_add(1))) + % (tempo as u64).saturating_add(1), + ) + } +} \ No newline at end of file diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index d67eeded0..1cb2c3448 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -5,6 +5,89 @@ use sp_std::vec; use substrate_fixed::types::{I32F32, I64F64, I96F32}; impl Pallet { + /// Calculates the total stake held by a hotkey on the network, considering child/parent relationships. + /// + /// This function performs the following steps: + /// 1. Checks for self-loops in the delegation graph. + /// 2. Retrieves the initial stake of the hotkey. + /// 3. Calculates the stake allocated to children. + /// 4. Calculates the stake received from parents. + /// 5. Computes the final stake by adjusting the initial stake with child and parent contributions. + /// + /// # Arguments + /// * `hotkey` - AccountId of the hotkey whose total network stake is to be calculated. + /// * `netuid` - Network unique identifier specifying the network context. + /// + /// # Returns + /// * `u64` - The total stake for the hotkey on the network after considering the stakes + /// from children and parents. + /// + /// # Note + /// This function now includes a check for self-loops in the delegation graph using the + /// `dfs_check_self_loops` method. However, it currently only logs warnings for detected loops + /// and does not alter the stake calculation based on these findings. + /// + /// # Panics + /// This function does not explicitly panic, but underlying arithmetic operations + /// use saturating arithmetic to prevent overflows. + /// + /// TODO: check for self loops. + /// TODO: (@distributedstatemachine): check if we should return error , otherwise self loop + /// detection is impossible to test. + pub fn get_stake_for_hotkey_on_subnet(hotkey: &T::AccountId, netuid: u16) -> u64 { + // Retrieve the initial total stake for the hotkey without any child/parent adjustments. + let initial_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); + let mut stake_to_children: u64 = 0; + let mut stake_from_parents: u64 = 0; + + // Retrieve lists of parents and children from storage, based on the hotkey and network ID. + let parents: Vec<(u64, T::AccountId)> = Self::get_parents(hotkey, netuid); + let children: Vec<(u64, T::AccountId)> = Self::get_children(hotkey, netuid); + + // Iterate over children to calculate the total stake allocated to them. + for (proportion, _) in children { + // Calculate the stake proportion allocated to the child based on the initial stake. + let normalized_proportion: I96F32 = + I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)); + let stake_proportion_to_child: I96F32 = + I96F32::from_num(initial_stake).saturating_mul(normalized_proportion); + + // Accumulate the total stake given to children. + stake_to_children = + stake_to_children.saturating_add(stake_proportion_to_child.to_num::()); + } + + // Iterate over parents to calculate the total stake received from them. + for (proportion, parent) in parents { + // Retrieve the parent's total stake. + let parent_stake: u64 = Self::get_total_stake_for_hotkey(&parent); + // Calculate the stake proportion received from the parent. + let normalized_proportion: I96F32 = + I96F32::from_num(proportion).saturating_div(I96F32::from_num(u64::MAX)); + let stake_proportion_from_parent: I96F32 = + I96F32::from_num(parent_stake).saturating_mul(normalized_proportion); + + // Accumulate the total stake received from parents. + stake_from_parents = + stake_from_parents.saturating_add(stake_proportion_from_parent.to_num::()); + } + + // Calculate the final stake for the hotkey by adjusting the initial stake with the stakes + // to/from children and parents. + let mut finalized_stake: u64 = initial_stake + .saturating_sub(stake_to_children) + .saturating_add(stake_from_parents); + + // get the max stake for the network + let max_stake = Self::get_network_max_stake(netuid); + + // Return the finalized stake value for the hotkey, but capped at the max stake. + finalized_stake = finalized_stake.min(max_stake); + + // Return the finalized stake value for the hotkey. + finalized_stake + } + /// Calculates reward consensus and returns the emissions for uids/hotkeys in a given `netuid`. /// (Dense version used only for testing purposes.) #[allow(clippy::indexing_slicing)] @@ -67,7 +150,8 @@ impl Pallet { // Access network stake as normalized vector. let mut stake_64: Vec = vec![I64F64::from_num(0.0); n as usize]; for (uid_i, hotkey) in &hotkeys { - stake_64[*uid_i as usize] = I64F64::from_num(Self::get_total_stake_for_hotkey(hotkey)); + stake_64[*uid_i as usize] = + I64F64::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); } inplace_normalize_64(&mut stake_64); let stake: Vec = vec_fixed64_to_fixed32(stake_64); @@ -265,6 +349,10 @@ impl Pallet { // == Value storage == // =================== let cloned_emission: Vec = combined_emission.clone(); + let cloned_stake_weight: Vec = stake + .iter() + .map(|xi| fixed_proportion_to_u16(*xi)) + .collect::>(); let cloned_ranks: Vec = ranks .iter() .map(|xi| fixed_proportion_to_u16(*xi)) @@ -290,6 +378,7 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); + StakeWeight::::insert(netuid, cloned_stake_weight.clone()); Active::::insert(netuid, active.clone()); Emission::::insert(netuid, cloned_emission); Rank::::insert(netuid, cloned_ranks); @@ -395,7 +484,8 @@ impl Pallet { // Access network stake as normalized vector. let mut stake_64: Vec = vec![I64F64::from_num(0.0); n as usize]; for (uid_i, hotkey) in &hotkeys { - stake_64[*uid_i as usize] = I64F64::from_num(Self::get_total_stake_for_hotkey(hotkey)); + stake_64[*uid_i as usize] = + I64F64::from_num(Self::get_stake_for_hotkey_on_subnet(hotkey, netuid)); } log::trace!("Stake : {:?}", &stake_64); inplace_normalize_64(&mut stake_64); @@ -631,6 +721,10 @@ impl Pallet { // =================== // == Value storage == // =================== + let cloned_stake_weight: Vec = stake + .iter() + .map(|xi| fixed_proportion_to_u16(*xi)) + .collect::>(); let cloned_emission: Vec = combined_emission.clone(); let cloned_ranks: Vec = ranks .iter() @@ -657,6 +751,7 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); + StakeWeight::::insert(netuid, cloned_stake_weight.clone()); Active::::insert(netuid, active.clone()); Emission::::insert(netuid, cloned_emission); Rank::::insert(netuid, cloned_ranks); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c28da051d..8d2b5c4fd 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -930,6 +930,9 @@ pub mod pallet { /// ======================================= /// ==== Subnetwork Consensus Storage ==== /// ======================================= + #[pallet::storage] // --- DMAP ( netuid ) --> stake_weight | weight for stake used in YC. + pub(super) type StakeWeight = + StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid pub type Uids = diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 228bec6e4..d8cb31166 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -842,5 +842,64 @@ mod dispatches { ) -> DispatchResult { Ok(()) } + + /// Set a single child for a given hotkey on a specified network. + /// + /// This function allows a coldkey to set a single child for a given hotkey on a specified network. + /// The proportion of the hotkey's stake to be allocated to the child is also specified. + /// + /// # Arguments: + /// * `origin` (::RuntimeOrigin): + /// - The signature of the calling coldkey. Setting a hotkey child can only be done by the coldkey. + /// + /// * `hotkey` (T::AccountId): + /// - The hotkey which will be assigned the child. + /// + /// * `child` (T::AccountId): + /// - The child which will be assigned to the hotkey. + /// + /// * `netuid` (u16): + /// - The u16 network identifier where the childkey will exist. + /// + /// * `proportion` (u64): + /// - Proportion of the hotkey's stake to be given to the child, the value must be u64 normalized. + /// + /// # Events: + /// * `ChildAddedSingular`: + /// - On successfully registering a child to a hotkey. + /// + /// # Errors: + /// * `SubNetworkDoesNotExist`: + /// - Attempting to register to a non-existent network. + /// * `RegistrationNotPermittedOnRootSubnet`: + /// - Attempting to register a child on the root network. + /// * `NonAssociatedColdKey`: + /// - The coldkey does not own the hotkey or the child is the same as the hotkey. + /// * `HotKeyAccountNotExists`: + /// - The hotkey account does not exist. + /// + /// # Detailed Explanation of Checks: + /// 1. **Signature Verification**: Ensures that the caller has signed the transaction, verifying the coldkey. + /// 2. **Root Network Check**: Ensures that the delegation is not on the root network, as child hotkeys are not valid on the root. + /// 3. **Network Existence Check**: Ensures that the specified network exists. + /// 4. **Ownership Verification**: Ensures that the coldkey owns the hotkey. + /// 5. **Hotkey Account Existence Check**: Ensures that the hotkey account already exists. + /// 6. **Child-Hotkey Distinction**: Ensures that the child is not the same as the hotkey. + /// 7. **Old Children Cleanup**: Removes the hotkey from the parent list of its old children. + /// 8. **New Children Assignment**: Assigns the new child to the hotkey and updates the parent list for the new child. + // TODO: Benchmark this call + #[pallet::call_index(67)] + #[pallet::weight((Weight::from_parts(119_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] + pub fn set_children( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + netuid: u16, + children: Vec<(u64, T::AccountId)>, + ) -> DispatchResultWithPostInfo { + Self::do_set_children(origin, hotkey, netuid, children)?; + Ok(().into()) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index f26a1cd55..7e244834d 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -158,5 +158,13 @@ mod errors { InsufficientBalanceToPerformColdkeySwap, /// The maximum number of coldkey destinations has been reached MaxColdkeyDestinationsReached, + /// Attempting to set an invalid child for a hotkey on a network. + InvalidChild, + /// Duplicate child when setting children. + DuplicateChild, + /// Proportion overflow when setting children. + ProportionOverflow, + /// Too many children MAX 5. + TooManyChildren, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index a5fb90e3f..e3e47f32e 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -171,5 +171,6 @@ mod events { /// The account ID of the coldkey coldkey: T::AccountId, }, + SetChildren(T::AccountId, u16, Vec<(u64, T::AccountId)>), } } diff --git a/pallets/subtensor/src/rpc_info/neuron_info.rs b/pallets/subtensor/src/rpc_info/neuron_info.rs index a4b58d666..6b370afd8 100644 --- a/pallets/subtensor/src/rpc_info/neuron_info.rs +++ b/pallets/subtensor/src/rpc_info/neuron_info.rs @@ -117,14 +117,10 @@ impl Pallet { } }) .collect::, Compact)>>(); - - let stake: Vec<(T::AccountId, Compact)> = - as IterableStorageDoubleMap>::iter_prefix( - hotkey.clone(), - ) - .map(|(coldkey, stake)| (coldkey, stake.into())) - .collect(); - + let stake: Vec<(T::AccountId, Compact)> = vec![( + coldkey, + Self::get_stake_for_hotkey_on_subnet(hotkey, netuid).into(), + )]; let neuron = NeuronInfo { hotkey: hotkey.clone(), coldkey: coldkey.clone(), diff --git a/pallets/subtensor/src/staking/mod.rs b/pallets/subtensor/src/staking/mod.rs index 5e1b5f6bf..0b3894b61 100644 --- a/pallets/subtensor/src/staking/mod.rs +++ b/pallets/subtensor/src/staking/mod.rs @@ -5,3 +5,4 @@ pub mod decrease_take; pub mod helpers; pub mod increase_take; pub mod remove_stake; +pub mod set_children; diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs new file mode 100644 index 000000000..79898b6ae --- /dev/null +++ b/pallets/subtensor/src/staking/set_children.rs @@ -0,0 +1,185 @@ +use super::*; +use substrate_fixed::types::I96F32; + +impl Pallet { + /// ---- The implementation for the extrinsic do_set_child_singular: Sets a single child. + /// + /// This function allows a coldkey to set children keys. + /// + /// # Arguments: + /// * `origin` (::RuntimeOrigin): + /// - The signature of the calling coldkey. Setting a hotkey child can only be done by the coldkey. + /// + /// * `hotkey` (T::AccountId): + /// - The hotkey which will be assigned the child. + /// + /// * `netuid` (u16): + /// - The u16 network identifier where the child keys will exist. + /// + /// * `children` Vec[(u64, T::AccountId)]: + /// - A list of children with their proportions. + /// + /// # Events: + /// * `ChildrenAdded`: + /// - On successfully registering children to a hotkey. + /// + /// # Errors: + /// * `SubNetworkDoesNotExist`: + /// - Attempting to register to a non-existent network. + /// * `RegistrationNotPermittedOnRootSubnet`: + /// - Attempting to register a child on the root network. + /// * `NonAssociatedColdKey`: + /// - The coldkey does not own the hotkey or the child is the same as the hotkey. + /// * `HotKeyAccountNotExists`: + /// - The hotkey account does not exist. + /// + /// # Detailed Explanation of Checks: + /// 1. **Signature Verification**: Ensures that the caller has signed the transaction, verifying the coldkey. + /// 2. **Root Network Check**: Ensures that the delegation is not on the root network, as child hotkeys are not valid on the root. + /// 3. **Network Existence Check**: Ensures that the specified network exists. + /// 4. **Ownership Verification**: Ensures that the coldkey owns the hotkey. + /// 5. **Hotkey Account Existence Check**: Ensures that the hotkey account already exists. + /// 6. **Child-Hotkey Distinction**: Ensures that the child is not the same as the hotkey. + /// 7. **Old Children Cleanup**: Removes the hotkey from the parent list of its old children. + /// 8. **New Children Assignment**: Assigns the new child to the hotkey and updates the parent list for the new child. + /// + pub fn do_set_children( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + netuid: u16, + children: Vec<(u64, T::AccountId)>, + ) -> DispatchResult { + // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) + let coldkey = ensure_signed(origin)?; + log::trace!( + "do_set_children( coldkey:{:?} hotkey:{:?} netuid:{:?} children:{:?} )", + coldkey, + netuid, + hotkey, + children + ); + + // --- 2. Check that this delegation is not on the root network. Child hotkeys are not valid on root. + ensure!( + netuid != Self::get_root_netuid(), + Error::::RegistrationNotPermittedOnRootSubnet + ); + + // --- 3. Check that the network we are trying to create the child on exists. + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + // --- 4. Check that the coldkey owns the hotkey. + ensure!( + Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::NonAssociatedColdKey + ); + + // --- 4.1. Ensure that the number of children does not exceed 5. + ensure!(children.len() <= 5, Error::::TooManyChildren); + + // --- 5. Ensure that each child is not the hotkey. + for (_, child_i) in &children { + ensure!(child_i != &hotkey, Error::::InvalidChild); + } + // --- 5.1. Ensure that the sum of the proportions does not exceed u64::MAX. + let _total_proportion: u64 = children + .iter() + .try_fold(0u64, |acc, &(proportion, _)| acc.checked_add(proportion)) + .ok_or(Error::::ProportionOverflow)?; + + // --- 5.2. Ensure there are no duplicates in the list of children. + let mut unique_children = Vec::new(); + for (_, child_i) in &children { + ensure!( + !unique_children.contains(child_i), + Error::::DuplicateChild + ); + unique_children.push(child_i.clone()); + } + + // --- 6. Erase myself from old children's parents. + let old_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(hotkey.clone(), netuid); + + // --- 6.0. Iterate over all my old children and remove myself from their parent's map. + for (_, old_child_i) in old_children.clone().iter() { + // --- 6.1. Get the old child's parents on this network. + let my_old_child_parents: Vec<(u64, T::AccountId)> = + ParentKeys::::get(old_child_i.clone(), netuid); + + // --- 6.2. Filter my hotkey from my old children's parents list. + let filtered_parents: Vec<(u64, T::AccountId)> = my_old_child_parents + .into_iter() + .filter(|(_, parent)| *parent != hotkey) + .collect(); + + // --- 6.3. Update the parent list in storage + ParentKeys::::insert(old_child_i, netuid, filtered_parents); + } + + // --- 7.1. Insert my new children + proportion list into the map. + ChildKeys::::insert(hotkey.clone(), netuid, children.clone()); + + // --- 7.2. Update the parents list for my new children. + for (proportion, new_child_i) in children.clone().iter() { + // --- 8.2.1. Get the child's parents on this network. + let mut new_child_previous_parents: Vec<(u64, T::AccountId)> = + ParentKeys::::get(new_child_i.clone(), netuid); + + // --- 7.2.2. Append my hotkey and proportion to my new child's parents list. + // NOTE: There are no duplicates possible because I previously removed my self from my old children. + new_child_previous_parents.push((*proportion, hotkey.clone())); + + // --- 7.2.3. Update the parents list in storage. + ParentKeys::::insert(new_child_i.clone(), netuid, new_child_previous_parents); + } + + // --- 8. Log and return. + log::trace!( + "SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )", + hotkey, + netuid, + children.clone() + ); + Self::deposit_event(Event::SetChildren(hotkey.clone(), netuid, children.clone())); + + // Ok and return. + Ok(()) + } + + /* Retrieves the list of children for a given hotkey and network. + /// + /// # Arguments + /// * `hotkey` - The hotkey whose children are to be retrieved. + /// * `netuid` - The network identifier. + /// + /// # Returns + /// * `Vec<(u64, T::AccountId)>` - A vector of tuples containing the proportion and child account ID. + /// + /// # Example + /// ``` + /// let children = SubtensorModule::get_children(&hotkey, netuid); + */ + pub fn get_children(hotkey: &T::AccountId, netuid: u16) -> Vec<(u64, T::AccountId)> { + ChildKeys::::get(hotkey, netuid) + } + + /* Retrieves the list of parents for a given child and network. + /// + /// # Arguments + /// * `child` - The child whose parents are to be retrieved. + /// * `netuid` - The network identifier. + /// + /// # Returns + /// * `Vec<(u64, T::AccountId)>` - A vector of tuples containing the proportion and parent account ID. + /// + /// # Example + /// ``` + /// let parents = SubtensorModule::get_parents(&child, netuid); + */ + pub fn get_parents(child: &T::AccountId, netuid: u16) -> Vec<(u64, T::AccountId)> { + ParentKeys::::get(child, netuid) + } +} diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 4688bcbb5..b46a50eb2 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -543,6 +543,17 @@ impl Pallet { H256::from_slice(&keccak_256_seal_hash_vec) } + pub fn hash_hotkey_to_u64(hotkey: &T::AccountId) -> u64 { + let binding = hotkey.encode(); + let (hotkey_bytes, _) = binding.split_at(32); + let mut full_bytes = [0u8; 64]; + // Copy the hotkey_bytes into the first half of full_bytes + full_bytes[..32].copy_from_slice(hotkey_bytes); + let keccak_256_seal_hash_vec: [u8; 32] = keccak_256(&full_bytes[..]); + let hash_u64: u64 = u64::from_le_bytes(keccak_256_seal_hash_vec[0..8].try_into().unwrap()); + hash_u64 + } + pub fn create_seal_hash(block_number_u64: u64, nonce_u64: u64, hotkey: &T::AccountId) -> H256 { let nonce = nonce_u64.to_le_bytes(); let block_hash_at_number: H256 = Self::get_block_hash_from_u64(block_number_u64); diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index c61133e94..a1929f498 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -695,4 +695,20 @@ impl Pallet { pub fn get_liquid_alpha_enabled(netuid: u16) -> bool { LiquidAlphaOn::::get(netuid) } + + /// Gets the current hotkey emission tempo. + /// + /// # Returns + /// * `u64` - The current emission tempo value. + pub fn get_hotkey_emission_tempo() -> u64 { + HotkeyEmissionTempo::::get() + } + /// Sets the hotkey emission tempo. + /// + /// # Arguments + /// * `emission_tempo` - The new emission tempo value to set. + pub fn set_hotkey_emission_tempo(emission_tempo: u64) { + HotkeyEmissionTempo::::set(emission_tempo); + Self::deposit_event(Event::HotkeyEmissionTempoSet(emission_tempo)); + } } diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs new file mode 100644 index 000000000..9ad07e1e7 --- /dev/null +++ b/pallets/subtensor/tests/children.rs @@ -0,0 +1,1261 @@ +use crate::mock::*; +use frame_support::{assert_err, assert_ok}; +mod mock; +use pallet_subtensor::*; +use sp_core::U256; + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_success --exact --nocapture +#[test] +fn test_do_set_child_singular_success() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + )); + + // Verify child assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion, child)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_network_does_not_exist --exact --nocapture +#[test] +fn test_do_set_child_singular_network_does_not_exist() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 999; // Non-existent network + let proportion: u64 = 1000; + + // Attempt to set child + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + ), + Error::::SubNetworkDoesNotExist + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_invalid_child --exact --nocapture +#[test] +fn test_do_set_child_singular_invalid_child() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Attempt to set child as the same hotkey + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![ + (proportion, hotkey) // Invalid child + ] + ), + Error::::InvalidChild + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_non_associated_coldkey --exact --nocapture +#[test] +fn test_do_set_child_singular_non_associated_coldkey() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey with a different coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, U256::from(999), 0); + + // Attempt to set child + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_root_network --exact --nocapture +#[test] +fn test_do_set_child_singular_root_network() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = SubtensorModule::get_root_netuid(); // Root network + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + + // Attempt to set child + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + ), + Error::::RegistrationNotPermittedOnRootSubnet + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_old_children_cleanup --exact --nocapture +#[test] +fn test_do_set_child_singular_old_children_cleanup() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let old_child = U256::from(3); + let new_child = U256::from(4); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set old child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, old_child)] + )); + + // Set new child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, new_child)] + )); + + // Verify old child is removed + let old_child_parents = SubtensorModule::get_parents(&old_child, netuid); + assert!(old_child_parents.is_empty()); + + // Verify new child assignment + let new_child_parents = SubtensorModule::get_parents(&new_child, netuid); + assert_eq!(new_child_parents, vec![(proportion, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_old_children_cleanup --exact --nocapture +#[test] +fn test_do_set_child_singular_new_children_assignment() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + )); + + // Verify child assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion, child)]); + + // Verify parent assignment + let parents = SubtensorModule::get_parents(&child, netuid); + assert_eq!(parents, vec![(proportion, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_proportion_edge_cases --exact --nocapture +#[test] +fn test_do_set_child_singular_proportion_edge_cases() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set child with minimum proportion + let min_proportion: u64 = 0; + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(min_proportion, child)] + )); + + // Verify child assignment with minimum proportion + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(min_proportion, child)]); + + // Set child with maximum proportion + let max_proportion: u64 = u64::MAX; + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(max_proportion, child)] + )); + + // Verify child assignment with maximum proportion + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(max_proportion, child)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_multiple_children --exact --nocapture +#[test] +fn test_do_set_child_singular_multiple_children() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + let proportion1: u64 = 500; + let proportion2: u64 = 500; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set first child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion1, child1)] + )); + + // Set second child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion2, child2)] + )); + + // Verify children assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion2, child2)]); + + // Verify parent assignment for both children + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert!(parents1.is_empty()); // Old child should be removed + + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert_eq!(parents2, vec![(proportion2, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_add_singular_child --exact --nocapture +#[test] +#[cfg(not(tarpaulin))] +fn test_add_singular_child() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let child = U256::from(1); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + assert_eq!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX, child)] + ), + Err(Error::::SubNetworkDoesNotExist.into()) + ); + add_network(netuid, 0, 0); + assert_eq!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX, child)] + ), + Err(Error::::NonAssociatedColdKey.into()) + ); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + assert_eq!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX, child)] + ), + Err(Error::::InvalidChild.into()) + ); + let child = U256::from(3); + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX, child)] + )); + }) +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_stake_for_hotkey_on_subnet --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey0 = U256::from(1); + let hotkey1 = U256::from(2); + let coldkey0 = U256::from(3); + let coldkey1 = U256::from(4); + + add_network(netuid, 0, 0); + + let max_stake: u64 = 3000; + SubtensorModule::set_network_max_stake(netuid, max_stake); + + SubtensorModule::create_account_if_non_existent(&coldkey0, &hotkey0); + SubtensorModule::create_account_if_non_existent(&coldkey1, &hotkey1); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey0, &hotkey0, 1000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey0, &hotkey1, 1000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &hotkey0, 1000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &hotkey1, 1000); + + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 2000); + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 2000); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid), + 2000 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid), + 2000 + ); + + // Set child relationship + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey0), + hotkey0, + netuid, + vec![(u64::MAX, hotkey1)] + )); + + // Check stakes after setting child + let stake0 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid); + let stake1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid); + + assert_eq!(stake0, 0); + assert_eq!(stake1, max_stake); + + // Change child relationship to 50% + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey0), + hotkey0, + netuid, + vec![(u64::MAX / 2, hotkey1)] + )); + + // Check stakes after changing child relationship + let stake0 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid); + let stake1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid); + + assert_eq!(stake0, 1001); + assert!(stake1 >= max_stake - 1 && stake1 <= max_stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_success --exact --nocapture +#[test] +fn test_do_revoke_child_singular_success() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + )); + + // Verify child assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion, child)]); + + // Revoke child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify child removal + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + + // Verify parent removal + let parents = SubtensorModule::get_parents(&child, netuid); + assert!(parents.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_network_does_not_exist --exact --nocapture +#[test] +fn test_do_revoke_child_singular_network_does_not_exist() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 999; // Non-existent network + + // Attempt to revoke child + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + ), + Error::::SubNetworkDoesNotExist + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_non_associated_coldkey --exact --nocapture +#[test] +fn test_do_revoke_child_singular_non_associated_coldkey() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + + // Add network and register hotkey with a different coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, U256::from(999), 0); + + // Attempt to revoke child + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_child_not_associated --exact --nocapture +#[test] +fn test_do_revoke_child_singular_child_not_associated() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + // Attempt to revoke child that is not associated + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX, child)] + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_success --exact --nocapture +#[test] +fn test_do_set_children_multiple_success() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + let proportion1: u64 = 1000; + let proportion2: u64 = 2000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion1, child1), (proportion2, child2)] + )); + + // Verify children assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion1, child1), (proportion2, child2)]); + + // Verify parent assignment for both children + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert_eq!(parents1, vec![(proportion1, hotkey)]); + + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert_eq!(parents2, vec![(proportion2, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_network_does_not_exist --exact --nocapture +#[test] +fn test_do_set_children_multiple_network_does_not_exist() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let netuid: u16 = 999; // Non-existent network + let proportion: u64 = 1000; + + // Attempt to set children + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child1)] + ), + Error::::SubNetworkDoesNotExist + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_invalid_child --exact --nocapture +#[test] +fn test_do_set_children_multiple_invalid_child() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Attempt to set child as the same hotkey + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, hotkey)] + ), + Error::::InvalidChild + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_non_associated_coldkey --exact --nocapture +#[test] +fn test_do_set_children_multiple_non_associated_coldkey() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey with a different coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, U256::from(999), 0); + + // Attempt to set children + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_root_network --exact --nocapture +#[test] +fn test_do_set_children_multiple_root_network() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = SubtensorModule::get_root_netuid(); // Root network + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + + // Attempt to set children + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + ), + Error::::RegistrationNotPermittedOnRootSubnet + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_old_children_cleanup --exact --nocapture +#[test] +fn test_do_set_children_multiple_old_children_cleanup() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let old_child = U256::from(3); + let new_child1 = U256::from(4); + let new_child2 = U256::from(5); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set old child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, old_child)] + )); + + // Set new children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, new_child1), (proportion, new_child2)] + )); + + // Verify old child is removed + let old_child_parents = SubtensorModule::get_parents(&old_child, netuid); + assert!(old_child_parents.is_empty()); + + // Verify new children assignment + let new_child1_parents = SubtensorModule::get_parents(&new_child1, netuid); + assert_eq!(new_child1_parents, vec![(proportion, hotkey)]); + + let new_child2_parents = SubtensorModule::get_parents(&new_child2, netuid); + assert_eq!(new_child2_parents, vec![(proportion, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_proportion_edge_cases --exact --nocapture +#[test] +fn test_do_set_children_multiple_proportion_edge_cases() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set children with minimum and maximum proportions + let min_proportion: u64 = 0; + let max_proportion: u64 = u64::MAX; + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(min_proportion, child1), (max_proportion, child2)] + )); + + // Verify children assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!( + children, + vec![(min_proportion, child1), (max_proportion, child2)] + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_overwrite_existing --exact --nocapture +#[test] +fn test_do_set_children_multiple_overwrite_existing() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let child3 = U256::from(5); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set initial children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child1), (proportion, child2)] + )); + + // Overwrite with new children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion * 2, child2), (proportion * 3, child3)] + )); + + // Verify final children assignment + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!( + children, + vec![(proportion * 2, child2), (proportion * 3, child3)] + ); + + // Verify parent assignment for all children + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert!(parents1.is_empty()); + + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert_eq!(parents2, vec![(proportion * 2, hotkey)]); + + let parents3 = SubtensorModule::get_parents(&child3, netuid); + assert_eq!(parents3, vec![(proportion * 3, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_empty_list --exact --nocapture +#[test] +fn test_do_set_children_multiple_empty_list() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set empty children list + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify children assignment is empty + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_success --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_success() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + let proportion1: u64 = 1000; + let proportion2: u64 = 2000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion1, child1), (proportion2, child2)] + )); + + // Revoke multiple children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify children removal + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + + // Verify parent removal for both children + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert!(parents1.is_empty()); + + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert!(parents2.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_network_does_not_exist --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_network_does_not_exist() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 999; // Non-existent network + // Attempt to revoke children + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX / 2, child1), (u64::MAX / 2, child2)] + ), + Error::::SubNetworkDoesNotExist + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_non_associated_coldkey --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_non_associated_coldkey() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid: u16 = 1; + + // Add network and register hotkey with a different coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, U256::from(999), 0); + + // Attempt to revoke children + assert_err!( + SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(u64::MAX / 2, child1), (u64::MAX / 2, child2)] + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_partial_revocation --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_partial_revocation() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let child3 = U256::from(5); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![ + (proportion, child1), + (proportion, child2), + (proportion, child3) + ] + )); + + // Revoke only child3 + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child1), (proportion, child2)] + )); + + // Verify children removal + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion, child1), (proportion, child2)]); + + // Verify parents. + let parents1 = SubtensorModule::get_parents(&child3, netuid); + assert!(parents1.is_empty()); + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert_eq!(parents1, vec![(proportion, hotkey)]); + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert_eq!(parents2, vec![(proportion, hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_non_existent_children --exact --nocapture + +#[test] +fn test_do_revoke_children_multiple_non_existent_children() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set one child + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child1)] + )); + + // Attempt to revoke existing and non-existent children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify all children are removed + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + + // Verify parent removal for the existing child + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert!(parents1.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_empty_list --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_empty_list() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Attempt to revoke with an empty list + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify no changes in children + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_complex_scenario --exact --nocapture +#[test] +fn test_do_revoke_children_multiple_complex_scenario() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let child3 = U256::from(5); + let netuid: u16 = 1; + let proportion1: u64 = 1000; + let proportion2: u64 = 2000; + let proportion3: u64 = 3000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![ + (proportion1, child1), + (proportion2, child2), + (proportion3, child3) + ] + )); + + // Revoke child2 + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion1, child1), (proportion3, child3)] + )); + + // Verify remaining children + let children = SubtensorModule::get_children(&hotkey, netuid); + assert_eq!(children, vec![(proportion1, child1), (proportion3, child3)]); + + // Verify parent removal for child2 + let parents2 = SubtensorModule::get_parents(&child2, netuid); + assert!(parents2.is_empty()); + + // Revoke remaining children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify all children are removed + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + + // Verify parent removal for all children + let parents1 = SubtensorModule::get_parents(&child1, netuid); + assert!(parents1.is_empty()); + let parents3 = SubtensorModule::get_parents(&child3, netuid); + assert!(parents3.is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_network_max_stake --exact --nocapture +#[test] +fn test_get_network_max_stake() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let default_max_stake = SubtensorModule::get_network_max_stake(netuid); + + // Check that the default value is set correctly + assert_eq!(default_max_stake, 500_000_000_000_000); + + // Set a new max stake value + let new_max_stake: u64 = 1_000_000; + SubtensorModule::set_network_max_stake(netuid, new_max_stake); + + // Check that the new value is retrieved correctly + assert_eq!( + SubtensorModule::get_network_max_stake(netuid), + new_max_stake + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake --exact --nocapture +#[test] +fn test_set_network_max_stake() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let initial_max_stake = SubtensorModule::get_network_max_stake(netuid); + + // Set a new max stake value + let new_max_stake: u64 = 500_000; + SubtensorModule::set_network_max_stake(netuid, new_max_stake); + + // Check that the new value is set correctly + assert_eq!( + SubtensorModule::get_network_max_stake(netuid), + new_max_stake + ); + assert_ne!( + SubtensorModule::get_network_max_stake(netuid), + initial_max_stake + ); + + // Check that the event is emitted + System::assert_last_event(Event::NetworkMaxStakeSet(netuid, new_max_stake).into()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake_multiple_networks --exact --nocapture +#[test] +fn test_set_network_max_stake_multiple_networks() { + new_test_ext(1).execute_with(|| { + let netuid1: u16 = 1; + let netuid2: u16 = 2; + + // Set different max stake values for two networks + let max_stake1: u64 = 1_000_000; + let max_stake2: u64 = 2_000_000; + SubtensorModule::set_network_max_stake(netuid1, max_stake1); + SubtensorModule::set_network_max_stake(netuid2, max_stake2); + + // Check that the values are set correctly for each network + assert_eq!(SubtensorModule::get_network_max_stake(netuid1), max_stake1); + assert_eq!(SubtensorModule::get_network_max_stake(netuid2), max_stake2); + assert_ne!( + SubtensorModule::get_network_max_stake(netuid1), + SubtensorModule::get_network_max_stake(netuid2) + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake_update --exact --nocapture +#[test] +fn test_set_network_max_stake_update() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + + // Set an initial max stake value + let initial_max_stake: u64 = 1_000_000; + SubtensorModule::set_network_max_stake(netuid, initial_max_stake); + + // Update the max stake value + let updated_max_stake: u64 = 1_500_000; + SubtensorModule::set_network_max_stake(netuid, updated_max_stake); + + // Check that the value is updated correctly + assert_eq!( + SubtensorModule::get_network_max_stake(netuid), + updated_max_stake + ); + assert_ne!( + SubtensorModule::get_network_max_stake(netuid), + initial_max_stake + ); + + // Check that the event is emitted for the update + System::assert_last_event(Event::NetworkMaxStakeSet(netuid, updated_max_stake).into()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_children_stake_values --exact --nocapture +#[test] +fn test_children_stake_values() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let child3 = U256::from(5); + let netuid: u16 = 1; + let proportion1: u64 = u64::MAX / 4; + let proportion2: u64 = u64::MAX / 4; + let proportion3: u64 = u64::MAX / 4; + + // Add network and register hotkey + add_network(netuid, 13, 0); + SubtensorModule::set_max_registrations_per_block(netuid, 4); + SubtensorModule::set_target_registrations_per_interval(netuid, 4); + register_ok_neuron(netuid, hotkey, coldkey, 0); + register_ok_neuron(netuid, child1, coldkey, 0); + register_ok_neuron(netuid, child2, coldkey, 0); + register_ok_neuron(netuid, child3, coldkey, 0); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey, + &hotkey, + 100_000_000_000_000, + ); + + // Set multiple children with proportions. + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![ + (proportion1, child1), + (proportion2, child2), + (proportion3, child3) + ] + )); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid), + 25_000_000_069_852 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid), + 24_999_999_976_716 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid), + 24_999_999_976_716 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child3, netuid), + 24_999_999_976_716 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child3, netuid) + + SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid) + + SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid) + + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid), + 100_000_000_000_000 + ); + }); +} diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs new file mode 100644 index 000000000..cd7d2cc81 --- /dev/null +++ b/pallets/subtensor/tests/coinbase.rs @@ -0,0 +1,154 @@ +use crate::mock::*; +mod mock; +// use frame_support::{assert_err, assert_ok}; +use sp_core::U256; + +// Test the ability to hash all sorts of hotkeys. +#[test] +#[cfg(not(tarpaulin))] +fn test_hotkey_hashing() { + new_test_ext(1).execute_with(|| { + for i in 0..10000 { + SubtensorModule::hash_hotkey_to_u64(&U256::from(i)); + } + }); +} + +// Test drain tempo on hotkeys. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_hotkey_drain_time -- --nocapture +#[test] +#[cfg(not(tarpaulin))] +fn test_hotkey_drain_time() { + new_test_ext(1).execute_with(|| { + // Block 0 + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(0), 0, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(1), 0, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(2), 0, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(3), 0, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(4), 0, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(5), 0, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(6), 0, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(7), 0, 1)); + + // Block 1 + assert!(SubtensorModule::should_drain_hotkey(&U256::from(0), 1, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(1), 1, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(2), 1, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(3), 1, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(4), 1, 1)); + assert!(!SubtensorModule::should_drain_hotkey(&U256::from(5), 1, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(6), 1, 1)); + assert!(SubtensorModule::should_drain_hotkey(&U256::from(7), 1, 1)); + }); +} + +// To run this test specifically, use the following command: +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_coinbase_basic -- --nocapture +#[test] +#[cfg(not(tarpaulin))] +fn test_coinbase_basic() { + new_test_ext(1).execute_with(|| { + // Define network ID + let netuid: u16 = 1; + let hotkey = U256::from(0); + let coldkey = U256::from(3); + + // Create a network with a tempo 1 + add_network(netuid, 1, 0); + register_ok_neuron(netuid, hotkey, coldkey, 100000); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, 1000); + + // Set the subnet emission value to 1. + SubtensorModule::set_emission_values(&[netuid], vec![1]).unwrap(); + assert_eq!(SubtensorModule::get_subnet_emission_value(netuid), 1); + + // Hotkey has no pending emission + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + + // Hotkey has same stake + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 1000); + + // Subnet has no pending emission. + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + // Step block + next_block(); + + // Hotkey has no pending emission + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + + // Hotkey has same stake + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey), 1000); + + // Subnet has no pending emission of 1 ( from coinbase ) + assert_eq!(SubtensorModule::get_pending_emission(netuid), 1); + + // Step block releases + next_block(); + + // Subnet pending has been drained. + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + // Hotkey pending immediately drained. + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + + // Hotkey has NEW stake + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + 1000 + 2 + ); + + // Set the hotkey drain time to 2 block. + SubtensorModule::set_hotkey_emission_tempo(2); + + // Step block releases + next_block(); + + // Subnet pending increased by 1 + assert_eq!(SubtensorModule::get_pending_emission(netuid), 1); + + // Hotkey pending not increased (still on subnet) + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + + // Hotkey has same stake + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + 1000 + 2 + ); + + // Step block releases + next_block(); + + // Subnet pending has been drained. + assert_eq!(SubtensorModule::get_pending_emission(netuid), 0); + + // Hotkey pending drained. + assert_eq!(SubtensorModule::get_pending_hotkey_emission(&hotkey), 0); + + // Hotkey has 2 new TAO. + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + 1000 + 4 + ); + }); +} + +// Test getting and setting hotkey emission tempo +#[test] +#[cfg(not(tarpaulin))] +fn test_set_and_get_hotkey_emission_tempo() { + new_test_ext(1).execute_with(|| { + // Get the default hotkey emission tempo + let default_tempo = SubtensorModule::get_hotkey_emission_tempo(); + assert_eq!(default_tempo, 0); // default is 0 in mock.rs + + // Set a new hotkey emission tempo + let new_tempo = 5; + SubtensorModule::set_hotkey_emission_tempo(new_tempo); + + // Get the updated hotkey emission tempo + let updated_tempo = SubtensorModule::get_hotkey_emission_tempo(); + assert_eq!(updated_tempo, new_tempo); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0ed3cd10f..df1687fdf 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1213,6 +1213,10 @@ impl fn set_liquid_alpha_enabled(netuid: u16, enabled: bool) { SubtensorModule::set_liquid_alpha_enabled(netuid, enabled); } + + fn set_hotkey_emission_tempo(emission_tempo: u64) { + SubtensorModule::set_hotkey_emission_tempo(emission_tempo); + } fn do_set_alpha_values( origin: RuntimeOrigin, From 8eb7bd9c9c7df4c8d586c1b4f7acdd38b57738ad Mon Sep 17 00:00:00 2001 From: const Date: Mon, 15 Jul 2024 17:03:29 -0500 Subject: [PATCH 017/269] merge childkeys --- pallets/admin-utils/src/benchmarking.rs | 8 + pallets/admin-utils/src/lib.rs | 55 +- pallets/admin-utils/src/weights.rs | 12 - pallets/admin-utils/tests/mock.rs | 8 + pallets/subtensor/src/coinbase/block_step.rs | 4 - .../subtensor/src/coinbase/run_coinbase.rs | 28 +- pallets/subtensor/src/lib.rs | 11 +- pallets/subtensor/src/macros/config.rs | 6 + pallets/subtensor/src/macros/events.rs | 5 + pallets/subtensor/src/rpc_info/neuron_info.rs | 4 +- pallets/subtensor/src/staking/set_children.rs | 1 - pallets/subtensor/src/subnets/registration.rs | 4 +- pallets/subtensor/src/utils.rs | 37 + pallets/subtensor/tests/block_step.rs | 940 -------------- pallets/subtensor/tests/coinbase.rs | 1 + pallets/subtensor/tests/mock.rs | 5 + pallets/subtensor/tests/staking.rs | 1076 ----------------- runtime/src/lib.rs | 10 +- 18 files changed, 162 insertions(+), 2053 deletions(-) delete mode 100644 pallets/subtensor/tests/block_step.rs diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 3165e907f..33e026553 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -248,5 +248,13 @@ mod benchmarks { _(RawOrigin::Root, 1u64/*emission_tempo*/)/*set_hotkey_emission_tempo*/; } + #[benchmark] + fn sudo_set_network_max_stake() { + T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16/*netuid*/, 1_000_000_000_000_000u64/*max_stake*/)/*sudo_set_network_max_stake*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 2aa45cbc9..59b0ed44a 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1050,8 +1050,9 @@ pub mod pallet { /// /// # Errors /// * `DispatchError::BadOrigin` - If the origin is not the root account. + // #[pallet::weight(T::WeightInfo::sudo_set_hotkey_emission_tempo())] #[pallet::call_index(52)] - #[pallet::weight(T::WeightInfo::sudo_set_hotkey_emission_tempo())] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_hotkey_emission_tempo( origin: OriginFor, emission_tempo: u64, @@ -1064,6 +1065,57 @@ pub mod pallet { ); Ok(()) } + + /// Sets the maximum stake allowed for a specific network. + /// + /// This function allows the root account to set the maximum stake for a given network. + /// It updates the network's maximum stake value and logs the change. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which must be the root account. + /// * `netuid` - The unique identifier of the network. + /// * `max_stake` - The new maximum stake value to set. + /// + /// # Returns + /// + /// Returns `Ok(())` if the operation is successful, or an error if it fails. + /// + /// # Example + /// + /// + /// # Notes + /// + /// - This function can only be called by the root account. + /// - The `netuid` should correspond to an existing network. + /// + /// # TODO + /// + // - Consider adding a check to ensure the `netuid` corresponds to an existing network. + // - Implement a mechanism to gradually adjust the max stake to prevent sudden changes. + // #[pallet::weight(T::WeightInfo::sudo_set_network_max_stake())] + #[pallet::call_index(53)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_network_max_stake( + origin: OriginFor, + netuid: u16, + max_stake: u64, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the new maximum stake for the specified network + T::Subtensor::set_network_max_stake(netuid, max_stake); + + // Log the change + log::trace!( + "NetworkMaxStakeSet( netuid: {:?}, max_stake: {:?} )", + netuid, + max_stake + ); + + Ok(()) + } } } @@ -1167,4 +1219,5 @@ pub trait SubtensorInterface { alpha_high: u16, ) -> Result<(), DispatchError>; fn set_hotkey_emission_tempo(emission_tempo: u64); + fn set_network_max_stake(netuid: u16, max_stake: u64); } diff --git a/pallets/admin-utils/src/weights.rs b/pallets/admin-utils/src/weights.rs index 0c0259ef2..84fe058f8 100644 --- a/pallets/admin-utils/src/weights.rs +++ b/pallets/admin-utils/src/weights.rs @@ -62,8 +62,6 @@ pub trait WeightInfo { fn sudo_set_tempo() -> Weight; fn sudo_set_commit_reveal_weights_interval() -> Weight; fn sudo_set_commit_reveal_weights_enabled() -> Weight; - fn sudo_set_hotkey_emission_tempo() -> Weight; - } /// Weights for `pallet_admin_utils` using the Substrate node and recommended hardware. @@ -807,14 +805,4 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: `SubtensorModule::HotkeyEmissionTempo` (r:0 w:1) - /// Proof: `SubtensorModule::HotkeyEmissionTempo` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn sudo_set_hotkey_emission_tempo() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 5_000_000 picoseconds. - Weight::from_parts(6_000_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } } \ No newline at end of file diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index c1f55fece..7ae11b6fb 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -114,6 +114,8 @@ parameter_types! { 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 InitialHotkeyEmissionTempo: u64 = 1; + pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO } impl pallet_subtensor::Config for Test { @@ -169,6 +171,8 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; + type InitialNetworkMaxStake = InitialNetworkMaxStake; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -478,6 +482,10 @@ impl pallet_admin_utils::SubtensorInterface f fn set_hotkey_emission_tempo(emission_tempo: u64) { SubtensorModule::set_hotkey_emission_tempo(emission_tempo) } + fn set_network_max_stake(netuid: u16, max_stake: u64) { + SubtensorModule::set_network_max_stake(netuid, max_stake) + } + fn do_set_alpha_values( origin: RuntimeOrigin, netuid: u16, diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index f4df51456..3c621155f 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -1,10 +1,6 @@ use super::*; -use frame_support::storage::IterableStorageDoubleMap; use frame_support::storage::IterableStorageMap; -use sp_runtime::Saturating; use substrate_fixed::types::I110F18; -use substrate_fixed::types::I64F64; -use substrate_fixed::types::I96F32; impl Pallet { /// Executes the necessary operations for each block. diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 7dd829853..0f747a89e 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -1,8 +1,5 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; -use frame_support::storage::IterableStorageMap; -use sp_runtime::Saturating; -use substrate_fixed::types::I110F18; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; @@ -190,8 +187,8 @@ impl Pallet { mining_emission: u64, ) { // --- 1. First, calculate the hotkey's share of the emission. - let take_proportion: I64F64 = - I64F64::from_num(Delegates::::get(hotkey)).saturating_div(I64F64::from_num(u16::MAX)); + let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) + .saturating_div(I64F64::from_num(u16::MAX)); let hotkey_take: u64 = take_proportion .saturating_mul(I64F64::from_num(validating_emission)) .to_num::(); @@ -274,8 +271,8 @@ impl Pallet { let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); // --- 5 Calculate the emission take for the hotkey. - let take_proportion: I64F64 = - I64F64::from_num(Delegates::::get(hotkey)).saturating_div(I64F64::from_num(u16::MAX)); + let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) + .saturating_div(I64F64::from_num(u16::MAX)); let hotkey_take: u64 = (take_proportion.saturating_mul(I64F64::from_num(emission))).to_num::(); @@ -287,11 +284,15 @@ impl Pallet { // --- 8 Iterate over each nominator. for (nominator, nominator_stake) in - as IterableStorageDoubleMap>::iter_prefix(hotkey) + as IterableStorageDoubleMap>::iter_prefix( + hotkey, + ) { // --- 9 Check if the stake was manually increased by the user since the last emission drain for this hotkey. // If it was, skip this nominator as they will not receive their proportion of the emission. - if LastAddStakeIncrease::::get(hotkey, nominator.clone()) > last_hotkey_emission_drain { + if LastAddStakeIncrease::::get(hotkey, nominator.clone()) + > last_hotkey_emission_drain + { continue; } @@ -365,9 +366,10 @@ impl Pallet { if tempo == 0 { return u64::MAX; } - (tempo as u64).saturating_sub( - (block_number.saturating_add((netuid as u64).saturating_add(1))) - % (tempo as u64).saturating_add(1), - ) + let netuid_plus_one = (netuid as u64).saturating_add(1); + let block_plus_netuid = block_number.saturating_add(netuid_plus_one); + let tempo_plus_one = (tempo as u64).saturating_add(1); + let remainder = block_plus_netuid.rem_euclid(tempo_plus_one); + (tempo as u64).saturating_sub(remainder) } } \ No newline at end of file diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8d2b5c4fd..dd98a318f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -502,7 +502,7 @@ pub mod pallet { #[pallet::type_value] /// Default value for network immunity period. pub fn DefaultHotkeyEmissionTempo() -> u64 { - 7200 + T::InitialHotkeyEmissionTempo::get() } #[pallet::type_value] /// Default value for rate limiting @@ -549,6 +549,11 @@ pub mod pallet { pub fn DefaultAlphaValues() -> (u16, u16) { (45875, 58982) } + #[pallet::type_value] + /// Default value for network max stake. + pub fn DefaultNetworkMaxStake() -> u64 { + T::InitialNetworkMaxStake::get() + } #[pallet::storage] pub type SenateRequiredStakePercentage = @@ -926,6 +931,10 @@ pub mod pallet { /// MAP ( netuid ) --> (alpha_low, alpha_high) pub type AlphaValues = StorageMap<_, Identity, u16, (u16, u16), ValueQuery, DefaultAlphaValues>; + /// MAP ( netuid ) --> max stake allowed on a subnet. + #[pallet::storage] + pub type NetworkMaxStake = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultNetworkMaxStake>; /// ======================================= /// ==== Subnetwork Consensus Storage ==== diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a640ecbb6..aca0f1b30 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -168,5 +168,11 @@ mod config { /// A flag to indicate if Liquid Alpha is enabled. #[pallet::constant] type LiquidAlphaOn: Get; + /// Initial network max stake. + #[pallet::constant] + type InitialNetworkMaxStake: Get; + /// Initial hotkey emission tempo. + #[pallet::constant] + type InitialHotkeyEmissionTempo: Get; } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index e3e47f32e..b93b8296b 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -171,6 +171,11 @@ mod events { /// The account ID of the coldkey coldkey: T::AccountId, }, + /// The children of a hotkey have been set SetChildren(T::AccountId, u16, Vec<(u64, T::AccountId)>), + /// The hotkey emission tempo has been set + HotkeyEmissionTempoSet(u64), + /// The network maximum stake has been set + NetworkMaxStakeSet(u16, u64), } } diff --git a/pallets/subtensor/src/rpc_info/neuron_info.rs b/pallets/subtensor/src/rpc_info/neuron_info.rs index 6b370afd8..cadd4b6e3 100644 --- a/pallets/subtensor/src/rpc_info/neuron_info.rs +++ b/pallets/subtensor/src/rpc_info/neuron_info.rs @@ -118,8 +118,8 @@ impl Pallet { }) .collect::, Compact)>>(); let stake: Vec<(T::AccountId, Compact)> = vec![( - coldkey, - Self::get_stake_for_hotkey_on_subnet(hotkey, netuid).into(), + coldkey.clone(), + Self::get_stake_for_hotkey_on_subnet(&hotkey, netuid).into(), )]; let neuron = NeuronInfo { hotkey: hotkey.clone(), diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 79898b6ae..071764734 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -1,5 +1,4 @@ use super::*; -use substrate_fixed::types::I96F32; impl Pallet { /// ---- The implementation for the extrinsic do_set_child_singular: Sets a single child. diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index b46a50eb2..ecff0fd99 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -550,10 +550,10 @@ impl Pallet { // Copy the hotkey_bytes into the first half of full_bytes full_bytes[..32].copy_from_slice(hotkey_bytes); let keccak_256_seal_hash_vec: [u8; 32] = keccak_256(&full_bytes[..]); - let hash_u64: u64 = u64::from_le_bytes(keccak_256_seal_hash_vec[0..8].try_into().unwrap()); + let hash_u64: u64 = u64::from_le_bytes(keccak_256_seal_hash_vec[0..8].try_into().unwrap_or_default()); hash_u64 } - + pub fn create_seal_hash(block_number_u64: u64, nonce_u64: u64, hotkey: &T::AccountId) -> H256 { let nonce = nonce_u64.to_le_bytes(); let block_hash_at_number: H256 = Self::get_block_hash_from_u64(block_number_u64); diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index a1929f498..6f5dbeaff 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -703,6 +703,7 @@ impl Pallet { pub fn get_hotkey_emission_tempo() -> u64 { HotkeyEmissionTempo::::get() } + /// Sets the hotkey emission tempo. /// /// # Arguments @@ -711,4 +712,40 @@ impl Pallet { HotkeyEmissionTempo::::set(emission_tempo); Self::deposit_event(Event::HotkeyEmissionTempoSet(emission_tempo)); } + + pub fn get_pending_hotkey_emission(hotkey: &T::AccountId) -> u64 { + PendingdHotkeyEmission::::get(hotkey) + } + + /// Retrieves the maximum stake allowed for a given network. + /// + /// # Arguments + /// + /// * `netuid` - The unique identifier of the network. + /// + /// # Returns + /// + /// * `u64` - The maximum stake allowed for the specified network. + pub fn get_network_max_stake(netuid: u16) -> u64 { + NetworkMaxStake::::get(netuid) + } + + /// Sets the maximum stake allowed for a given network. + /// + /// # Arguments + /// + /// * `netuid` - The unique identifier of the network. + /// * `max_stake` - The new maximum stake value to set. + /// + /// # Effects + /// + /// * Updates the NetworkMaxStake storage. + /// * Emits a NetworkMaxStakeSet event. + pub fn set_network_max_stake(netuid: u16, max_stake: u64) { + // Update the NetworkMaxStake storage with the new max_stake value + NetworkMaxStake::::insert(netuid, max_stake); + + // Emit an event to notify listeners about the change + Self::deposit_event(Event::NetworkMaxStakeSet(netuid, max_stake)); + } } diff --git a/pallets/subtensor/tests/block_step.rs b/pallets/subtensor/tests/block_step.rs deleted file mode 100644 index 87fdb8bcf..000000000 --- a/pallets/subtensor/tests/block_step.rs +++ /dev/null @@ -1,940 +0,0 @@ -#![allow(clippy::unwrap_used)] - -mod mock; -use frame_support::assert_ok; -use frame_system::Config; -use mock::*; -use sp_core::U256; - -#[test] -fn test_loaded_emission() { - new_test_ext(1).execute_with(|| { - let n: u16 = 100; - let netuid: u16 = 1; - let tempo: u16 = 10; - let netuids: Vec = vec![1]; - let emission: Vec = vec![1000000000]; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_allowed_uids(netuid, n); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_emission_values(&netuids, emission).unwrap(); - for i in 0..n { - SubtensorModule::append_neuron(netuid, &U256::from(i), 0); - } - assert!(SubtensorModule::get_loaded_emission_tuples(netuid).is_none()); - - // Try loading at block 0 - let block: u64 = 0; - assert_eq!( - SubtensorModule::blocks_until_next_epoch(netuid, tempo, block), - 8 - ); - SubtensorModule::generate_emission(block); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid).is_none()); - - // Try loading at block = 9; - let block: u64 = 8; - assert_eq!( - SubtensorModule::blocks_until_next_epoch(netuid, tempo, block), - 0 - ); - SubtensorModule::generate_emission(block); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid).is_some()); - assert_eq!( - SubtensorModule::get_loaded_emission_tuples(netuid) - .unwrap() - .len(), - n as usize - ); - - // Try draining the emission tuples - // None remaining because we are at epoch. - let block: u64 = 8; - SubtensorModule::drain_emission(block); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid).is_none()); - - // Generate more emission. - SubtensorModule::generate_emission(8); - assert_eq!( - SubtensorModule::get_loaded_emission_tuples(netuid) - .unwrap() - .len(), - n as usize - ); - - for block in 9..19 { - let mut n_remaining: usize = 0; - let mut n_to_drain: usize = 0; - if let Some(tuples) = SubtensorModule::get_loaded_emission_tuples(netuid) { - n_remaining = tuples.len(); - n_to_drain = - SubtensorModule::tuples_to_drain_this_block(netuid, tempo, block, tuples.len()); - } - SubtensorModule::drain_emission(block); // drain it with 9 more blocks to go - if let Some(tuples) = SubtensorModule::get_loaded_emission_tuples(netuid) { - assert_eq!(tuples.len(), n_remaining - n_to_drain); - } - log::info!("n_to_drain: {:?}", n_to_drain); - log::info!( - "SubtensorModule::get_loaded_emission_tuples( netuid ).len(): {:?}", - n_remaining - n_to_drain - ); - } - }) -} - -#[test] -fn test_tuples_to_drain_this_block() { - new_test_ext(1).execute_with(|| { - // pub fn tuples_to_drain_this_block( netuid: u16, tempo: u16, block_number: u64, n_remaining: usize ) -> usize { - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 1, 0, 10), 10); // drain all epoch block. - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 0, 0, 10), 10); // drain all no tempo. - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 10, 0, 10), 2); // drain 10 / ( 10 / 2 ) = 2 - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 20, 0, 10), 1); // drain 10 / ( 20 / 2 ) = 1 - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 10, 0, 20), 5); // drain 20 / ( 9 / 2 ) = 5 - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 20, 0, 0), 0); // nothing to drain. - assert_eq!(SubtensorModule::tuples_to_drain_this_block(0, 10, 1, 20), 5); // drain 19 / ( 10 / 2 ) = 4 - assert_eq!( - SubtensorModule::tuples_to_drain_this_block(0, 10, 10, 20), - 4 - ); // drain 19 / ( 10 / 2 ) = 4 - assert_eq!( - SubtensorModule::tuples_to_drain_this_block(0, 10, 15, 20), - 10 - ); // drain 19 / ( 10 / 2 ) = 4 - assert_eq!( - SubtensorModule::tuples_to_drain_this_block(0, 10, 19, 20), - 20 - ); // drain 19 / ( 10 / 2 ) = 4 - assert_eq!( - SubtensorModule::tuples_to_drain_this_block(0, 10, 20, 20), - 20 - ); // drain 19 / ( 10 / 2 ) = 4 - for i in 0..10 { - for j in 0..10 { - for k in 0..10 { - for l in 0..10 { - assert!(SubtensorModule::tuples_to_drain_this_block(i, j, k, l) <= 10); - } - } - } - } - }) -} - -#[test] -fn test_blocks_until_epoch() { - new_test_ext(1).execute_with(|| { - // Check tempo = 0 block = * netuid = * - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 0, 0), 1000); - - // Check tempo = 1 block = * netuid = * - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 1, 0), 0); - assert_eq!(SubtensorModule::blocks_until_next_epoch(1, 1, 0), 1); - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 1, 1), 1); - assert_eq!(SubtensorModule::blocks_until_next_epoch(1, 1, 1), 0); - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 1, 2), 0); - assert_eq!(SubtensorModule::blocks_until_next_epoch(1, 1, 2), 1); - for i in 0..100 { - if i % 2 == 0 { - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 1, i), 0); - assert_eq!(SubtensorModule::blocks_until_next_epoch(1, 1, i), 1); - } else { - assert_eq!(SubtensorModule::blocks_until_next_epoch(0, 1, i), 1); - assert_eq!(SubtensorModule::blocks_until_next_epoch(1, 1, i), 0); - } - } - - // Check general case. - for netuid in 0..30_u16 { - for block in 0..30_u64 { - for tempo in 1..30_u16 { - assert_eq!( - SubtensorModule::blocks_until_next_epoch(netuid, tempo, block), - tempo as u64 - (block + netuid as u64 + 1) % (tempo as u64 + 1) - ); - } - } - } - }); -} - -// /******************************************** -// block_step::adjust_registration_terms_for_networks tests -// *********************************************/ -#[test] -fn test_burn_adjustment() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 1; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - assert_eq!( - SubtensorModule::get_adjustment_interval(netuid), - adjustment_interval - ); // Sanity check the adjustment interval. - - // Register key 1. - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - // We are over the number of regs allowed this interval. - // Step the block and trigger the adjustment. - step_block(1); - - // Check the adjusted burn. - assert_eq!(SubtensorModule::get_burn_as_u64(netuid), 1500); - }); -} - -#[test] -fn test_burn_adjustment_with_moving_average() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 1; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - // Set alpha here. - SubtensorModule::set_adjustment_alpha(netuid, u64::MAX / 2); - - // Register key 1. - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - // We are over the number of regs allowed this interval. - // Step the block and trigger the adjustment. - step_block(1); - - // Check the adjusted burn. - // 0.5 * 1000 + 0.5 * 1500 = 1250 - assert_eq!(SubtensorModule::get_burn_as_u64(netuid), 1250); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_a() { - // Test case A of the difficulty and burn adjustment algorithm. - // ==================== - // There are too many registrations this interval and most of them are pow registrations - // this triggers an increase in the pow difficulty. - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 1; - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_min_difficulty(netuid, start_diff); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. This is a burn registration. - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. This is a POW registration - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 0, - &hotkey_account_id_2, - ); - let result0 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - curr_block_num, - nonce0, - work0, - hotkey_account_id_2, - coldkey_account_id_2, - ); - assert_ok!(result0); - - // Register key 3. This is a POW registration - let hotkey_account_id_3 = U256::from(3); - let coldkey_account_id_3 = U256::from(3); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 11231312312, - &hotkey_account_id_3, - ); - let result1 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_3), - netuid, - curr_block_num, - nonce1, - work1, - hotkey_account_id_3, - coldkey_account_id_3, - ); - assert_ok!(result1); - - // We are over the number of regs allowed this interval. - // Most of them are POW registrations (2 out of 3) - // Step the block and trigger the adjustment. - step_block(1); - curr_block_num += 1; - - // Check the adjusted POW difficulty has INCREASED. - // and the burn has not changed. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert_eq!(adjusted_burn, burn_cost); - - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert!(adjusted_diff > start_diff); - assert_eq!(adjusted_diff, 20_000); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_b() { - // Test case B of the difficulty and burn adjustment algorithm. - // ==================== - // There are too many registrations this interval and most of them are burn registrations - // this triggers an increase in the burn cost. - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 1; - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - // Register key 3. This one is a POW registration - let hotkey_account_id_3 = U256::from(3); - let coldkey_account_id_3 = U256::from(3); - let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 0, - &hotkey_account_id_3, - ); - let result = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_3), - netuid, - curr_block_num, - nonce, - work, - hotkey_account_id_3, - coldkey_account_id_3, - ); - assert_ok!(result); - - // We are over the number of regs allowed this interval. - // Most of them are burn registrations (2 out of 3) - // Step the block and trigger the adjustment. - step_block(1); - curr_block_num += 1; - - // Check the adjusted burn has INCREASED. - // and the difficulty has not changed. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert!(adjusted_burn > burn_cost); - assert_eq!(adjusted_burn, 2_000); - - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert_eq!(adjusted_diff, start_diff); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_c() { - // Test case C of the difficulty and burn adjustment algorithm. - // ==================== - // There are not enough registrations this interval and most of them are POW registrations - // this triggers a decrease in the burn cost - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 4; // Needs registrations < 4 to trigger - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. This is a BURN registration - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. This is a POW registration - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - let (nonce0, work0): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 0, - &hotkey_account_id_2, - ); - let result0 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - curr_block_num, - nonce0, - work0, - hotkey_account_id_2, - coldkey_account_id_2, - ); - assert_ok!(result0); - - // Register key 3. This is a POW registration - let hotkey_account_id_3 = U256::from(3); - let coldkey_account_id_3 = U256::from(3); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 11231312312, - &hotkey_account_id_3, - ); - let result1 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_3), - netuid, - curr_block_num, - nonce1, - work1, - hotkey_account_id_3, - coldkey_account_id_3, - ); - assert_ok!(result1); - - // We are UNDER the number of regs allowed this interval. - // Most of them are POW registrations (2 out of 3) - // Step the block and trigger the adjustment. - step_block(1); - curr_block_num += 1; - - // Check the adjusted burn has DECREASED. - // and the difficulty has not changed. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert!(adjusted_burn < burn_cost); - assert_eq!(adjusted_burn, 875); - - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert_eq!(adjusted_diff, start_diff); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_d() { - // Test case D of the difficulty and burn adjustment algorithm. - // ==================== - // There are not enough registrations this interval and most of them are BURN registrations - // this triggers a decrease in the POW difficulty - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval = 4; // Needs registrations < 4 to trigger - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_min_difficulty(netuid, 1); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. This is a BURN registration - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_1, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - hotkey_account_id_1 - )); - - // Register key 2. This is a BURN registration - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - // Register key 3. This is a POW registration - let hotkey_account_id_3 = U256::from(3); - let coldkey_account_id_3 = U256::from(3); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 11231312312, - &hotkey_account_id_3, - ); - let result1 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_3), - netuid, - curr_block_num, - nonce1, - work1, - hotkey_account_id_3, - coldkey_account_id_3, - ); - assert_ok!(result1); - - // We are UNDER the number of regs allowed this interval. - // Most of them are BURN registrations (2 out of 3) - // Step the block and trigger the adjustment. - step_block(1); - curr_block_num += 1; - - // Check the adjusted POW difficulty has DECREASED. - // and the burn has not changed. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert_eq!(adjusted_burn, burn_cost); - - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert!(adjusted_diff < start_diff); - assert_eq!(adjusted_diff, 8750); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_e() { - // Test case E of the difficulty and burn adjustment algorithm. - // ==================== - // There are not enough registrations this interval and nobody registered either POW or BURN - // this triggers a decrease in the BURN cost and POW difficulty - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval: u16 = 3; - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 10); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_min_difficulty(netuid, 1); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. This is a POW registration - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 11231312312, - &hotkey_account_id_1, - ); - let result1 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - curr_block_num, - nonce1, - work1, - hotkey_account_id_1, - coldkey_account_id_1, - ); - assert_ok!(result1); - - // Register key 2. This is a BURN registration - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - step_block(1); - curr_block_num += 1; - - // We are UNDER the number of regs allowed this interval. - // And the number of regs of each type is equal - - // Check the adjusted BURN has DECREASED. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert!(adjusted_burn < burn_cost); - assert_eq!(adjusted_burn, 833); - - // Check the adjusted POW difficulty has DECREASED. - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert!(adjusted_diff < start_diff); - assert_eq!(adjusted_diff, 8_333); - }); -} - -#[test] -#[allow(unused_assignments)] -fn test_burn_adjustment_case_f() { - // Test case F of the difficulty and burn adjustment algorithm. - // ==================== - // There are too many registrations this interval and the pow and burn registrations are equal - // this triggers an increase in the burn cost and pow difficulty - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval: u16 = 1; - let start_diff: u64 = 10_000; - let mut curr_block_num = 0; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 10); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_min_difficulty(netuid, start_diff); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // Register key 1. This is a POW registration - let hotkey_account_id_1 = U256::from(1); - let coldkey_account_id_1 = U256::from(1); - let (nonce1, work1): (u64, Vec) = SubtensorModule::create_work_for_block_number( - netuid, - curr_block_num, - 11231312312, - &hotkey_account_id_1, - ); - let result1 = SubtensorModule::register( - <::RuntimeOrigin>::signed(hotkey_account_id_1), - netuid, - curr_block_num, - nonce1, - work1, - hotkey_account_id_1, - coldkey_account_id_1, - ); - assert_ok!(result1); - - // Register key 2. This is a BURN registration - let hotkey_account_id_2 = U256::from(2); - let coldkey_account_id_2 = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id_2, 10000); - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(hotkey_account_id_2), - netuid, - hotkey_account_id_2 - )); - - step_block(1); - curr_block_num += 1; - // We are OVER the number of regs allowed this interval. - // And the number of regs of each type is equal - - // Check the adjusted BURN has INCREASED. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert!(adjusted_burn > burn_cost); - assert_eq!(adjusted_burn, 1_500); - - // Check the adjusted POW difficulty has INCREASED. - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert!(adjusted_diff > start_diff); - assert_eq!(adjusted_diff, 15_000); - }); -} - -#[test] -fn test_burn_adjustment_case_e_zero_registrations() { - // Test case E of the difficulty and burn adjustment algorithm. - // ==================== - // There are not enough registrations this interval and nobody registered either POW or BURN - // this triggers a decrease in the BURN cost and POW difficulty - - // BUT there are zero registrations this interval. - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let burn_cost: u64 = 1000; - let adjustment_interval = 1; - let target_registrations_per_interval: u16 = 1; - let start_diff: u64 = 10_000; - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 10); - SubtensorModule::set_burn(netuid, burn_cost); - SubtensorModule::set_difficulty(netuid, start_diff); - SubtensorModule::set_min_difficulty(netuid, 1); - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - SubtensorModule::set_adjustment_alpha(netuid, 58000); // Set to old value. - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - - // No registrations this interval of any kind. - step_block(1); - - // We are UNDER the number of regs allowed this interval. - // And the number of regs of each type is equal - - // Check the adjusted BURN has DECREASED. - let adjusted_burn = SubtensorModule::get_burn_as_u64(netuid); - assert!(adjusted_burn < burn_cost); - assert_eq!(adjusted_burn, 500); - - // Check the adjusted POW difficulty has DECREASED. - let adjusted_diff = SubtensorModule::get_difficulty_as_u64(netuid); - assert!(adjusted_diff < start_diff); - assert_eq!(adjusted_diff, 5_000); - }); -} - -#[test] -fn test_emission_based_on_registration_status() { - new_test_ext(1).execute_with(|| { - let n: u16 = 100; - let netuid_off: u16 = 1; - let netuid_on: u16 = 2; - let tempo: u16 = 1; - let netuids: Vec = vec![netuid_off, netuid_on]; - let emissions: Vec = vec![1000000000, 1000000000]; - - // Add subnets with registration turned off and on - add_network(netuid_off, tempo, 0); - add_network(netuid_on, tempo, 0); - SubtensorModule::set_max_allowed_uids(netuid_off, n); - SubtensorModule::set_max_allowed_uids(netuid_on, n); - SubtensorModule::set_emission_values(&netuids, emissions).unwrap(); - SubtensorModule::set_network_registration_allowed(netuid_off, false); - SubtensorModule::set_network_registration_allowed(netuid_on, true); - - // Populate the subnets with neurons - for i in 0..n { - SubtensorModule::append_neuron(netuid_off, &U256::from(i), 0); - SubtensorModule::append_neuron(netuid_on, &U256::from(i), 0); - } - - // Generate emission at block 0 - let block: u64 = 0; - SubtensorModule::generate_emission(block); - - // Verify that no emission tuples are loaded for the subnet with registration off - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_none()); - - // Verify that emission tuples are loaded for the subnet with registration on - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_on).is_some()); - assert_eq!( - SubtensorModule::get_loaded_emission_tuples(netuid_on) - .unwrap() - .len(), - n as usize - ); - - // Step to the next epoch block - let epoch_block: u16 = tempo; - step_block(epoch_block); - - // Verify that no emission tuples are loaded for the subnet with registration off - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_none()); - log::info!( - "Emissions for netuid with registration off: {:?}", - SubtensorModule::get_loaded_emission_tuples(netuid_off) - ); - - // Verify that emission tuples are loaded for the subnet with registration on - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_on).is_some()); - log::info!( - "Emissions for netuid with registration on: {:?}", - SubtensorModule::get_loaded_emission_tuples(netuid_on) - ); - assert_eq!( - SubtensorModule::get_loaded_emission_tuples(netuid_on) - .unwrap() - .len(), - n as usize - ); - - let block: u64 = 0; - // drain the emission tuples for the subnet with registration on - SubtensorModule::drain_emission(block); - // Turn on registration for the subnet with registration off - SubtensorModule::set_network_registration_allowed(netuid_off, true); - SubtensorModule::set_network_registration_allowed(netuid_on, false); - - // Generate emission at the next block - let next_block: u64 = block + 1; - SubtensorModule::generate_emission(next_block); - - // Verify that emission tuples are now loaded for the subnet with registration turned on - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_some()); - log::info!( - "Emissions for netuid with registration on: {:?}", - SubtensorModule::get_loaded_emission_tuples(netuid_on) - ); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_on).is_none()); - assert_eq!( - SubtensorModule::get_loaded_emission_tuples(netuid_off) - .unwrap() - .len(), - n as usize - ); - }); -} - -#[test] -fn test_epoch_runs_when_registration_disabled() { - new_test_ext(1).execute_with(|| { - let n: u16 = 100; - let netuid_off: u16 = 1; - let tempo: u16 = 1; - let netuids: Vec = vec![netuid_off]; - let emissions: Vec = vec![1000000000]; - - // Add subnets with registration turned off and on - add_network(netuid_off, tempo, 0); - SubtensorModule::set_max_allowed_uids(netuid_off, n); - SubtensorModule::set_emission_values(&netuids, emissions).unwrap(); - SubtensorModule::set_network_registration_allowed(netuid_off, false); - - // Populate the subnets with neurons - for i in 0..n { - SubtensorModule::append_neuron(netuid_off, &U256::from(i), 0); - } - - // Generate emission at block 1 - let block: u64 = 1; - SubtensorModule::generate_emission(block); - - step_block(1); // Now block 2 - - // Verify blocks since last step was set - assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid_off), 1); - - // Step to the next epoch block - let epoch_block: u16 = tempo; - step_block(epoch_block); - - // Verify blocks since last step was set, this indicates we ran the epoch - assert_eq!( - SubtensorModule::get_blocks_since_last_step(netuid_off), - 0_u64 - ); - assert!(SubtensorModule::get_loaded_emission_tuples(netuid_off).is_some()); - }); -} diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index cd7d2cc81..aed3c9b55 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -135,6 +135,7 @@ fn test_coinbase_basic() { } // Test getting and setting hotkey emission tempo +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_set_and_get_hotkey_emission_tempo -- --nocapture #[test] #[cfg(not(tarpaulin))] fn test_set_and_get_hotkey_emission_tempo() { diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 71e1d593b..27d11eb13 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -168,6 +168,9 @@ parameter_types! { 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 InitialHotkeyEmissionTempo: u64 = 0; // Defaults to draining every block. + pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500,000 TAO + } // Configure collective pallet for council @@ -378,6 +381,8 @@ impl pallet_subtensor::Config for Test { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; + type InitialNetworkMaxStake = InitialNetworkMaxStake; } impl pallet_utility::Config for Test { diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index ece30391d..2952426a9 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1189,1082 +1189,6 @@ fn test_delegate_stake_division_by_zero_check() { <::RuntimeOrigin>::signed(coldkey), hotkey )); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey, 0, 1000); - }); -} - -#[test] -#[cfg(not(tarpaulin))] -fn test_full_with_delegating() { - new_test_ext(1).execute_with(|| { - let netuid = 1; - // Make two accounts. - let hotkey0 = U256::from(1); - let hotkey1 = U256::from(2); - - let coldkey0 = U256::from(3); - let coldkey1 = U256::from(4); - add_network(netuid, 0, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 4); - SubtensorModule::set_target_registrations_per_interval(netuid, 4); - SubtensorModule::set_max_allowed_uids(netuid, 4); // Allow all 4 to be registered at once - SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval - - // Neither key can add stake because they dont have fundss. - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - - // Add balances. - SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 60000); - SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 60000); - - // We have enough, but the keys are not registered. - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - - // Cant remove either. - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 10 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 10 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 10 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 10 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - - // Neither key can become a delegate either because we are not registered. - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - ), - Err(Error::::HotKeyAccountNotExists.into()) - ); - - // Register the 2 neurons to a new network. - register_ok_neuron(netuid, hotkey0, coldkey0, 124124); - register_ok_neuron(netuid, hotkey1, coldkey1, 987907); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey0), - coldkey0 - ); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey1), - coldkey1 - ); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey0, &hotkey0)); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey1, &hotkey1)); - - // We try to delegate stake but niether are allowing delegation. - assert!(!SubtensorModule::hotkey_is_delegate(&hotkey0)); - assert!(!SubtensorModule::hotkey_is_delegate(&hotkey1)); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 100 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 100 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - - // We stake and all is ok. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 100 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 100 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 100); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 100); - //assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey0 ), 100 ); - //assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey1 ), 100 ); - assert_eq!(SubtensorModule::get_total_stake(), 200); - - // Cant remove these funds because we are not delegating. - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - - // Emit inflation through non delegates. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 100); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 100); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 200); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 200); - - // Try allowing the keys to become delegates, fails because of incorrect coldkeys. - // Set take to be 0. - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 0 - ), - Err(Error::::NonAssociatedColdKey.into()) - ); - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 0 - ), - Err(Error::::NonAssociatedColdKey.into()) - ); - - // Become delegates all is ok. - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - SubtensorModule::get_min_take() - )); - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - SubtensorModule::get_min_take() - )); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey0)); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey1)); - - // Cant become a delegate twice. - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - SubtensorModule::get_min_take() - ), - Err(Error::::HotKeyAlreadyDelegate.into()) - ); - assert_eq!( - SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - u16::MAX / 10 - ), - Err(Error::::HotKeyAlreadyDelegate.into()) - ); - - // This add stake works for delegates. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 200 - ); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 200 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 300 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 300 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 200 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 500); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 400); - //assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey0 ), 400 ); - //assert_eq!( SubtensorModule::get_total_stake_for_coldkey( &coldkey1 ), 500 ); - assert_eq!(SubtensorModule::get_total_stake(), 900); - - // Lets emit inflation through the hot and coldkeys. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 1000); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 1000); - - // validator_take = take * validator_emission = 10% * 1000 = 100 - // old_stake + (validator_emission - validator_take) * stake_for_coldkey_and_hotkey / total_stake_for_hotkey + validator_take - // = - // 200 + 900 * 200 / 500 + 100 = 660 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 654 - ); - // validator_take = take * validator_emission = 9% * 1000 = 90 - // old_stake + (validator_emission - validator_take) * stake_for_coldkey_and_hotkey / total_stake_for_hotkey - // = - // 200 + (1000 - 90) * 200 / 400 = 655 ~ 654 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 655 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 846 - ); // 300 + 910 x ( 300 / 500 ) = 300 + 546 = 846 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 745 - ); // 200 + 1090 x ( 200 / 400 ) = 300 + 545 = 745 - assert_eq!(SubtensorModule::get_total_stake(), 2900); // 600 + 700 + 900 + 750 = 2900 - - // // Try unstaking too much. - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100000 - ), - Err(Error::::NotEnoughStakeToWithdraw.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 100000 - ), - Err(Error::::NotEnoughStakeToWithdraw.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 100000 - ), - Err(Error::::NotEnoughStakeToWithdraw.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 100000 - ), - Err(Error::::NotEnoughStakeToWithdraw.into()) - ); - - // unstaking is ok. - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - )); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 100 - )); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 100 - )); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 100 - )); - - // All the amounts have been decreased. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 554 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 555 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 746 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 645 - ); - - // Lets register and stake a new key. - let hotkey2 = U256::from(5); - let coldkey2 = U256::from(6); - register_ok_neuron(netuid, hotkey2, coldkey2, 248_123); - assert!(SubtensorModule::is_hotkey_registered_on_any_network( - &hotkey0 - )); - assert!(SubtensorModule::is_hotkey_registered_on_any_network( - &hotkey1 - )); - - SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 60_000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 1000 - )); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 900 - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey2, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey2, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - - // Lets make this new key a delegate with a 10% take. - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - SubtensorModule::get_min_take() - )); - - // Add nominate some stake. - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey2, - 1_000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey2, - 1_000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 1_000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey2), - 1_000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey2), - 1_000 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 3_000); - assert_eq!(SubtensorModule::get_total_stake(), 5_500); - - // Lets emit inflation through this new key with distributed ownership. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey2, 0, 1000); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 1_394 - ); // 1000 + 94 + 900 * (1000/3000) = 1400 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey2), - 1_303 - ); // 1000 + 900 * (1000/3000) = 1300 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey2), - 1_303 - ); // 1000 + 900 * (1000/3000) = 1300 - assert_eq!(SubtensorModule::get_total_stake(), 6_500); // before + 1_000 = 5_500 + 1_000 = 6_500 - - step_block(1); - - // Lets register and stake a new key. - let hotkey3 = U256::from(7); - let coldkey3 = U256::from(8); - register_ok_neuron(netuid, hotkey3, coldkey3, 4124124); - SubtensorModule::add_balance_to_coldkey_account(&coldkey3, 60000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey3), - hotkey3, - 1000 - )); - - step_block(3); - - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey3), - hotkey3, - SubtensorModule::get_min_take() - )); // Full take. - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey3, - 1000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey3, - 1000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey3, - 1000 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey3), - 1000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey3), - 1000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey3), - 1000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey3, &hotkey3), - 1000 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey3), 4000); - assert_eq!(SubtensorModule::get_total_stake(), 10_500); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey3, 0, 1000); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey3), - 1227 - ); // 1000 + 90% * 1000 * 1000/4000 = 1225 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey3), - 1227 - ); // 1000 + 90% * 1000 * 1000/4000 = 1225 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey3), - 1227 - ); // 1000 + 90% * 1000 * 1000/4000 = 1225 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey3, &hotkey3), - 1319 - ); // 1000 + 25 * 3 + 1000 * 1000/4000 = 1325 - assert_eq!(SubtensorModule::get_total_stake(), 11_500); // before + 1_000 = 10_500 + 1_000 = 11_500 - }); -} - -// Verify delegates with servers get the full server inflation. -#[test] -fn test_full_with_delegating_some_servers() { - new_test_ext(1).execute_with(|| { - let netuid = 1; - // Make two accounts. - let hotkey0 = U256::from(1); - let hotkey1 = U256::from(2); - - let coldkey0 = U256::from(3); - let coldkey1 = U256::from(4); - SubtensorModule::set_max_registrations_per_block(netuid, 4); - SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs - SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval - - // Neither key can add stake because they dont have fundss. - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - - // Add balances. - SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 60000); - SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 60000); - - // Register the 2 neurons to a new network. - let netuid = 1; - add_network(netuid, 0, 0); - register_ok_neuron(netuid, hotkey0, coldkey0, 124124); - register_ok_neuron(netuid, hotkey1, coldkey1, 987907); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey0), - coldkey0 - ); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey1), - coldkey1 - ); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey0, &hotkey0)); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey1, &hotkey1)); - - // We stake and all is ok. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 100 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 100 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 100); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 100); - assert_eq!(SubtensorModule::get_total_stake(), 200); - - // Emit inflation through non delegates. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 100); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 100); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 200); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 200); - - // Become delegates all is ok. - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - SubtensorModule::get_min_take() - )); - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - SubtensorModule::get_min_take() - )); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey0)); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey1)); - - // This add stake works for delegates. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 200 - ); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 200 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 300 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 200 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 300 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 200 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 500); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 400); - assert_eq!(SubtensorModule::get_total_stake(), 900); - - // Lets emit inflation through the hot and coldkeys. - // fist emission arg is for a server. This should only go to the owner of the hotkey. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 200, 1_000); // 1_200 total emission. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 123, 2_000); // 2_123 total emission. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 854 - ); // 200 + (200 + 910 x ( 200 / 500 )) = 200 + (200 + 400) + 60 = 854 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 846 - ); // 300 + 910 x ( 300 / 500 ) = 300 + 546 = 846 - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 1_700); // initial + server emission + validator emission = 799 + 899 = 1_698 - - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 1_110 - ); // 200 + (0 + 2000 x ( 200 / 400 )) - 100 = 200 + (1000) - 100= 1_110 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 1_413 - ); // 200 + (123 + 2000 x ( 200 / 400 )) + 100 = 200 + (1_200)+ 100 = 1_423 - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 2_523); // 400 + 2_123 - assert_eq!(SubtensorModule::get_total_stake(), 4_223); // 2_100 + 2_123 = 4_223 - - // Lets emit MORE inflation through the hot and coldkeys. - // This time only server emission. This should go to the owner of the hotkey. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 350, 0); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 150, 0); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 1_204 - ); // + 350 + 54 = 1_204 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 1_110 - ); // No change. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 846 - ); // No change. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 1_563 - ); // 1_323 + 150 + 90 = 1_573 - assert_eq!(SubtensorModule::get_total_stake(), 4_723); // 4_223 + 500 = 4_823 - - // Lets register and stake a new key. - let hotkey2 = U256::from(5); - let coldkey2 = U256::from(6); - register_ok_neuron(netuid, hotkey2, coldkey2, 248123); - SubtensorModule::add_balance_to_coldkey_account(&coldkey2, 60_000); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 1_000 - )); - assert_ok!(SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 900 - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey2, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - assert_eq!( - SubtensorModule::remove_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey2, - 10 - ), - Err(Error::::HotKeyNotDelegateAndSignerNotOwnHotKey.into()) - ); - - assert_eq!(SubtensorModule::get_total_stake(), 5_623); // 4_723 + 900 = 5_623 - - // Lets make this new key a delegate with a 9% take. - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - SubtensorModule::get_min_take() - )); - - // Add nominate some stake. - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey2, - 1000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey2, - 1000 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey2), - hotkey2, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 1000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey2), - 1000 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey2), - 1000 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey2), 3_000); - assert_eq!(SubtensorModule::get_total_stake(), 7_723); // 5_623 + (1_000 + 1_000 + 100) = 7_723 - - // Lets emit inflation through this new key with distributed ownership. - // We will emit 100 server emission, which should go in-full to the owner of the hotkey. - // We will emit 1000 validator emission, which should be distributed in-part to the nominators. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey2, 100, 1000); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 1_494 - ); // 1000 + 100 + 94 + 900 * (1000/3000) = 1000 + 200 + 300 = 1_494 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey2), - 1_303 - ); // 1000 + 900 * (1000/3000) = 1000 + 300 = 1_303 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey2), - 1_303 - ); // 1000 + 900 * (1000/3000) = 1000 + 300 = 1300 - assert_eq!(SubtensorModule::get_total_stake(), 8_823); // 7_723 + 1_100 = 8_823 - - // Lets emit MORE inflation through this new key with distributed ownership. - // This time we do ONLY server emission - // We will emit 123 server emission, which should go in-full to the owner of the hotkey. - // We will emit *0* validator emission. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey2, 123, 0); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey2, &hotkey2), - 1_617 - ); // 1_500 + 117 = 1_617 - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey2), - 1_303 - ); // No change. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey2), - 1_303 - ); // No change. - assert_eq!(SubtensorModule::get_total_stake(), 8_946); // 8_823 + 123 = 8_946 - }); -} - -#[test] -fn test_full_block_emission_occurs() { - new_test_ext(1).execute_with(|| { - let netuid = 1; - // Make two accounts. - let hotkey0 = U256::from(1); - let hotkey1 = U256::from(2); - - let coldkey0 = U256::from(3); - let coldkey1 = U256::from(4); - SubtensorModule::set_max_registrations_per_block(netuid, 4); - SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs - SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval - - // Neither key can add stake because they dont have fundss. - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - assert_eq!( - SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 60000 - ), - Err(Error::::NotEnoughBalanceToStake.into()) - ); - - // Add balances. - SubtensorModule::add_balance_to_coldkey_account(&coldkey0, 60000); - SubtensorModule::add_balance_to_coldkey_account(&coldkey1, 60000); - - // Register the 2 neurons to a new network. - let netuid = 1; - add_network(netuid, 0, 0); - register_ok_neuron(netuid, hotkey0, coldkey0, 124124); - register_ok_neuron(netuid, hotkey1, coldkey1, 987907); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey0), - coldkey0 - ); - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&hotkey1), - coldkey1 - ); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey0, &hotkey0)); - assert!(SubtensorModule::coldkey_owns_hotkey(&coldkey1, &hotkey1)); - - // We stake and all is ok. - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 0 - ); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - 100 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - 100 - )); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey0), - 100 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey0, &hotkey1), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey0), - 0 - ); - assert_eq!( - SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey1, &hotkey1), - 100 - ); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 100); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 100); - assert_eq!(SubtensorModule::get_total_stake(), 200); - - // Emit inflation through non delegates. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 111); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 234); - // Verify the full emission occurs. - assert_eq!(SubtensorModule::get_total_stake(), 200 + 111 + 234); // 200 + 111 + 234 = 545 - - // Become delegates all is ok. - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey0), - hotkey0, - SubtensorModule::get_min_take() - )); - assert_ok!(SubtensorModule::do_become_delegate( - <::RuntimeOrigin>::signed(coldkey1), - hotkey1, - SubtensorModule::get_min_take() - )); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey0)); - assert!(SubtensorModule::hotkey_is_delegate(&hotkey1)); - - // Add some delegate stake - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey0), - hotkey1, - 200 - )); - assert_ok!(SubtensorModule::add_stake( - <::RuntimeOrigin>::signed(coldkey1), - hotkey0, - 300 - )); - - assert_eq!(SubtensorModule::get_total_stake(), 545 + 500); // 545 + 500 = 1045 - - // Lets emit inflation with delegatees, with both validator and server emission - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 200, 1_000); // 1_200 total emission. - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 123, 2_000); // 2_123 total emission. - - assert_eq!(SubtensorModule::get_total_stake(), 1045 + 1_200 + 2_123); // before + 1_200 + 2_123 = 4_368 - - // Lets emit MORE inflation through the hot and coldkeys. - // This time JUSt server emission - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 350, 0); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 150, 0); - - assert_eq!(SubtensorModule::get_total_stake(), 4_368 + 350 + 150); // before + 350 + 150 = 4_868 - - // Lastly, do only validator emission - - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey0, 0, 12_948); - SubtensorModule::emit_inflation_through_hotkey_account(&hotkey1, 0, 1_874); - - assert_eq!(SubtensorModule::get_total_stake(), 4_868 + 12_948 + 1_874); // before + 12_948 + 1_874 = 19_690 }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index df1687fdf..119156228 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -880,6 +880,8 @@ parameter_types! { 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 SubtensorInitialHotkeyEmissionTempo: u64 = 7200; // Drain every day. + pub const SubtensorInitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO } impl pallet_subtensor::Config for Runtime { @@ -935,6 +937,8 @@ impl pallet_subtensor::Config for Runtime { type AlphaHigh = InitialAlphaHigh; type AlphaLow = InitialAlphaLow; type LiquidAlphaOn = InitialLiquidAlphaOn; + type InitialHotkeyEmissionTempo = SubtensorInitialHotkeyEmissionTempo; + type InitialNetworkMaxStake = SubtensorInitialNetworkMaxStake; } use sp_runtime::BoundedVec; @@ -1213,11 +1217,15 @@ impl fn set_liquid_alpha_enabled(netuid: u16, enabled: bool) { SubtensorModule::set_liquid_alpha_enabled(netuid, enabled); } - + fn set_hotkey_emission_tempo(emission_tempo: u64) { SubtensorModule::set_hotkey_emission_tempo(emission_tempo); } + fn set_network_max_stake(netuid: u16, max_stake: u64) { + SubtensorModule::set_network_max_stake(netuid, max_stake); + } + fn do_set_alpha_values( origin: RuntimeOrigin, netuid: u16, From 73648323536b0209d04b2efa525bbdad3cdadaea Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 09:27:25 -0400 Subject: [PATCH 018/269] clippy --- runtime/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 58c415bc0..bbc7f6623 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,7 +12,6 @@ pub mod check_nonce; mod migrations; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_metadata_hash_extension::CheckMetadataHash; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, From 64aca954586082883a1d83752c2c66793713102b Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 10:03:40 -0400 Subject: [PATCH 019/269] benchmarking fix --- Cargo.lock | 1 + node/Cargo.toml | 1 + node/src/benchmarking.rs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ceeb1098e..e55fbd013 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4673,6 +4673,7 @@ dependencies = [ "clap", "frame-benchmarking", "frame-benchmarking-cli", + "frame-metadata-hash-extension", "frame-system", "futures", "jsonrpsee", diff --git a/node/Cargo.toml b/node/Cargo.toml index 7fc6eff48..0e1a418a3 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -54,6 +54,7 @@ sp-io = { workspace = true } sp-timestamp = { workspace = true } sp-inherents = { workspace = true } sp-keyring = { workspace = true } +frame-metadata-hash-extension = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-commitments = { path = "../pallets/commitments" } diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index ba176e15f..b194479b5 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -136,6 +136,7 @@ pub fn create_benchmark_extrinsic( pallet_transaction_payment::ChargeTransactionPayment::::from(0), pallet_subtensor::SubtensorSignedExtension::::new(), pallet_commitments::CommitmentsSignedExtension::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -152,6 +153,7 @@ pub fn create_benchmark_extrinsic( (), (), (), + None, ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); From 9ec6c4e0a261690f6e0ca274c87932dab513bcae Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Jul 2024 17:26:12 -0400 Subject: [PATCH 020/269] fmt --- node/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index b194479b5..cf48df62f 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -136,7 +136,7 @@ pub fn create_benchmark_extrinsic( pallet_transaction_payment::ChargeTransactionPayment::::from(0), pallet_subtensor::SubtensorSignedExtension::::new(), pallet_commitments::CommitmentsSignedExtension::::new(), - frame_metadata_hash_extension::CheckMetadataHash::::new(true), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -153,7 +153,7 @@ pub fn create_benchmark_extrinsic( (), (), (), - None, + None, ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); From f241f6683b0345a88b9c3658b90dd0a9d5b884ff Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 17 Jul 2024 10:33:25 -0400 Subject: [PATCH 021/269] add root crate --- Cargo.lock | 11 +++++++++++ Cargo.toml | 18 ++++++++++++++++++ src/lib.rs | 0 3 files changed, 29 insertions(+) create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 868e85b89..cc30d5335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9112,6 +9112,17 @@ dependencies = [ "wasm-opt", ] +[[package]] +name = "subtensor" +version = "0.1.0" +dependencies = [ + "node-subtensor", + "node-subtensor-runtime", + "pallet-commitments", + "pallet-subtensor", + "subtensor-macros", +] + [[package]] name = "subtensor-custom-rpc" version = "0.0.2" diff --git a/Cargo.toml b/Cargo.toml index 8d9eff122..cd5208864 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,21 @@ +[package] +name = "subtensor" +version = "0.1.0" +description = "Implementation of the bittensor blockchain" +authors = ["Substrate DevHub "] +homepage = "https://substrate.io/" +edition = "2021" +license = "Unlicense" +publish = false +repository = "https://github.com/opentensor/subtensor" + +[dependencies] +node-subtensor = { path = "node", version = "4.0.0-dev" } +pallet-commitments = { path = "pallets/commitments", version = "4.0.0-dev" } +pallet-subtensor = { path = "pallets/subtensor", version = "4.0.0-dev" } +node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } +subtensor-macros = { path = "support/macros", version = "0.1.0" } + [workspace] members = [ "node", diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..e69de29bb From 770b21afb8b5cd78420b30edf3b04db39dde5101 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 17 Jul 2024 11:13:24 -0400 Subject: [PATCH 022/269] linting scaffold --- Cargo.lock | 113 +++++++++++++++++++------------------- Cargo.toml | 9 +++ build.rs | 40 ++++++++++++++ support/macros/Cargo.toml | 6 +- 4 files changed, 110 insertions(+), 58 deletions(-) create mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index cc30d5335..7d98e1d14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,7 +223,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -625,7 +625,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -751,7 +751,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1159,7 +1159,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1553,7 +1553,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1580,7 +1580,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1597,7 +1597,7 @@ checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1621,7 +1621,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1632,7 +1632,7 @@ checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1738,7 +1738,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1751,7 +1751,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1840,7 +1840,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -1880,7 +1880,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.67", + "syn 2.0.71", "termcolor", "toml 0.8.14", "walkdir", @@ -2040,7 +2040,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2153,7 +2153,7 @@ dependencies = [ "prettyplease 0.2.20", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2489,7 +2489,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2501,7 +2501,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2511,7 +2511,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -2662,7 +2662,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -3453,7 +3453,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4204,7 +4204,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4218,7 +4218,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4229,7 +4229,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -4240,7 +4240,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5590,7 +5590,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5631,7 +5631,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5717,7 +5717,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5727,7 +5727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5854,7 +5854,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5922,7 +5922,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -5968,7 +5968,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6036,7 +6036,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6304,7 +6304,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -6763,7 +6763,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7597,7 +7597,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7878,7 +7878,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -7926,7 +7926,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8159,7 +8159,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8382,7 +8382,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8401,7 +8401,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.10. dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8411,7 +8411,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8636,7 +8636,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8649,7 +8649,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -8862,7 +8862,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9031,7 +9031,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9120,7 +9120,10 @@ dependencies = [ "node-subtensor-runtime", "pallet-commitments", "pallet-subtensor", + "proc-macro2", "subtensor-macros", + "syn 2.0.71", + "walkdir", ] [[package]] @@ -9155,7 +9158,7 @@ dependencies = [ "ahash 0.8.11", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9183,9 +9186,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.67" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8655ed1d86f3af4ee3fd3263786bc14245ad17c4c7e85ba7187fb3ae028c90" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -9291,7 +9294,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9411,7 +9414,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9569,7 +9572,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -9974,7 +9977,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", "wasm-bindgen-shared", ] @@ -10008,7 +10011,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10727,7 +10730,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] @@ -10747,7 +10750,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.67", + "syn 2.0.71", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cd5208864..0ae193639 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,11 @@ pallet-subtensor = { path = "pallets/subtensor", version = "4.0.0-dev" } node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } subtensor-macros = { path = "support/macros", version = "0.1.0" } +[build-dependencies] +syn.workspace = true +proc-macro2.workspace = true +walkdir.workspace = true + [workspace] members = [ "node", @@ -54,6 +59,10 @@ serde_json = { version = "1.0.116", default-features = false } serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } +syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } +quote = "1" +proc-macro2 = "1" +walkdir = "2" subtensor-macros = { path = "support/macros" } diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..82843fefa --- /dev/null +++ b/build.rs @@ -0,0 +1,40 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use walkdir::WalkDir; + +fn main() { + // Get the root directory of the workspace + let workspace_root = env::var("CARGO_MANIFEST_DIR").unwrap(); + let workspace_root = Path::new(&workspace_root); + + // Collect all Rust source files in the workspace + let rust_files = collect_rust_files(workspace_root); + + // Parse each Rust file with syn + for file in rust_files { + let Ok(content) = fs::read_to_string(&file) else { + continue; + }; + let Ok(parsed_file) = syn::parse_file(&content) else { + continue; + }; + //println!("{}", parsed_file.items.len()) + } +} + +// Recursively collects all Rust files in the given directory +fn collect_rust_files(dir: &Path) -> Vec { + let mut rust_files = Vec::new(); + + for entry in WalkDir::new(dir) { + let entry = entry.unwrap(); + let path = entry.path(); + + if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("rs") { + rust_files.push(path.to_path_buf()); + } + } + + rust_files +} diff --git a/support/macros/Cargo.toml b/support/macros/Cargo.toml index 10a15ba0d..b5a5febad 100644 --- a/support/macros/Cargo.toml +++ b/support/macros/Cargo.toml @@ -12,9 +12,9 @@ homepage = "https://bittensor.com/" proc-macro = true [dependencies] -syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } -proc-macro2 = "1" -quote = "1" +syn.workspace = true +proc-macro2.workspace = true +quote.workspace = true ahash = "0.8" [lints] From aebe7418ee8c1b359ba3a06ee29d4ce8c7e38b68 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 17 Jul 2024 11:33:07 -0400 Subject: [PATCH 023/269] lints trait --- build.rs | 2 ++ lints/mod.rs | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 lints/mod.rs diff --git a/build.rs b/build.rs index 82843fefa..b5dd45950 100644 --- a/build.rs +++ b/build.rs @@ -3,6 +3,8 @@ use std::fs; use std::path::{Path, PathBuf}; use walkdir::WalkDir; +mod lints; + fn main() { // Get the root directory of the workspace let workspace_root = env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/lints/mod.rs b/lints/mod.rs new file mode 100644 index 000000000..4338ee07c --- /dev/null +++ b/lints/mod.rs @@ -0,0 +1,10 @@ +use syn::{File, Result}; + +/// A trait that defines custom lints that can be run within our workspace. +/// +/// Each lint is run in parallel on all Rust source files in the workspace. Within a lint you +/// can issue an error the same way you would in a proc macro, and otherwise return `Ok(())` if +/// there are no errors. +pub trait Lint { + fn lint(source: &File) -> Result<()>; +} From 33d11d7986ef32d71d5a4e2d0c065b40a7924df1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 17 Jul 2024 12:10:43 -0400 Subject: [PATCH 024/269] more --- build.rs | 1 + lints/dummy_lint.rs | 10 ++++++++++ lints/lint.rs | 11 +++++++++++ lints/mod.rs | 13 +++++-------- 4 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 lints/dummy_lint.rs create mode 100644 lints/lint.rs diff --git a/build.rs b/build.rs index b5dd45950..155cbd226 100644 --- a/build.rs +++ b/build.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use walkdir::WalkDir; mod lints; +use lints::*; fn main() { // Get the root directory of the workspace diff --git a/lints/dummy_lint.rs b/lints/dummy_lint.rs new file mode 100644 index 000000000..889d43be0 --- /dev/null +++ b/lints/dummy_lint.rs @@ -0,0 +1,10 @@ +use super::*; + +pub struct DummyLint; + +impl Lint for DummyLint { + fn lint(_source: &File) -> Result<()> { + // This is a dummy lint that does nothing + Ok(()) + } +} diff --git a/lints/lint.rs b/lints/lint.rs new file mode 100644 index 000000000..400af5401 --- /dev/null +++ b/lints/lint.rs @@ -0,0 +1,11 @@ +use super::*; + +/// A trait that defines custom lints that can be run within our workspace. +/// +/// Each lint is run in parallel on all Rust source files in the workspace. Within a lint you +/// can issue an error the same way you would in a proc macro, and otherwise return `Ok(())` if +/// there are no errors. +pub trait Lint { + /// Lints the given Rust source file, returning a compile error if any issues are found. + fn lint(source: &File) -> Result<()>; +} diff --git a/lints/mod.rs b/lints/mod.rs index 4338ee07c..3db2d6bd3 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -1,10 +1,7 @@ use syn::{File, Result}; -/// A trait that defines custom lints that can be run within our workspace. -/// -/// Each lint is run in parallel on all Rust source files in the workspace. Within a lint you -/// can issue an error the same way you would in a proc macro, and otherwise return `Ok(())` if -/// there are no errors. -pub trait Lint { - fn lint(source: &File) -> Result<()>; -} +pub mod lint; +pub use lint::*; + +mod dummy_lint; +use dummy_lint::DummyLint; From 44e1d4d88f5e6f9f974dc65d8fca17c3230f8e77 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 17 Jul 2024 14:57:04 -0400 Subject: [PATCH 025/269] working but not parallel --- Cargo.lock | 1 + Cargo.toml | 1 + build.rs | 37 ++++++++++++++++++++++++++++++++++++- lints/dummy_lint.rs | 5 +++-- lints/lint.rs | 6 ++++-- lints/mod.rs | 2 +- 6 files changed, 46 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d98e1d14..41d243a01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9121,6 +9121,7 @@ dependencies = [ "pallet-commitments", "pallet-subtensor", "proc-macro2", + "rayon", "subtensor-macros", "syn 2.0.71", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 0ae193639..8a34008d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ subtensor-macros = { path = "support/macros", version = "0.1.0" } syn.workspace = true proc-macro2.workspace = true walkdir.workspace = true +rayon = "1.10" [workspace] members = [ diff --git a/build.rs b/build.rs index 155cbd226..96bc52b67 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,13 @@ +use rayon::prelude::*; use std::env; use std::fs; use std::path::{Path, PathBuf}; +use std::process::exit; +use std::sync::mpsc::channel; +use syn::spanned::Spanned; +use syn::Error; +use syn::File; +use syn::Result; use walkdir::WalkDir; mod lints; @@ -14,15 +21,39 @@ fn main() { // Collect all Rust source files in the workspace let rust_files = collect_rust_files(workspace_root); + let mut found_error = None; + // Parse each Rust file with syn for file in rust_files { + if found_error.is_some() { + break; + } let Ok(content) = fs::read_to_string(&file) else { continue; }; let Ok(parsed_file) = syn::parse_file(&content) else { continue; }; - //println!("{}", parsed_file.items.len()) + + let track_lint = |result: Result<()>| { + let Err(error) = result else { + return; + }; + found_error = Some((error, file)); + }; + + track_lint(DummyLint::lint(parsed_file)); + } + + if let Some((error, file)) = found_error { + let start = error.span().start(); + let end = error.span().end(); + let start_line = start.line; + let start_col = start.column; + let end_line = end.line; + let end_col = end.column; + let file_path = file.display(); + panic!("{}:{}:{}: {}", file_path, start_line, start_col, error); } } @@ -34,6 +65,10 @@ fn collect_rust_files(dir: &Path) -> Vec { let entry = entry.unwrap(); let path = entry.path(); + if path.ends_with("target") || path.ends_with("build.rs") { + continue; + } + if path.is_file() && path.extension().and_then(|ext| ext.to_str()) == Some("rs") { rust_files.push(path.to_path_buf()); } diff --git a/lints/dummy_lint.rs b/lints/dummy_lint.rs index 889d43be0..4447b66b5 100644 --- a/lints/dummy_lint.rs +++ b/lints/dummy_lint.rs @@ -1,10 +1,11 @@ +use syn::{spanned::Spanned, Error}; + use super::*; pub struct DummyLint; impl Lint for DummyLint { - fn lint(_source: &File) -> Result<()> { - // This is a dummy lint that does nothing + fn lint(_source: File) -> Result<()> { Ok(()) } } diff --git a/lints/lint.rs b/lints/lint.rs index 400af5401..69047c34c 100644 --- a/lints/lint.rs +++ b/lints/lint.rs @@ -1,3 +1,5 @@ +use rayon::iter::IntoParallelIterator; + use super::*; /// A trait that defines custom lints that can be run within our workspace. @@ -5,7 +7,7 @@ use super::*; /// Each lint is run in parallel on all Rust source files in the workspace. Within a lint you /// can issue an error the same way you would in a proc macro, and otherwise return `Ok(())` if /// there are no errors. -pub trait Lint { +pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. - fn lint(source: &File) -> Result<()>; + fn lint(source: File) -> Result<()>; } diff --git a/lints/mod.rs b/lints/mod.rs index 3db2d6bd3..d2f9bb1e7 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -4,4 +4,4 @@ pub mod lint; pub use lint::*; mod dummy_lint; -use dummy_lint::DummyLint; +pub use dummy_lint::DummyLint; From 893b1ec9d8f42c650472535689ab77a357c34347 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 18 Jul 2024 11:17:55 -0400 Subject: [PATCH 026/269] use rc3 with new fix polkadot-sdk/pull/4117 --- Cargo.toml | 140 ++++++++++++++++++++++++++--------------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8bfb9d2b0..4a7565a01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,81 +39,81 @@ litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } subtensor-macros = { path = "support/macros" } -frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" , default-features = false } -frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" , default-features = false } +frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +frame-try-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +pallet-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-insecure-randomness-collective-flip = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-membership = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-multisig = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-preimage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-safe-mode = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-scheduler = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-sudo = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-transaction-payment-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +pallet-transaction-payment-rpc-runtime-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-grandpa-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-chain-spec-derive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-consensus-slots = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-executor = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-keystore = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-network = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-rpc-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-service = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-telemetry = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } -sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2", default-features = false } +sp-api = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-block-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-consensus-aura = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-genesis-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-core = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-inherents = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-io = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-keyring = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-offchain = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-session = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-std = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-storage = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-timestamp = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +sp-tracing = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } +sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } substrate-fixed = { git = "https://github.com/encointer/substrate-fixed.git", tag = "v0.5.9" } -substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } -substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc2" } +substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } +substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } frame-metadata = "16" [profile.release] From 237634cc96cc244cc7be45ba90a9f59c132b046d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 18 Jul 2024 11:18:10 -0400 Subject: [PATCH 027/269] incl lock --- Cargo.lock | 420 ++++++++++++++++++++++++++--------------------------- 1 file changed, 210 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e55fbd013..4a50f8a12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2308,7 +2308,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fork-tree" version = "12.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", ] @@ -2331,7 +2331,7 @@ checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "frame-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-support-procedural", @@ -2347,16 +2347,16 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] [[package]] name = "frame-benchmarking-cli" version = "32.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "array-bytes 6.2.3", @@ -2388,15 +2388,15 @@ dependencies = [ "sp-blockchain", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-inherents", "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "thousands", ] @@ -2404,7 +2404,7 @@ dependencies = [ [[package]] name = "frame-executive" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aquamarine 0.3.3", "frame-support", @@ -2416,8 +2416,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -2435,7 +2435,7 @@ dependencies = [ [[package]] name = "frame-metadata-hash-extension" version = "0.1.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "docify", @@ -2450,7 +2450,7 @@ dependencies = [ [[package]] name = "frame-support" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aquamarine 0.5.0", "array-bytes 6.2.3", @@ -2473,7 +2473,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-crypto-hashing-proc-macro", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-genesis-builder", "sp-inherents", "sp-io", @@ -2481,8 +2481,8 @@ dependencies = [ "sp-runtime", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", "static_assertions", "tt-call", @@ -2491,7 +2491,7 @@ dependencies = [ [[package]] name = "frame-support-procedural" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "cfg-expr", @@ -2510,7 +2510,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support-procedural-tools-derive", "proc-macro-crate 3.1.0", @@ -2522,7 +2522,7 @@ dependencies = [ [[package]] name = "frame-support-procedural-tools-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro2", "quote", @@ -2532,7 +2532,7 @@ dependencies = [ [[package]] name = "frame-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "cfg-if", "docify", @@ -2544,7 +2544,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version", "sp-weights", ] @@ -2552,7 +2552,7 @@ dependencies = [ [[package]] name = "frame-system-benchmarking" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -2561,13 +2561,13 @@ dependencies = [ "scale-info", "sp-core", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "frame-system-rpc-runtime-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "sp-api", @@ -2576,13 +2576,13 @@ dependencies = [ [[package]] name = "frame-try-runtime" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "parity-scale-codec", "sp-api", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -4770,9 +4770,9 @@ dependencies = [ "sp-offchain", "sp-runtime", "sp-session", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "sp-version", "substrate-wasm-builder", @@ -4983,7 +4983,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", "substrate-fixed", "subtensor-macros", @@ -4992,7 +4992,7 @@ dependencies = [ [[package]] name = "pallet-aura" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5003,13 +5003,13 @@ dependencies = [ "sp-application-crypto", "sp-consensus-aura", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-authorship" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5017,13 +5017,13 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-balances" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5033,7 +5033,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -5049,7 +5049,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] @@ -5067,14 +5067,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] [[package]] name = "pallet-grandpa" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5091,13 +5091,13 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5105,13 +5105,13 @@ dependencies = [ "safe-mix", "scale-info", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-membership" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5122,13 +5122,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-multisig" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5138,13 +5138,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-preimage" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5155,13 +5155,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-proxy" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5170,7 +5170,7 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -5186,14 +5186,14 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "subtensor-macros", ] [[package]] name = "pallet-safe-mode" version = "9.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5206,13 +5206,13 @@ dependencies = [ "scale-info", "sp-arithmetic", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-scheduler" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5223,14 +5223,14 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", ] [[package]] name = "pallet-session" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5245,7 +5245,7 @@ dependencies = [ "sp-session", "sp-staking", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", ] @@ -5277,8 +5277,8 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version", "substrate-fixed", "subtensor-macros", @@ -5287,7 +5287,7 @@ dependencies = [ [[package]] name = "pallet-sudo" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5297,13 +5297,13 @@ dependencies = [ "scale-info", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-timestamp" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "frame-benchmarking", @@ -5315,15 +5315,15 @@ dependencies = [ "sp-inherents", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-timestamp", ] [[package]] name = "pallet-transaction-payment" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-support", "frame-system", @@ -5333,13 +5333,13 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "pallet-transaction-payment-rpc" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", @@ -5355,7 +5355,7 @@ dependencies = [ [[package]] name = "pallet-transaction-payment-rpc-runtime-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "pallet-transaction-payment", "parity-scale-codec", @@ -5367,7 +5367,7 @@ dependencies = [ [[package]] name = "pallet-utility" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-benchmarking", "frame-support", @@ -5377,7 +5377,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -6715,18 +6715,18 @@ dependencies = [ [[package]] name = "sc-allocator" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "sp-core", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", ] [[package]] name = "sc-basic-authorship" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "futures-timer", @@ -6748,7 +6748,7 @@ dependencies = [ [[package]] name = "sc-block-builder" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "sp-api", @@ -6763,7 +6763,7 @@ dependencies = [ [[package]] name = "sc-chain-spec" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "docify", @@ -6789,7 +6789,7 @@ dependencies = [ [[package]] name = "sc-chain-spec-derive" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -6800,7 +6800,7 @@ dependencies = [ [[package]] name = "sc-cli" version = "0.36.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "chrono", @@ -6841,7 +6841,7 @@ dependencies = [ [[package]] name = "sc-client-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "fnv", "futures", @@ -6856,11 +6856,11 @@ dependencies = [ "sp-consensus", "sp-core", "sp-database", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-runtime", "sp-state-machine", "sp-statement-store", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "substrate-prometheus-endpoint", ] @@ -6868,7 +6868,7 @@ dependencies = [ [[package]] name = "sc-client-db" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "kvdb", @@ -6894,7 +6894,7 @@ dependencies = [ [[package]] name = "sc-consensus" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -6919,7 +6919,7 @@ dependencies = [ [[package]] name = "sc-consensus-aura" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -6948,7 +6948,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "array-bytes 6.2.3", @@ -6991,7 +6991,7 @@ dependencies = [ [[package]] name = "sc-consensus-grandpa-rpc" version = "0.19.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "finality-grandpa", "futures", @@ -7011,7 +7011,7 @@ dependencies = [ [[package]] name = "sc-consensus-slots" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7034,7 +7034,7 @@ dependencies = [ [[package]] name = "sc-executor" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", @@ -7044,25 +7044,25 @@ dependencies = [ "schnellru", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-io", "sp-panic-handler", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "sp-version", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "tracing", ] [[package]] name = "sc-executor-common" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "polkavm", "sc-allocator", "sp-maybe-compressed-blob", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "wasm-instrument", ] @@ -7070,18 +7070,18 @@ dependencies = [ [[package]] name = "sc-executor-polkavm" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "polkavm", "sc-executor-common", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sc-executor-wasmtime" version = "0.29.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "anyhow", "cfg-if", @@ -7091,15 +7091,15 @@ dependencies = [ "rustix 0.36.17", "sc-allocator", "sc-executor-common", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "wasmtime", ] [[package]] name = "sc-informant" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ansi_term", "futures", @@ -7116,7 +7116,7 @@ dependencies = [ [[package]] name = "sc-keystore" version = "25.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "parking_lot 0.12.3", @@ -7130,7 +7130,7 @@ dependencies = [ [[package]] name = "sc-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 4.2.0", "arrayvec", @@ -7159,7 +7159,7 @@ dependencies = [ [[package]] name = "sc-network" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7202,7 +7202,7 @@ dependencies = [ [[package]] name = "sc-network-bitswap" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-channel", "cid", @@ -7222,7 +7222,7 @@ dependencies = [ [[package]] name = "sc-network-common" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -7239,7 +7239,7 @@ dependencies = [ [[package]] name = "sc-network-gossip" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "futures", @@ -7258,7 +7258,7 @@ dependencies = [ [[package]] name = "sc-network-light" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7279,7 +7279,7 @@ dependencies = [ [[package]] name = "sc-network-sync" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "async-channel", @@ -7315,7 +7315,7 @@ dependencies = [ [[package]] name = "sc-network-transactions" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7334,7 +7334,7 @@ dependencies = [ [[package]] name = "sc-offchain" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "bytes", @@ -7357,7 +7357,7 @@ dependencies = [ "sc-utils", "sp-api", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", "sp-offchain", "sp-runtime", @@ -7368,7 +7368,7 @@ dependencies = [ [[package]] name = "sc-proposer-metrics" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "substrate-prometheus-endpoint", @@ -7377,7 +7377,7 @@ dependencies = [ [[package]] name = "sc-rpc" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "jsonrpsee", @@ -7409,7 +7409,7 @@ dependencies = [ [[package]] name = "sc-rpc-api" version = "0.33.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "jsonrpsee", "parity-scale-codec", @@ -7429,7 +7429,7 @@ dependencies = [ [[package]] name = "sc-rpc-server" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "governor", @@ -7447,7 +7447,7 @@ dependencies = [ [[package]] name = "sc-rpc-spec-v2" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "futures", @@ -7478,7 +7478,7 @@ dependencies = [ [[package]] name = "sc-service" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "directories", @@ -7520,12 +7520,12 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", "sp-runtime", "sp-session", "sp-state-machine", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", @@ -7542,7 +7542,7 @@ dependencies = [ [[package]] name = "sc-state-db" version = "0.30.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "log", "parity-scale-codec", @@ -7553,7 +7553,7 @@ dependencies = [ [[package]] name = "sc-sysinfo" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "derive_more", "futures", @@ -7568,13 +7568,13 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sc-telemetry" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "chrono", "futures", @@ -7593,7 +7593,7 @@ dependencies = [ [[package]] name = "sc-tracing" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ansi_term", "chrono", @@ -7613,7 +7613,7 @@ dependencies = [ "sp-core", "sp-rpc", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "tracing", "tracing-log 0.1.4", @@ -7623,7 +7623,7 @@ dependencies = [ [[package]] name = "sc-tracing-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", @@ -7634,7 +7634,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7652,7 +7652,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-runtime", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-transaction-pool", "substrate-prometheus-endpoint", "thiserror", @@ -7661,7 +7661,7 @@ dependencies = [ [[package]] name = "sc-transaction-pool-api" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -7677,7 +7677,7 @@ dependencies = [ [[package]] name = "sc-utils" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-channel", "futures", @@ -8189,7 +8189,7 @@ dependencies = [ [[package]] name = "sp-api" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "log", @@ -8197,12 +8197,12 @@ dependencies = [ "scale-info", "sp-api-proc-macro", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-metadata-ir", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "sp-version", "thiserror", @@ -8211,7 +8211,7 @@ dependencies = [ [[package]] name = "sp-api-proc-macro" version = "15.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "blake2 0.10.6", @@ -8225,20 +8225,20 @@ dependencies = [ [[package]] name = "sp-application-crypto" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", "serde", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sp-arithmetic" version = "23.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "integer-sqrt", @@ -8246,7 +8246,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] @@ -8271,7 +8271,7 @@ dependencies = [ [[package]] name = "sp-block-builder" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-inherents", @@ -8281,7 +8281,7 @@ dependencies = [ [[package]] name = "sp-blockchain" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "futures", "log", @@ -8299,7 +8299,7 @@ dependencies = [ [[package]] name = "sp-consensus" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "futures", @@ -8314,7 +8314,7 @@ dependencies = [ [[package]] name = "sp-consensus-aura" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8330,7 +8330,7 @@ dependencies = [ [[package]] name = "sp-consensus-grandpa" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "finality-grandpa", "log", @@ -8347,7 +8347,7 @@ dependencies = [ [[package]] name = "sp-consensus-slots" version = "0.32.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8358,7 +8358,7 @@ dependencies = [ [[package]] name = "sp-core" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "bandersnatch_vrfs", @@ -8389,11 +8389,11 @@ dependencies = [ "secrecy", "serde", "sp-crypto-hashing", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "ss58-registry", "substrate-bip39", "thiserror", @@ -8425,7 +8425,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "blake2b_simd", "byteorder", @@ -8438,7 +8438,7 @@ dependencies = [ [[package]] name = "sp-crypto-hashing-proc-macro" version = "0.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "quote", "sp-crypto-hashing", @@ -8448,7 +8448,7 @@ dependencies = [ [[package]] name = "sp-database" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "kvdb", "parking_lot 0.12.3", @@ -8457,7 +8457,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "proc-macro2", "quote", @@ -8477,11 +8477,11 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "environmental", "parity-scale-codec", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -8497,7 +8497,7 @@ dependencies = [ [[package]] name = "sp-genesis-builder" version = "0.7.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "serde_json", "sp-api", @@ -8507,7 +8507,7 @@ dependencies = [ [[package]] name = "sp-inherents" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "impl-trait-for-tuples", @@ -8520,7 +8520,7 @@ dependencies = [ [[package]] name = "sp-io" version = "30.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bytes", "ed25519-dalek", @@ -8532,12 +8532,12 @@ dependencies = [ "secp256k1", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-keystore", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-state-machine", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-trie", "tracing", "tracing-core", @@ -8546,7 +8546,7 @@ dependencies = [ [[package]] name = "sp-keyring" version = "31.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-core", "sp-runtime", @@ -8556,18 +8556,18 @@ dependencies = [ [[package]] name = "sp-keystore" version = "0.34.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] name = "sp-maybe-compressed-blob" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "thiserror", "zstd 0.12.4", @@ -8576,7 +8576,7 @@ dependencies = [ [[package]] name = "sp-metadata-ir" version = "0.6.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-metadata", "parity-scale-codec", @@ -8586,7 +8586,7 @@ dependencies = [ [[package]] name = "sp-mixnet" version = "0.4.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8597,7 +8597,7 @@ dependencies = [ [[package]] name = "sp-offchain" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-core", @@ -8607,7 +8607,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "backtrace", "lazy_static", @@ -8617,7 +8617,7 @@ dependencies = [ [[package]] name = "sp-rpc" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "rustc-hash", "serde", @@ -8627,7 +8627,7 @@ dependencies = [ [[package]] name = "sp-runtime" version = "31.0.1" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "docify", "either", @@ -8644,26 +8644,26 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-io", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", ] [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", "polkavm-derive", "primitive-types", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", - "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-runtime-interface-proc-macro 17.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-storage 19.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", + "sp-wasm-interface 20.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "static_assertions", ] @@ -8689,7 +8689,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "Inflector", "expander", @@ -8715,7 +8715,7 @@ dependencies = [ [[package]] name = "sp-session" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "scale-info", @@ -8729,7 +8729,7 @@ dependencies = [ [[package]] name = "sp-staking" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", @@ -8742,7 +8742,7 @@ dependencies = [ [[package]] name = "sp-state-machine" version = "0.35.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hash-db", "log", @@ -8751,7 +8751,7 @@ dependencies = [ "rand", "smallvec", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-panic-handler", "sp-trie", "thiserror", @@ -8762,7 +8762,7 @@ dependencies = [ [[package]] name = "sp-statement-store" version = "10.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "aes-gcm", "curve25519-dalek 4.1.3", @@ -8776,9 +8776,9 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-crypto-hashing", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-runtime", - "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-runtime-interface 24.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "x25519-dalek 2.0.1", ] @@ -8786,7 +8786,7 @@ dependencies = [ [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" [[package]] name = "sp-std" @@ -8796,13 +8796,13 @@ source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06f [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-serde", "parity-scale-codec", "ref-cast", "serde", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -8820,7 +8820,7 @@ dependencies = [ [[package]] name = "sp-timestamp" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8832,7 +8832,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "tracing", @@ -8854,7 +8854,7 @@ dependencies = [ [[package]] name = "sp-transaction-pool" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "sp-api", "sp-runtime", @@ -8863,7 +8863,7 @@ dependencies = [ [[package]] name = "sp-transaction-storage-proof" version = "26.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "async-trait", "parity-scale-codec", @@ -8877,7 +8877,7 @@ dependencies = [ [[package]] name = "sp-trie" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "ahash 0.8.11", "hash-db", @@ -8890,7 +8890,7 @@ dependencies = [ "scale-info", "schnellru", "sp-core", - "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-externalities 0.25.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "thiserror", "tracing", "trie-db", @@ -8900,7 +8900,7 @@ dependencies = [ [[package]] name = "sp-version" version = "29.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8909,7 +8909,7 @@ dependencies = [ "serde", "sp-crypto-hashing-proc-macro", "sp-runtime", - "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version-proc-macro", "thiserror", ] @@ -8917,7 +8917,7 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" version = "13.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "parity-scale-codec", "proc-macro2", @@ -8928,7 +8928,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -8950,7 +8950,7 @@ dependencies = [ [[package]] name = "sp-weights" version = "27.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "bounded-collections", "parity-scale-codec", @@ -8958,7 +8958,7 @@ dependencies = [ "serde", "smallvec", "sp-arithmetic", - "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-debug-derive 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", ] [[package]] @@ -9097,7 +9097,7 @@ dependencies = [ [[package]] name = "substrate-bip39" version = "0.4.7" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -9109,7 +9109,7 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" [[package]] name = "substrate-fixed" @@ -9125,7 +9125,7 @@ dependencies = [ [[package]] name = "substrate-frame-rpc-system" version = "28.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "frame-system-rpc-runtime-api", "futures", @@ -9144,7 +9144,7 @@ dependencies = [ [[package]] name = "substrate-prometheus-endpoint" version = "0.17.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "hyper", "log", @@ -9156,7 +9156,7 @@ dependencies = [ [[package]] name = "substrate-wasm-builder" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2#b53d5c529d403a0bbf36441c3896200c234c058c" +source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2f55dfe06bae13e9f47ccf587acfd3fb9cd923" dependencies = [ "array-bytes 6.2.3", "build-helper", @@ -9172,7 +9172,7 @@ dependencies = [ "sp-core", "sp-io", "sp-maybe-compressed-blob", - "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc2)", + "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-version", "strum 0.26.2", "tempfile", From c80df3d667d8e8212b0d21ea63ce5b493210feac Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 18 Jul 2024 12:11:23 -0400 Subject: [PATCH 028/269] bump CI From 07e46e312b56dd129d5a9b73cb9f5080db1d0e45 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 12:28:42 -0500 Subject: [PATCH 029/269] initial --- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/swap_hotkey.rs | 280 +++++++++++++++++++++++++++ 2 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/swap_hotkey.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f7824e2a3..cfb3a5828 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -367,7 +367,7 @@ pub mod pallet { pub type TotalColdkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] - /// MAP (hot, cold) --> stake | Returns a tuple (u64: stakes, u64: block_number) + /// MAP (hot, cold) --> u64, u64) | Returns a tuple (u64: stakes, u64: block_number) pub type TotalHotkeyColdkeyStakesThisInterval = StorageDoubleMap< _, Identity, diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs new file mode 100644 index 000000000..068b24b78 --- /dev/null +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -0,0 +1,280 @@ +use super::*; +use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; +use frame_support::traits::fungible::Mutate; +use frame_support::traits::tokens::Preservation; +use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use sp_core::{Get, U256}; + +impl Pallet { + /// Swaps the hotkey of a coldkey account. + /// + /// # Arguments + /// + /// * `origin` - The origin of the transaction, and also the coldkey account. + /// * `old_hotkey` - The old hotkey to be swapped. + /// * `new_hotkey` - The new hotkey to replace the old one. + /// + /// # Returns + /// + /// * `DispatchResultWithPostInfo` - The result of the dispatch. + /// + /// # Errors + /// + /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. + /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. + /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. + /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. + /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. + pub fn do_swap_hotkey( + origin: T::RuntimeOrigin, + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + ) -> DispatchResultWithPostInfo { + // Ensure the origin is signed and get the coldkey + let coldkey = ensure_signed(origin)?; + + // Check if the coldkey is in arbitration + ensure!( + !Self::coldkey_in_arbitration(&coldkey), + Error::::ColdkeyIsInArbitration + ); + + // Initialize the weight for this operation + let mut weight = T::DbWeight::get().reads(2); + + // Ensure the new hotkey is different from the old one + ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); + // Ensure the new hotkey is not already registered on any network + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + // Update the weight for the checks above + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); + // Ensure the coldkey owns the old hotkey + ensure!( + Self::coldkey_owns_hotkey(&coldkey, old_hotkey), + Error::::NonAssociatedColdKey + ); + + // Get the current block number + let block: u64 = Self::get_current_block_as_u64(); + // Ensure the transaction rate limit is not exceeded + ensure!( + !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), + Error::::HotKeySetTxRateLimitExceeded + ); + + // Update the weight for reading the total networks + weight.saturating_accrue( + T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), + ); + + // Get the cost for swapping the key + let swap_cost = Self::get_key_swap_cost(); + log::debug!("Swap cost: {:?}", swap_cost); + + // Ensure the coldkey has enough balance to pay for the swap + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + // Remove the swap cost from the coldkey's account + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; + // Burn the tokens + Self::burn_tokens(actual_burn_amount); + + // Perform the hotkey swap + Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); + + // Update the last transaction block for the coldkey + Self::set_last_tx_block(&coldkey, block); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + // Emit an event for the hotkey swap + Self::deposit_event(Event::HotkeySwapped { + coldkey, + old_hotkey: old_hotkey.clone(), + new_hotkey: new_hotkey.clone(), + }); + + // Return the weight of the operation + Ok(Some(weight).into()) + } + + pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) { + + // 1. Swap owner. + // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. + Owner::::remove(old_hotkey); + Owner::::insert(new_hotkey, coldkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 2. Swap OwnedHotkeys. + // OwnedHotkeys( coldkey ) -> Vec -- the hotkeys that the coldkey owns. + let mut hotkeys = OwnedHotkeys::::get(coldkey); + // Add the new key if needed. + if !hotkeys.contains(new_hotkey) { + hotkeys.push(new_hotkey.clone()); + } + // Remove the old key. + hotkeys.retain(|hk| *hk != *old_hotkey); + OwnedHotkeys::::insert(coldkey, hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // 3. Swap total hotkey stake. + // TotalHotkeyStake( hotkey ) -> stake -- the total stake that the hotkey has across all delegates. + let old_total_hotkey_stake = TotalHotkeyStake::::get( old_hotkey ); // Get the old total hotkey stake. + let new_total_hotkey_stake = TotalHotkeyStake::::get( new_hotkey ); // Get the new total hotkey stake. + TotalHotkeyStake::::remove( old_hotkey ); // Remove the old total hotkey stake. + TotalHotkeyStake::::insert( new_hotkey, old_total_hotkey_stake.saturating_add( new_total_hotkey_stake ) ); // Insert the new total hotkey stake via the addition. + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + + // Swap total hotkey stakes. + // TotalHotkeyColdkeyStakesThisInterval( hotkey ) --> (u64: stakes, u64: block_number) + let stake_tuples: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); + for (coldkey, stake_tup) in stake_tuples { + // NOTE: You could use this to increase your allowed stake operations but this would cost. + TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake_tup); + TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // Swap LastTxBlock + // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. + let old_last_tx_block: u64 = LastTxBlock::::get( old_hotkey ); + LastTxBlock::::remove( old_hotkey ); + LastTxBlock::::insert( new_hotkey, Self::get_current_block_as_u64() ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Swap LastTxBlockDelegateTake + // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. + LastTxBlockDelegateTake::::remove( old_hotkey ); + LastTxBlockDelegateTake::::insert( new_hotkey, Self::get_current_block_as_u64() ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Swap Senate members. + // Senate( hotkey ) --> ? + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // 4. Swap delegates. + // Delegates( hotkey ) -> take value -- the hotkey delegate take value. + let old_delegate_take = Delegates::::get( old_hotkey ); + Delegates::::remove( old_hotkey ); // Remove the old delegate take. + Delegates::::insert( new_hotkey, old_delegate_take ); // Insert the new delegate take. + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // Swap all subnet specific info. + let all_netuid: Vec = Self::get_all_subnet_netuids(); + for netuid in all_netuids { + // 7.1 Remove the previous hotkey and insert the new hotkey from membership. + // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. + let is_network_member: bool = IsNetworkMember::::get( old_hotkey, netuid ); + IsNetworkMember::::remove( old_hotkey, netuid ); + IsNetworkMember::::insert( new_hotkey, netuid, is_network_member ); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 7.2 Swap Uids + Keys. + // Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member. + // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. + if is_network_member { + // 7.2.1 Swap the UIDS + let old_uid: u16 = Uids::::get(netuid, old_hotkey); + Uids::::remove(netuid, old_hotkey); + Uids::::insert(netuid, new_hotkey, old_uid); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 7.2.2 Swap the keys. + Keys::::insert(netuid, old_uid, new_hotkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + } + + // 7.3 Swap Prometheus. + // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. + if is_network_member { + let old_prometheus_info: PrometheusInfo = Prometheus::::get(netuid, old_hotkey); + Prometheus::::remove(netuid, old_hotkey); + Prometheus::::insert(netuid, new_hotkey, old_prometheus_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // 7.4. Swap axons. + // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. + if is_network_member { + let old_axon_info: AxonInfo = Axons::::get(netuid, old_hotkey); + Axons::::remove(netuid, old_hotkey); + Axons::::insert(netuid, new_hotkey, old_axon_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + + // 7.5 Swap WeightCommits + // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. + if is_network_member { + if let Ok(old_weight_commits) = WeightCommits::::try_get(old_hotkey) { + WeightCommits::::remove(old_hotkey); + WeightCommits::::insert(new_hotkey, old_weight_commits); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + } + + // 7.5. Swap the subnet loaded emission. + // LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet. + if is_network_member { + if let Some(mut old_loaded_emission) = LoadedEmission::::get(netuid) { + for emission in old_loaded_emission.iter_mut() { + if emission.0 == *old_hotkey { + emission.0 = new_hotkey.clone(); + } + } + LoadedEmission::::remove(netuid); + LoadedEmission::::insert(netuid, old_loaded_emission); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + } + + } + + // Swap Stake. + // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. + let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); + // Clear the entire old prefix here. + let _ = Stake::::clear_prefix( old_hotkey, stakes.len() as u32, None ); + // Iterate over all the staking rows and insert them into the new hotkey. + for (coldkey, old_stake_amount) in stakes { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + + // Swap Stake value + // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. + // Get the new stake value. + let new_stake_value: u64 = Stake::::get(new_hotkey, &coldkey); + // Insert the new stake value. + Stake::::insert(new_hotkey, &coldkey, new_stake_value.saturating_add(old_stake_amount)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // Swap StakingHotkeys. + // StakingHotkeys( coldkey ) --> Vec -- the hotkeys that the coldkey stakes. + let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); + staking_hotkeys.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey); + staking_hotkeys.push(new_hotkey.clone()); + StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + } + } + + pub fn swap_senate_member( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { + weight.saturating_accrue(T::DbWeight::get().reads(1)); + if T::SenateMembers::is_member(old_hotkey) { + T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } + Ok(()) + } +} From 0432b53d428e1f7f5d2b449bbcb0a649e0e6a01d Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 13:26:28 -0500 Subject: [PATCH 030/269] add tests --- pallets/subtensor/src/lib.rs | 35 +- pallets/subtensor/src/swap.rs | 417 +------------- pallets/subtensor/src/swap_hotkey.rs | 47 +- pallets/subtensor/tests/swap.rs | 719 +------------------------ pallets/subtensor/tests/swap_hotkey.rs | 542 +++++++++++++++++++ 5 files changed, 604 insertions(+), 1156 deletions(-) create mode 100644 pallets/subtensor/tests/swap_hotkey.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index cfb3a5828..d492b5477 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -45,6 +45,7 @@ mod root; mod serving; mod staking; mod swap; +mod swap_hotkey; mod uids; mod utils; mod weights; @@ -272,7 +273,7 @@ pub mod pallet { } #[pallet::storage] - pub(super) type SenateRequiredStakePercentage = + pub type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; /// ============================ @@ -796,15 +797,15 @@ pub mod pallet { } #[pallet::storage] // --- ITEM ( tx_rate_limit ) - pub(super) type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; + pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; #[pallet::storage] // --- ITEM ( tx_rate_limit ) - pub(super) type TxDelegateTakeRateLimit = + pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; #[pallet::storage] // --- MAP ( key ) --> last_block pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] // --- MAP ( key ) --> last_block - pub(super) type LastTxBlockDelegateTake = + pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; /// Default value for serving rate limit. @@ -1084,41 +1085,41 @@ pub mod pallet { StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; #[pallet::storage] // --- DMAP ( netuid ) --> active - pub(super) type Active = + pub type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] // --- DMAP ( netuid ) --> rank - pub(super) type Rank = + pub type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> trust - pub(super) type Trust = + pub type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> consensus - pub(super) type Consensus = + pub type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> incentive - pub(super) type Incentive = + pub type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> dividends - pub(super) type Dividends = + pub type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> emission - pub(super) type Emission = + pub type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> last_update - pub(super) type LastUpdate = + pub type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> validator_trust - pub(super) type ValidatorTrust = + pub type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> pruning_scores - pub(super) type PruningScores = + pub type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> validator_permit - pub(super) type ValidatorPermit = + pub type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] // --- DMAP ( netuid, uid ) --> weights - pub(super) type Weights = StorageDoubleMap< + pub type Weights = StorageDoubleMap< _, Identity, u16, @@ -1129,7 +1130,7 @@ pub mod pallet { DefaultWeights, >; #[pallet::storage] // --- DMAP ( netuid, uid ) --> bonds - pub(super) type Bonds = StorageDoubleMap< + pub type Bonds = StorageDoubleMap< _, Identity, u16, diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 8e4ca5cc9..5d8f66c68 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -6,100 +6,6 @@ use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; use sp_core::{Get, U256}; impl Pallet { - /// Swaps the hotkey of a coldkey account. - /// - /// # Arguments - /// - /// * `origin` - The origin of the transaction, and also the coldkey account. - /// * `old_hotkey` - The old hotkey to be swapped. - /// * `new_hotkey` - The new hotkey to replace the old one. - /// - /// # Returns - /// - /// * `DispatchResultWithPostInfo` - The result of the dispatch. - /// - /// # Errors - /// - /// * `NonAssociatedColdKey` - If the coldkey does not own the old hotkey. - /// * `HotKeySetTxRateLimitExceeded` - If the transaction rate limit is exceeded. - /// * `NewHotKeyIsSameWithOld` - If the new hotkey is the same as the old hotkey. - /// * `HotKeyAlreadyRegisteredInSubNet` - If the new hotkey is already registered in the subnet. - /// * `NotEnoughBalanceToPaySwapHotKey` - If there is not enough balance to pay for the swap. - pub fn do_swap_hotkey( - origin: T::RuntimeOrigin, - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - ) -> DispatchResultWithPostInfo { - let coldkey = ensure_signed(origin)?; - - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); - - let mut weight = T::DbWeight::get().reads(2); - - ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); - ensure!( - Self::coldkey_owns_hotkey(&coldkey, old_hotkey), - Error::::NonAssociatedColdKey - ); - - let block: u64 = Self::get_current_block_as_u64(); - ensure!( - !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), - Error::::HotKeySetTxRateLimitExceeded - ); - - weight.saturating_accrue( - T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), - ); - - let swap_cost = Self::get_key_swap_cost(); - log::debug!("Swap cost: {:?}", swap_cost); - - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), - Error::::NotEnoughBalanceToPaySwapHotKey - ); - let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - Self::burn_tokens(actual_burn_amount); - - Self::swap_owner(old_hotkey, new_hotkey, &coldkey, &mut weight); - Self::swap_total_hotkey_stake(old_hotkey, new_hotkey, &mut weight); - Self::swap_delegates(old_hotkey, new_hotkey, &mut weight); - Self::swap_stake(old_hotkey, new_hotkey, &mut weight); - - // Store the value of is_network_member for the old key - let netuid_is_member: Vec = Self::get_netuid_is_member(old_hotkey, &mut weight); - - Self::swap_is_network_member(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_axons(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_keys(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_loaded_emission(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_uids(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_prometheus(old_hotkey, new_hotkey, &netuid_is_member, &mut weight); - Self::swap_senate_member(old_hotkey, new_hotkey, &mut weight)?; - - Self::swap_total_hotkey_coldkey_stakes_this_interval(old_hotkey, new_hotkey, &mut weight); - - Self::set_last_tx_block(&coldkey, block); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - Self::deposit_event(Event::HotkeySwapped { - coldkey, - old_hotkey: old_hotkey.clone(), - new_hotkey: new_hotkey.clone(), - }); - - Ok(Some(weight).into()) - } /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. /// @@ -472,316 +378,6 @@ impl Pallet { netuid_is_member } - /// Swaps the owner of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `coldkey` - The coldkey owning the hotkey. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_owner( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - coldkey: &T::AccountId, - weight: &mut Weight, - ) { - Owner::::remove(old_hotkey); - Owner::::insert(new_hotkey, coldkey.clone()); - - // Update OwnedHotkeys map - let mut hotkeys = OwnedHotkeys::::get(coldkey); - if !hotkeys.contains(new_hotkey) { - hotkeys.push(new_hotkey.clone()); - } - hotkeys.retain(|hk| *hk != *old_hotkey); - OwnedHotkeys::::insert(coldkey, hotkeys); - - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - - /// Swaps the total stake of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. - /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). - pub fn swap_total_hotkey_stake( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - if let Ok(total_hotkey_stake) = TotalHotkeyStake::::try_get(old_hotkey) { - TotalHotkeyStake::::remove(old_hotkey); - TotalHotkeyStake::::insert(new_hotkey, total_hotkey_stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - /// Swaps the delegates of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 if the old hotkey exists, otherwise 1 for the failed read. - /// * Writes: 2 if the old hotkey exists (one for removal and one for insertion). - pub fn swap_delegates( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - if let Ok(delegate_take) = Delegates::::try_get(old_hotkey) { - Delegates::::remove(old_hotkey); - Delegates::::insert(new_hotkey, delegate_take); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - /// Swaps the stake of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - pub fn swap_stake(old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, weight: &mut Weight) { - let mut writes: u64 = 0; - let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); - let stake_count = stakes.len() as u32; - - for (coldkey, stake_amount) in stakes { - Stake::::insert(new_hotkey, &coldkey, stake_amount); - writes = writes.saturating_add(1u64); // One write for insert - - // Update StakingHotkeys map - let mut staking_hotkeys = StakingHotkeys::::get(&coldkey); - if !staking_hotkeys.contains(new_hotkey) { - staking_hotkeys.push(new_hotkey.clone()); - writes = writes.saturating_add(1u64); // One write for insert - } - if let Some(pos) = staking_hotkeys.iter().position(|x| x == old_hotkey) { - staking_hotkeys.remove(pos); - writes = writes.saturating_add(1u64); // One write for remove - } - StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); - writes = writes.saturating_add(1u64); // One write for insert - } - - // Clear the prefix for the old hotkey after transferring all stakes - let _ = Stake::::clear_prefix(old_hotkey, stake_count, None); - writes = writes.saturating_add(1); // One write for insert; // One write for clear_prefix - - // TODO: Remove all entries for old hotkey from StakingHotkeys map - - weight.saturating_accrue(T::DbWeight::get().writes(writes)); - } - - /// Swaps the network membership status of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - pub fn swap_is_network_member( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - let _ = IsNetworkMember::::clear_prefix(old_hotkey, netuid_is_member.len() as u32, None); - weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); - for netuid in netuid_is_member.iter() { - IsNetworkMember::::insert(new_hotkey, netuid, true); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - - /// Swaps the axons of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 for each network ID if the old hotkey exists in that network. - /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). - pub fn swap_axons( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(axon_info) = Axons::::try_get(netuid, old_hotkey) { - Axons::::remove(netuid, old_hotkey); - Axons::::insert(netuid, new_hotkey, axon_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - } - - /// Swaps the references in the keys storage map of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - pub fn swap_keys( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - let mut writes: u64 = 0; - for netuid in netuid_is_member { - let keys: Vec<(u16, T::AccountId)> = Keys::::iter_prefix(netuid).collect(); - for (uid, key) in keys { - if key == *old_hotkey { - log::info!("old hotkey found: {:?}", old_hotkey); - Keys::::insert(netuid, uid, new_hotkey.clone()); - } - writes = writes.saturating_add(2u64); - } - } - log::info!("writes: {:?}", writes); - weight.saturating_accrue(T::DbWeight::get().writes(writes)); - } - - /// Swaps the loaded emission of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_loaded_emission( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member { - if let Some(mut emissions) = LoadedEmission::::get(netuid) { - for emission in emissions.iter_mut() { - if emission.0 == *old_hotkey { - emission.0 = new_hotkey.clone(); - } - } - LoadedEmission::::insert(netuid, emissions); - } - } - weight.saturating_accrue(T::DbWeight::get().writes(netuid_is_member.len() as u64)); - } - - /// Swaps the UIDs of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_uids( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(uid) = Uids::::try_get(netuid, old_hotkey) { - Uids::::remove(netuid, old_hotkey); - Uids::::insert(netuid, new_hotkey, uid); - weight.saturating_accrue(T::DbWeight::get().writes(2)); - } - } - } - - /// Swaps the Prometheus data of the hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `netuid_is_member` - A vector of network IDs where the hotkey is a member. - /// * `weight` - The weight of the transaction. - /// - /// # Weight Calculation - /// - /// * Reads: 1 for each network ID if the old hotkey exists in that network. - /// * Writes: 2 for each network ID if the old hotkey exists in that network (one for removal and one for insertion). - pub fn swap_prometheus( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - netuid_is_member: &[u16], - weight: &mut Weight, - ) { - for netuid in netuid_is_member.iter() { - if let Ok(prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { - Prometheus::::remove(netuid, old_hotkey); - Prometheus::::insert(netuid, new_hotkey, prometheus_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } else { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - } - - /// Swaps the total hotkey-coldkey stakes for the current interval. - /// - /// # Arguments - /// - /// * `old_hotkey` - The old hotkey. - /// * `new_hotkey` - The new hotkey. - /// * `weight` - The weight of the transaction. - /// - pub fn swap_total_hotkey_coldkey_stakes_this_interval( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) { - let stakes: Vec<(T::AccountId, (u64, u64))> = - TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); - log::info!("Stakes to swap: {:?}", stakes); - for (coldkey, stake) in stakes { - log::info!( - "Swapping stake for coldkey: {:?}, stake: {:?}", - coldkey, - stake - ); - TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake); - TotalHotkeyColdkeyStakesThisInterval::::remove(old_hotkey, &coldkey); - weight.saturating_accrue(T::DbWeight::get().writes(2)); // One write for insert and one for remove - } - } /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. /// @@ -1040,16 +636,5 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - pub fn swap_senate_member( - old_hotkey: &T::AccountId, - new_hotkey: &T::AccountId, - weight: &mut Weight, - ) -> DispatchResult { - weight.saturating_accrue(T::DbWeight::get().reads(1)); - if T::SenateMembers::is_member(old_hotkey) { - T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - Ok(()) - } + } diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index 068b24b78..222a4ff37 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -103,7 +103,7 @@ impl Pallet { Ok(Some(weight).into()) } - pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) { + pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) -> DispatchResult { // 1. Swap owner. // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. @@ -169,7 +169,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // Swap all subnet specific info. - let all_netuid: Vec = Self::get_all_subnet_netuids(); + let all_netuids: Vec = Self::get_all_subnet_netuids(); for netuid in all_netuids { // 7.1 Remove the previous hotkey and insert the new hotkey from membership. // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. @@ -183,40 +183,43 @@ impl Pallet { // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. if is_network_member { // 7.2.1 Swap the UIDS - let old_uid: u16 = Uids::::get(netuid, old_hotkey); - Uids::::remove(netuid, old_hotkey); - Uids::::insert(netuid, new_hotkey, old_uid); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - - // 7.2.2 Swap the keys. - Keys::::insert(netuid, old_uid, new_hotkey.clone()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + if let Ok(old_uid) = Uids::::try_get(netuid, old_hotkey) { + Uids::::remove(netuid, old_hotkey); + Uids::::insert(netuid, new_hotkey, old_uid); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // 7.2.2 Swap the keys. + Keys::::insert(netuid, old_uid, new_hotkey.clone()); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); + } } // 7.3 Swap Prometheus. // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. if is_network_member { - let old_prometheus_info: PrometheusInfo = Prometheus::::get(netuid, old_hotkey); - Prometheus::::remove(netuid, old_hotkey); - Prometheus::::insert(netuid, new_hotkey, old_prometheus_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + if let Ok(old_prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { + Prometheus::::remove(netuid, old_hotkey); + Prometheus::::insert(netuid, new_hotkey, old_prometheus_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } } // 7.4. Swap axons. // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. if is_network_member { - let old_axon_info: AxonInfo = Axons::::get(netuid, old_hotkey); - Axons::::remove(netuid, old_hotkey); - Axons::::insert(netuid, new_hotkey, old_axon_info); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + if let Ok(old_axon_info) = Axons::::try_get(netuid, old_hotkey) { + Axons::::remove(netuid, old_hotkey); + Axons::::insert(netuid, new_hotkey, old_axon_info); + weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + } } // 7.5 Swap WeightCommits // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. if is_network_member { - if let Ok(old_weight_commits) = WeightCommits::::try_get(old_hotkey) { - WeightCommits::::remove(old_hotkey); - WeightCommits::::insert(new_hotkey, old_weight_commits); + if let Ok(old_weight_commits) = WeightCommits::::try_get(netuid, old_hotkey) { + WeightCommits::::remove(netuid, old_hotkey); + WeightCommits::::insert(netuid, new_hotkey, old_weight_commits); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } } @@ -263,6 +266,8 @@ impl Pallet { StakingHotkeys::::insert(coldkey.clone(), staking_hotkeys); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + + Ok(()) } pub fn swap_senate_member( diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 21c3a983a..86fe109be 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -9,6 +9,7 @@ use mock::*; use pallet_subtensor::*; use sp_core::U256; +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap -- test_do_swap_hotkey_ok --exact --nocapture #[test] fn test_do_swap_hotkey_ok() { new_test_ext(1).execute_with(|| { @@ -42,15 +43,11 @@ fn test_do_swap_hotkey_ok() { ); // Verify other storage changes - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), - SubtensorModule::get_total_stake_for_hotkey(&old_hotkey) - ); - assert_eq!( + assert_ne!( SubtensorModule::get_delegate(new_hotkey.encode()), SubtensorModule::get_delegate(old_hotkey.encode()) ); - assert_eq!( + assert_ne!( SubtensorModule::get_last_tx_block(&new_hotkey), SubtensorModule::get_last_tx_block(&old_hotkey) ); @@ -62,29 +59,6 @@ fn test_do_swap_hotkey_ok() { } let mut weight = Weight::zero(); - // UIDs - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert_eq!( - Uids::::get(netuid, new_hotkey), - Uids::::get(netuid, old_hotkey) - ); - } - - // Prometheus - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert_eq!( - Prometheus::::get(netuid, new_hotkey), - Prometheus::::get(netuid, old_hotkey) - ); - } - - // LoadedEmission - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert_eq!( - LoadedEmission::::get(netuid).unwrap(), - LoadedEmission::::get(netuid).unwrap() - ); - } // IsNetworkMember for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { @@ -96,7 +70,7 @@ fn test_do_swap_hotkey_ok() { assert_eq!(Owner::::get(new_hotkey), coldkey); // TotalHotkeyStake - assert_eq!( + assert_ne!( TotalHotkeyStake::::get(new_hotkey), TotalHotkeyStake::::get(old_hotkey) ); @@ -129,185 +103,6 @@ fn test_do_swap_hotkey_ok() { }); } -#[test] -fn test_do_swap_hotkey_ok_robust() { - new_test_ext(1).execute_with(|| { - let num_subnets: u16 = 10; - let tempo: u16 = 13; - let swap_cost = 1_000_000_000u64; - - // Create 10 sets of keys - let mut old_hotkeys = vec![]; - let mut new_hotkeys = vec![]; - let mut coldkeys = vec![]; - - for i in 0..10 { - old_hotkeys.push(U256::from(i * 2 + 1)); - new_hotkeys.push(U256::from(i * 2 + 2)); - coldkeys.push(U256::from(i * 2 + 11)); - } - - // Setup initial state - for netuid in 1..=num_subnets { - add_network(netuid, tempo, 0); - SubtensorModule::set_max_registrations_per_block(netuid, 20); - SubtensorModule::set_target_registrations_per_interval(netuid, 1000); - log::info!( - "Registrations this interval for netuid {:?} is {:?}", - netuid, - SubtensorModule::get_target_registrations_per_interval(netuid) - ); - for i in 0..10 { - register_ok_neuron(netuid, old_hotkeys[i], coldkeys[i], 0); - } - } - - // Add balance to coldkeys for swap cost - for coldkey in coldkeys.iter().take(10) { - SubtensorModule::add_balance_to_coldkey_account(coldkey, swap_cost); - } - - // Add old_hotkeys[0] and old_hotkeys[1] to Senate - assert_ok!(SenateMembers::add_member( - RawOrigin::Root.into(), - old_hotkeys[0] - )); - assert_ok!(SenateMembers::add_member( - RawOrigin::Root.into(), - old_hotkeys[1] - )); - - // Verify initial Senate membership - assert!(Senate::is_member(&old_hotkeys[0])); - assert!(Senate::is_member(&old_hotkeys[1])); - assert!(!Senate::is_member(&new_hotkeys[0])); - assert!(!Senate::is_member(&new_hotkeys[1])); - - // Perform the swaps for only two hotkeys - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkeys[0]), - &old_hotkeys[0], - &new_hotkeys[0] - )); - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkeys[1]), - &old_hotkeys[1], - &new_hotkeys[1] - )); - - // Verify the swaps - for netuid in 1..=num_subnets { - for i in 0..10 { - if i == 0 || i == 1 { - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), - coldkeys[i] - ); - assert_ne!( - SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkeys[i]), - coldkeys[i] - ); - - // Verify other storage changes - assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&new_hotkeys[i]), - SubtensorModule::get_total_stake_for_hotkey(&old_hotkeys[i]) - ); - - assert_eq!( - SubtensorModule::get_delegate(new_hotkeys[i].encode()), - SubtensorModule::get_delegate(old_hotkeys[i].encode()) - ); - - assert_eq!( - SubtensorModule::get_last_tx_block(&new_hotkeys[i]), - SubtensorModule::get_last_tx_block(&old_hotkeys[i]) - ); - - // Verify raw storage maps - // Stake - for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkeys[i]) { - assert_eq!(Stake::::get(new_hotkeys[i], coldkey), stake_amount); - } - - let mut weight = Weight::zero(); - // UIDs - for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) - { - assert_eq!( - Uids::::get(netuid, new_hotkeys[i]), - Uids::::get(netuid, old_hotkeys[i]) - ); - } - - // Prometheus - for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) - { - assert_eq!( - Prometheus::::get(netuid, new_hotkeys[i]), - Prometheus::::get(netuid, old_hotkeys[i]) - ); - } - - // LoadedEmission - for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) - { - assert_eq!( - LoadedEmission::::get(netuid).unwrap(), - LoadedEmission::::get(netuid).unwrap() - ); - } - - // IsNetworkMember - for netuid in - SubtensorModule::get_netuid_is_member(&old_hotkeys[i], &mut weight) - { - assert!(IsNetworkMember::::contains_key( - new_hotkeys[i], - netuid - )); - assert!(!IsNetworkMember::::contains_key( - old_hotkeys[i], - netuid - )); - } - - // Owner - assert_eq!(Owner::::get(new_hotkeys[i]), coldkeys[i]); - - // Keys - for (uid, hotkey) in Keys::::iter_prefix(netuid) { - if hotkey == old_hotkeys[i] { - assert_eq!(Keys::::get(netuid, uid), new_hotkeys[i]); - } - } - - // Verify Senate membership swap - assert!(!Senate::is_member(&old_hotkeys[i])); - assert!(Senate::is_member(&new_hotkeys[i])); - } else { - // Ensure other hotkeys remain unchanged - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkeys[i]), - coldkeys[i] - ); - assert_ne!( - SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkeys[i]), - coldkeys[i] - ); - - // Verify Senate membership remains unchanged for other hotkeys - assert!(!Senate::is_member(&old_hotkeys[i])); - assert!(!Senate::is_member(&new_hotkeys[i])); - } - } - } - }); -} - #[test] fn test_swap_hotkey_tx_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { @@ -402,7 +197,7 @@ fn test_swap_owner_success() { Owner::::insert(old_hotkey, coldkey); // Perform the swap - SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -422,7 +217,7 @@ fn test_swap_owner_old_hotkey_not_exist() { assert!(!Owner::::contains_key(old_hotkey)); // Perform the swap - SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -444,7 +239,7 @@ fn test_swap_owner_new_hotkey_already_exists() { Owner::::insert(new_hotkey, another_coldkey); // Perform the swap - SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Owner::::get(new_hotkey), coldkey); @@ -452,31 +247,12 @@ fn test_swap_owner_new_hotkey_already_exists() { }); } -#[test] -fn test_swap_owner_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - // Initialize Owner for old_hotkey - Owner::::insert(old_hotkey, coldkey); - - // Perform the swap - SubtensorModule::swap_owner(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); - assert_eq!(weight, expected_weight); - }); -} - #[test] fn test_swap_total_hotkey_stake_success() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); + let coldkey = U256::from(3); let total_stake = 1000u64; let mut weight = Weight::zero(); @@ -484,7 +260,7 @@ fn test_swap_total_hotkey_stake_success() { TotalHotkeyStake::::insert(old_hotkey, total_stake); // Perform the swap - SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(TotalHotkeyStake::::get(new_hotkey), total_stake); @@ -492,49 +268,14 @@ fn test_swap_total_hotkey_stake_success() { }); } -#[test] -fn test_swap_total_hotkey_stake_old_hotkey_not_exist() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let mut weight = Weight::zero(); - - // Ensure old_hotkey does not exist - assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); - - // Perform the swap - SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); - // Verify that new_hotkey does not have a stake - assert!(!TotalHotkeyStake::::contains_key(new_hotkey)); - }); -} - -#[test] -fn test_swap_total_hotkey_stake_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let total_stake = 1000u64; - let mut weight = Weight::zero(); - - // Initialize TotalHotkeyStake for old_hotkey - TotalHotkeyStake::::insert(old_hotkey, total_stake); - - // Perform the swap - SubtensorModule::swap_total_hotkey_stake(&old_hotkey, &new_hotkey, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} #[test] fn test_swap_delegates_success() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); + let coldkey = U256::from(3); let delegate_take = 10u16; let mut weight = Weight::zero(); @@ -542,7 +283,7 @@ fn test_swap_delegates_success() { Delegates::::insert(old_hotkey, delegate_take); // Perform the swap - SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Delegates::::get(new_hotkey), delegate_take); @@ -550,29 +291,12 @@ fn test_swap_delegates_success() { }); } -#[test] -fn test_swap_delegates_old_hotkey_not_exist() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let mut weight = Weight::zero(); - - // Ensure old_hotkey does not exist - assert!(!Delegates::::contains_key(old_hotkey)); - - // Perform the swap - SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); - - // Verify that new_hotkey does not have a delegate - assert!(!Delegates::::contains_key(new_hotkey)); - }); -} - #[test] fn test_swap_delegates_weight_update() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); + let coldkey = U256::from(3); let delegate_take = 10u16; let mut weight = Weight::zero(); @@ -580,7 +304,7 @@ fn test_swap_delegates_weight_update() { Delegates::::insert(old_hotkey, delegate_take); // Perform the swap - SubtensorModule::swap_delegates(&old_hotkey, &new_hotkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the weight update let expected_weight = ::DbWeight::get().reads_writes(1, 2); @@ -601,7 +325,7 @@ fn test_swap_stake_success() { Stake::::insert(old_hotkey, coldkey, stake_amount); // Perform the swap - SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); @@ -625,7 +349,7 @@ fn test_swap_stake_old_hotkey_not_exist() { assert!(Stake::::contains_key(old_hotkey, coldkey)); // Perform the swap - SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify that new_hotkey has the stake and old_hotkey does not assert!(Stake::::contains_key(new_hotkey, coldkey)); @@ -633,390 +357,6 @@ fn test_swap_stake_old_hotkey_not_exist() { }); } -#[test] -fn test_swap_stake_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let stake_amount = 1000u64; - let mut weight = Weight::zero(); - - // Initialize Stake for old_hotkey - Stake::::insert(old_hotkey, coldkey, stake_amount); - - // Perform the swap - SubtensorModule::swap_stake(&old_hotkey, &new_hotkey, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_is_network_member_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let mut weight = Weight::zero(); - - // Initialize IsNetworkMember for old_hotkey - for netuid in &netuid_is_member { - IsNetworkMember::::insert(old_hotkey, netuid, true); - } - - // Perform the swap - SubtensorModule::swap_is_network_member( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight, - ); - - // Verify the swap - for netuid in &netuid_is_member { - assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); - assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); - } - }); -} - -#[test] -fn test_swap_is_network_member_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let mut weight = Weight::zero(); - - // Initialize IsNetworkMember for old_hotkey - for netuid in &netuid_is_member { - IsNetworkMember::::insert(old_hotkey, netuid, true); - } - - // Perform the swap - SubtensorModule::swap_is_network_member( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight, - ); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_axons_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let axon_info = AxonInfo { - block: 100, - version: 1, - ip: 0x1234567890abcdef, - port: 8080, - ip_type: 4, - protocol: 1, - placeholder1: 0, - placeholder2: 0, - }; - let mut weight = Weight::zero(); - - // Initialize Axons for old_hotkey - for netuid in &netuid_is_member { - Axons::::insert(netuid, old_hotkey, axon_info.clone()); - } - - // Perform the swap - SubtensorModule::swap_axons(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the swap - for netuid in &netuid_is_member { - assert_eq!(Axons::::get(netuid, new_hotkey).unwrap(), axon_info); - assert!(!Axons::::contains_key(netuid, old_hotkey)); - } - }); -} - -#[test] -fn test_swap_axons_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let axon_info = AxonInfo { - block: 100, - version: 1, - ip: 0x1234567890abcdef, - port: 8080, - ip_type: 4, - protocol: 1, - placeholder1: 0, - placeholder2: 0, - }; - let mut weight = Weight::zero(); - - // Initialize Axons for old_hotkey - for netuid in &netuid_is_member { - Axons::::insert(netuid, old_hotkey, axon_info.clone()); - } - - // Perform the swap - SubtensorModule::swap_axons(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the weight update - let expected_weight = netuid_is_member.len() as u64 - * ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_keys_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let uid = 42u16; - let mut weight = Weight::zero(); - - // Initialize Keys for old_hotkey - for netuid in &netuid_is_member { - log::info!("Inserting old_hotkey:{:?} netuid:{:?}", old_hotkey, netuid); - Keys::::insert(*netuid, uid, old_hotkey); - } - - // Perform the swap - SubtensorModule::swap_keys(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the swap - for netuid in &netuid_is_member { - log::info!( - "neutuid, uid, hotkey: {:?}, {:?}, {:?}", - netuid, - uid, - new_hotkey - ); - assert_eq!(Keys::::get(netuid, uid), new_hotkey); - } - }); -} - -#[test] -fn test_swap_keys_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let uid = 42u16; - let mut weight = Weight::zero(); - - // Initialize Keys for old_hotkey - for netuid in &netuid_is_member { - Keys::::insert(*netuid, uid, old_hotkey); - } - - // Perform the swap - SubtensorModule::swap_keys(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_loaded_emission_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let se = 100u64; - let ve = 200u64; - let mut weight = Weight::zero(); - - // Initialize LoadedEmission for old_hotkey - for netuid in &netuid_is_member { - LoadedEmission::::mutate(netuid, |emission_exists| { - if let Some(emissions) = emission_exists { - emissions.push((old_hotkey, se, ve)); - } else { - *emission_exists = Some(vec![(old_hotkey, se, ve)]); - } - }); - } - - // Perform the swap - SubtensorModule::swap_loaded_emission( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight, - ); - - // Verify the swap - for netuid in &netuid_is_member { - let emissions = LoadedEmission::::get(netuid).unwrap(); - assert!(emissions.iter().any(|(hk, _, _)| hk == &new_hotkey)); - assert!(!emissions.iter().any(|(hk, _, _)| hk == &old_hotkey)); - } - }); -} - -#[test] -fn test_swap_loaded_emission_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - // let uid = 42u64; - let se = 100u64; - let ve = 200u64; - let mut weight = Weight::zero(); - - // Initialize LoadedEmission for old_hotkey - for netuid in &netuid_is_member { - LoadedEmission::::mutate(netuid, |emission_exists| { - if let Some(emissions) = emission_exists { - emissions.push((old_hotkey, se, ve)); - } else { - *emission_exists = Some(vec![(old_hotkey, se, ve)]); - } - }); - } - - // Perform the swap - SubtensorModule::swap_loaded_emission( - &old_hotkey, - &new_hotkey, - &netuid_is_member, - &mut weight, - ); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_uids_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let uid = 42u16; - let mut weight = Weight::zero(); - - // Initialize Uids for old_hotkey - for netuid in &netuid_is_member { - Uids::::insert(netuid, old_hotkey, uid); - } - - // Perform the swap - SubtensorModule::swap_uids(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the swap - for netuid in &netuid_is_member { - assert_eq!(Uids::::get(netuid, new_hotkey).unwrap(), uid); - assert!(!Uids::::contains_key(netuid, old_hotkey)); - } - }); -} - -#[test] -fn test_swap_uids_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let uid = 42u16; - let mut weight = Weight::zero(); - - // Initialize Uids for old_hotkey - for netuid in &netuid_is_member { - Uids::::insert(netuid, old_hotkey, uid); - } - - // Perform the swap - SubtensorModule::swap_uids(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(4); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_prometheus_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let prometheus_info = PrometheusInfo { - block: 100, - version: 1, - ip: 0x1234567890abcdef, - port: 8080, - ip_type: 4, - }; - let mut weight = Weight::zero(); - - // Initialize Prometheus for old_hotkey - for netuid in &netuid_is_member { - Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); - } - - // Perform the swap - SubtensorModule::swap_prometheus(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the swap - for netuid in &netuid_is_member { - assert_eq!( - Prometheus::::get(netuid, new_hotkey).unwrap(), - prometheus_info - ); - assert!(!Prometheus::::contains_key(netuid, old_hotkey)); - } - }); -} - -#[test] -fn test_swap_prometheus_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let netuid_is_member = vec![1u16, 2u16]; - let prometheus_info = PrometheusInfo { - block: 100, - version: 1, - ip: 0x1234567890abcdef, - port: 8080, - ip_type: 4, - }; - let mut weight = Weight::zero(); - - // Initialize Prometheus for old_hotkey - for netuid in &netuid_is_member { - Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); - } - - // Perform the swap - SubtensorModule::swap_prometheus(&old_hotkey, &new_hotkey, &netuid_is_member, &mut weight); - - // Verify the weight update - let expected_weight = netuid_is_member.len() as u64 - * ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} - #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { new_test_ext(1).execute_with(|| { @@ -1030,9 +370,10 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap - SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( + SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, + &coldkey, &mut weight, ); @@ -1047,32 +388,6 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { }); } -#[test] -fn test_swap_total_hotkey_coldkey_stakes_this_interval_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let stake = (1000u64, 42u64); - let mut weight = Weight::zero(); - - // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey - TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); - - // Perform the swap - - SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval( - &old_hotkey, - &new_hotkey, - &mut weight, - ); - - // Verify the weight update - let expected_weight = ::DbWeight::get().writes(2); - assert_eq!(weight, expected_weight); - }); -} - #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs new file mode 100644 index 000000000..22e266efa --- /dev/null +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -0,0 +1,542 @@ +#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] + +use codec::Encode; +use frame_support::weights::Weight; +use frame_support::{assert_err, assert_noop, assert_ok}; +use frame_system::{Config, RawOrigin}; +mod mock; +use mock::*; +use pallet_subtensor::*; +use sp_core::U256; +use sp_core::H256; + + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner --exact --nocapture +#[test] +fn test_swap_owner() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + Owner::::insert(&old_hotkey, &coldkey); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(&new_hotkey), coldkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owned_hotkeys --exact --nocapture +#[test] +fn test_swap_owned_hotkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + OwnedHotkeys::::insert(&coldkey, vec![old_hotkey]); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + let hotkeys = OwnedHotkeys::::get(&coldkey); + assert!(!hotkeys.contains(&old_hotkey)); + assert!(hotkeys.contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_total_hotkey_stake --exact --nocapture +#[test] +fn test_swap_total_hotkey_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + TotalHotkeyStake::::insert(&old_hotkey, 100); + TotalHotkeyStake::::insert(&new_hotkey, 50); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 150); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- 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_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, (100, 1000)); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key(&old_hotkey, &coldkey)); + assert_eq!(TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), (100, 1000)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_last_tx_block --exact --nocapture +#[test] +fn test_swap_last_tx_block() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + LastTxBlock::::insert(&old_hotkey, 1000); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!LastTxBlock::::contains_key(&old_hotkey)); + assert_eq!(LastTxBlock::::get(&new_hotkey), SubtensorModule::get_current_block_as_u64()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_last_tx_block_delegate_take --exact --nocapture +#[test] +fn test_swap_last_tx_block_delegate_take() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + pallet_subtensor::LastTxBlockDelegateTake::::insert(&old_hotkey, 1000); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!LastTxBlockDelegateTake::::contains_key(&old_hotkey)); + assert_eq!(LastTxBlockDelegateTake::::get(&new_hotkey), SubtensorModule::get_current_block_as_u64()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_senate_members --exact --nocapture +#[test] +fn test_swap_senate_members() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Assuming there's a way to add a member to the senate + // SenateMembers::add_member(&old_hotkey); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + // Assert that the old_hotkey is no longer a member and new_hotkey is now a member + // assert!(!SenateMembers::is_member(&old_hotkey)); + // assert!(SenateMembers::is_member(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_delegates --exact --nocapture +#[test] +fn test_swap_delegates() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + Delegates::::insert(&old_hotkey, 100); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!Delegates::::contains_key(&old_hotkey)); + assert_eq!(Delegates::::get(&new_hotkey), 100); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_subnet_membership --exact --nocapture +#[test] +fn test_swap_subnet_membership() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); + assert!(IsNetworkMember::::get(&new_hotkey, netuid)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_uids_and_keys --exact --nocapture +#[test] +fn test_swap_uids_and_keys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let uid = 5u16; + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + Uids::::insert(netuid, &old_hotkey, uid); + Keys::::insert(netuid, uid, old_hotkey); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert_eq!(Uids::::get(netuid, &old_hotkey), None); + assert_eq!(Uids::::get(netuid, &new_hotkey), Some(uid)); + assert_eq!(Keys::::get(netuid, uid), new_hotkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_prometheus --exact --nocapture +#[test] +fn test_swap_prometheus() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let prometheus_info = PrometheusInfo::default(); + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); + assert_eq!(Prometheus::::get(netuid, &new_hotkey), Some(prometheus_info)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_axons --exact --nocapture +#[test] +fn test_swap_axons() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let axon_info = AxonInfo::default(); + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!Axons::::contains_key(netuid, &old_hotkey)); + assert_eq!(Axons::::get(netuid, &new_hotkey), Some(axon_info)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_weight_commits --exact --nocapture +#[test] +fn test_swap_weight_commits() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let weight_commits = (H256::from_low_u64_be(100), 200); + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + WeightCommits::::insert(netuid, &old_hotkey, weight_commits.clone()); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!WeightCommits::::contains_key(netuid, &old_hotkey)); + assert_eq!(WeightCommits::::get(netuid, &new_hotkey), Some(weight_commits)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_loaded_emission --exact --nocapture +#[test] +fn test_swap_loaded_emission() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let server_emission = 1000u64; + let validator_emission = 1000u64; + let mut weight = Weight::zero(); + + add_network(netuid, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid, true); + LoadedEmission::::insert(netuid, vec![(old_hotkey, server_emission, validator_emission)]); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + let new_loaded_emission = LoadedEmission::::get(netuid); + assert_eq!(new_loaded_emission, Some(vec![(new_hotkey, server_emission, validator_emission)])); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake --exact --nocapture +#[test] +fn test_swap_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 100u64; + let mut weight = Weight::zero(); + + Stake::::insert(&old_hotkey, &coldkey, stake_amount); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_staking_hotkeys --exact --nocapture +#[test] +fn test_swap_staking_hotkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + Stake::::insert(&old_hotkey, &coldkey, 100); + StakingHotkeys::::insert(&coldkey, vec![old_hotkey]); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + let staking_hotkeys = StakingHotkeys::::get(&coldkey); + assert!(!staking_hotkeys.contains(&old_hotkey)); + assert!(staking_hotkeys.contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_multiple_coldkeys --exact --nocapture +#[test] +fn test_swap_hotkey_with_multiple_coldkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + let mut weight = Weight::zero(); + + Stake::::insert(&old_hotkey, &coldkey1, 100); + Stake::::insert(&old_hotkey, &coldkey2, 200); + StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(&coldkey2, vec![old_hotkey]); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + + assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); + assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); + assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_existing_stake --exact --nocapture +#[test] +fn test_swap_hotkey_with_existing_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + Stake::::insert(&old_hotkey, &coldkey, 100); + Stake::::insert(&new_hotkey, &coldkey, 50); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert_eq!(Stake::::get(&new_hotkey, &coldkey), 150); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_multiple_subnets --exact --nocapture +#[test] +fn test_swap_hotkey_with_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid1 = 0; + let netuid2 = 1; + let mut weight = Weight::zero(); + + add_network(netuid1, 0, 1); + add_network(netuid2, 0, 1); + IsNetworkMember::::insert(&old_hotkey, netuid1, true); + IsNetworkMember::::insert(&old_hotkey, netuid2, true); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_staking_hotkeys_multiple_coldkeys --exact --nocapture +#[test] +fn test_swap_staking_hotkeys_multiple_coldkeys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + let mut weight = Weight::zero(); + + // Set up initial state + Stake::::insert(&old_hotkey, &coldkey1, 100); + Stake::::insert(&old_hotkey, &coldkey2, 200); + StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(&coldkey2, vec![old_hotkey, U256::from(5)]); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + + // Check if new_hotkey replaced old_hotkey in StakingHotkeys + assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(&coldkey1).contains(&old_hotkey)); + + // Check if new_hotkey replaced old_hotkey for coldkey2 as well + assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(&coldkey2).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(&coldkey2).contains(&U256::from(5))); // Other hotkeys should remain + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_no_stake --exact --nocapture +#[test] +fn test_swap_hotkey_with_no_stake() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Set up initial state with no stake + Owner::::insert(&old_hotkey, &coldkey); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + + // Check if ownership transferred + assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(&new_hotkey), coldkey); + + // Ensure no unexpected changes in Stake + assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); + assert!(!Stake::::contains_key(&new_hotkey, &coldkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_multiple_coldkeys_and_subnets --exact --nocapture +#[test] +fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); + let netuid1 = 0; + let netuid2 = 1; + let mut weight = Weight::zero(); + + // Set up initial state + add_network(netuid1, 0, 1); + add_network(netuid2, 0, 1); + Owner::::insert(&old_hotkey, &coldkey1); + Stake::::insert(&old_hotkey, &coldkey1, 100); + Stake::::insert(&old_hotkey, &coldkey2, 200); + IsNetworkMember::::insert(&old_hotkey, netuid1, true); + IsNetworkMember::::insert(&old_hotkey, netuid2, true); + TotalHotkeyStake::::insert(&old_hotkey, 300); + + assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + + // Check ownership transfer + assert!(!Owner::::contains_key(&old_hotkey)); + assert_eq!(Owner::::get(&new_hotkey), coldkey1); + + // Check stake transfer + assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); + assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); + assert!(!Stake::::contains_key(&old_hotkey, &coldkey1)); + assert!(!Stake::::contains_key(&old_hotkey, &coldkey2)); + + // Check subnet membership transfer + assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + + // Check total stake transfer + assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 300); + assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_error_cases --exact --nocapture +#[test] +fn test_swap_hotkey_error_cases() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let wrong_coldkey = U256::from(4); + + // Set up initial state + Owner::::insert(&old_hotkey, &coldkey); + TotalNetworks::::put(1); + LastTxBlock::::insert(&coldkey, 0); + + // Test not enough balance + let swap_cost = SubtensorModule::get_key_swap_cost(); + assert_noop!( + SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey), + Error::::NotEnoughBalanceToPaySwapHotKey + ); + + let initial_balance = SubtensorModule::get_key_swap_cost() + 1000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance); + + // Test new hotkey same as old + assert_noop!( + SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &old_hotkey), + Error::::NewHotKeyIsSameWithOld + ); + + // Test new hotkey already registered + IsNetworkMember::::insert(&new_hotkey, 0, true); + assert_noop!( + SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + IsNetworkMember::::remove(&new_hotkey, 0); + + // Test non-associated coldkey + assert_noop!( + SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(wrong_coldkey), &old_hotkey, &new_hotkey), + Error::::NonAssociatedColdKey + ); + + + // Run the successful swap + assert_ok!(SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey)); + + // Check balance after swap + assert_eq!(Balances::free_balance(&coldkey), initial_balance - swap_cost); + }); +} From 7f938f7940810831203169062de60440a7caacc6 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 13:34:03 -0500 Subject: [PATCH 031/269] tests pass --- pallets/subtensor/src/swap_hotkey.rs | 12 +- pallets/subtensor/tests/swap.rs | 378 ------------------------- pallets/subtensor/tests/swap_hotkey.rs | 275 ++++++++++++++++++ 3 files changed, 279 insertions(+), 386 deletions(-) diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index 222a4ff37..a99e4b3dc 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -1,9 +1,6 @@ use super::*; -use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; -use frame_support::traits::fungible::Mutate; -use frame_support::traits::tokens::Preservation; -use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; -use sp_core::{Get, U256}; +use frame_support::weights::Weight; +use sp_core::Get; impl Pallet { /// Swaps the hotkey of a coldkey account. @@ -86,7 +83,7 @@ impl Pallet { Self::burn_tokens(actual_burn_amount); // Perform the hotkey swap - Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); + let _ = Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); // Update the last transaction block for the coldkey Self::set_last_tx_block(&coldkey, block); @@ -143,10 +140,9 @@ impl Pallet { // Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. - let old_last_tx_block: u64 = LastTxBlock::::get( old_hotkey ); LastTxBlock::::remove( old_hotkey ); LastTxBlock::::insert( new_hotkey, Self::get_current_block_as_u64() ); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); // Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index 86fe109be..f7f288cc2 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -9,384 +9,6 @@ use mock::*; use pallet_subtensor::*; use sp_core::U256; -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap -- test_do_swap_hotkey_ok --exact --nocapture -#[test] -fn test_do_swap_hotkey_ok() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let swap_cost = 1_000_000_000u64; - - // Setup initial state - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, old_hotkey, coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); - - // Perform the swap - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &old_hotkey, - &new_hotkey - )); - - // Verify the swap - assert_eq!( - SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey), - coldkey - ); - assert_ne!( - SubtensorModule::get_owning_coldkey_for_hotkey(&old_hotkey), - coldkey - ); - - // Verify other storage changes - assert_ne!( - SubtensorModule::get_delegate(new_hotkey.encode()), - SubtensorModule::get_delegate(old_hotkey.encode()) - ); - assert_ne!( - SubtensorModule::get_last_tx_block(&new_hotkey), - SubtensorModule::get_last_tx_block(&old_hotkey) - ); - - // Verify raw storage maps - // Stake - for (coldkey, stake_amount) in Stake::::iter_prefix(old_hotkey) { - assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); - } - - let mut weight = Weight::zero(); - - // IsNetworkMember - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert!(IsNetworkMember::::contains_key(new_hotkey, netuid)); - assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); - } - - // Owner - assert_eq!(Owner::::get(new_hotkey), coldkey); - - // TotalHotkeyStake - assert_ne!( - TotalHotkeyStake::::get(new_hotkey), - TotalHotkeyStake::::get(old_hotkey) - ); - - // Delegates - assert_eq!( - Delegates::::get(new_hotkey), - Delegates::::get(old_hotkey) - ); - - // LastTxBlock - assert_eq!( - LastTxBlock::::get(new_hotkey), - LastTxBlock::::get(old_hotkey) - ); - - // Axons - for netuid in SubtensorModule::get_netuid_is_member(&old_hotkey, &mut weight) { - assert_eq!( - Axons::::get(netuid, new_hotkey), - Axons::::get(netuid, old_hotkey) - ); - } - - // TotalHotkeyColdkeyStakesThisInterval - assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), - TotalHotkeyColdkeyStakesThisInterval::::get(old_hotkey, coldkey) - ); - }); -} - -#[test] -fn test_swap_hotkey_tx_rate_limit_exceeded() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let old_hotkey = U256::from(1); - let new_hotkey_1 = U256::from(2); - let new_hotkey_2 = U256::from(4); - let coldkey = U256::from(3); - let swap_cost = 1_000_000_000u64 * 2; - - let tx_rate_limit = 1; - - // Get the current transaction rate limit - let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); - log::info!("current_tx_rate_limit: {:?}", current_tx_rate_limit); - - // Set the transaction rate limit - SubtensorModule::set_tx_rate_limit(tx_rate_limit); - // assert the rate limit is set to 1000 blocks - assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); - - // Setup initial state - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, old_hotkey, coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); - - // Perform the first swap - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &old_hotkey, - &new_hotkey_1 - )); - - // Attempt to perform another swap immediately, which should fail due to rate limit - assert_err!( - SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &new_hotkey_1, - &new_hotkey_2 - ), - Error::::HotKeySetTxRateLimitExceeded - ); - - // move in time past the rate limit - step_block(1001); - assert_ok!(SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(coldkey), - &new_hotkey_1, - &new_hotkey_2 - )); - }); -} - -#[test] -fn test_do_swap_hotkey_err_not_owner() { - new_test_ext(1).execute_with(|| { - let netuid: u16 = 1; - let tempo: u16 = 13; - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let not_owner_coldkey = U256::from(4); - let swap_cost = 1_000_000_000u64; - - // Setup initial state - add_network(netuid, tempo, 0); - register_ok_neuron(netuid, old_hotkey, coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(¬_owner_coldkey, swap_cost); - - // Attempt the swap with a non-owner coldkey - assert_err!( - SubtensorModule::do_swap_hotkey( - <::RuntimeOrigin>::signed(not_owner_coldkey), - &old_hotkey, - &new_hotkey - ), - Error::::NonAssociatedColdKey - ); - }); -} - -#[test] -fn test_swap_owner_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - // Initialize Owner for old_hotkey - Owner::::insert(old_hotkey, coldkey); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(new_hotkey), coldkey); - assert!(!Owner::::contains_key(old_hotkey)); - }); -} - -#[test] -fn test_swap_owner_old_hotkey_not_exist() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let mut weight = Weight::zero(); - - // Ensure old_hotkey does not exist - assert!(!Owner::::contains_key(old_hotkey)); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(new_hotkey), coldkey); - assert!(!Owner::::contains_key(old_hotkey)); - }); -} - -#[test] -fn test_swap_owner_new_hotkey_already_exists() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let another_coldkey = U256::from(4); - let mut weight = Weight::zero(); - - // Initialize Owner for old_hotkey and new_hotkey - Owner::::insert(old_hotkey, coldkey); - Owner::::insert(new_hotkey, another_coldkey); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Owner::::get(new_hotkey), coldkey); - assert!(!Owner::::contains_key(old_hotkey)); - }); -} - -#[test] -fn test_swap_total_hotkey_stake_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let total_stake = 1000u64; - let mut weight = Weight::zero(); - - // Initialize TotalHotkeyStake for old_hotkey - TotalHotkeyStake::::insert(old_hotkey, total_stake); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(TotalHotkeyStake::::get(new_hotkey), total_stake); - assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); - }); -} - - - -#[test] -fn test_swap_delegates_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let delegate_take = 10u16; - let mut weight = Weight::zero(); - - // Initialize Delegates for old_hotkey - Delegates::::insert(old_hotkey, delegate_take); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Delegates::::get(new_hotkey), delegate_take); - assert!(!Delegates::::contains_key(old_hotkey)); - }); -} - -#[test] -fn test_swap_delegates_weight_update() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let delegate_take = 10u16; - let mut weight = Weight::zero(); - - // Initialize Delegates for old_hotkey - Delegates::::insert(old_hotkey, delegate_take); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the weight update - let expected_weight = ::DbWeight::get().reads_writes(1, 2); - assert_eq!(weight, expected_weight); - }); -} - -#[test] -fn test_swap_stake_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let stake_amount = 1000u64; - let mut weight = Weight::zero(); - - // Initialize Stake for old_hotkey - Stake::::insert(old_hotkey, coldkey, stake_amount); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify the swap - assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); - assert!(!Stake::::contains_key(old_hotkey, coldkey)); - }); -} - -#[test] -fn test_swap_stake_old_hotkey_not_exist() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let stake_amount = 1000u64; - let mut weight = Weight::zero(); - - // Initialize Stake for old_hotkey - Stake::::insert(old_hotkey, coldkey, stake_amount); - - // Ensure old_hotkey has a stake - assert!(Stake::::contains_key(old_hotkey, coldkey)); - - // Perform the swap - SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); - - // Verify that new_hotkey has the stake and old_hotkey does not - assert!(Stake::::contains_key(new_hotkey, coldkey)); - assert!(!Stake::::contains_key(old_hotkey, coldkey)); - }); -} - -#[test] -fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { - new_test_ext(1).execute_with(|| { - let old_hotkey = U256::from(1); - let new_hotkey = U256::from(2); - let coldkey = U256::from(3); - let stake = (1000u64, 42u64); // Example tuple value - let mut weight = Weight::zero(); - - // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey - TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); - - // Perform the swap - SubtensorModule::perform_hotkey_swap( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight, - ); - - // Verify the swap - assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), - stake - ); - assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - old_hotkey, coldkey - )); - }); -} #[test] fn test_do_swap_coldkey_success() { diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 22e266efa..35227d3b1 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -488,6 +488,281 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_tx_rate_limit_exceeded --exact --nocapture +#[test] +fn test_swap_hotkey_tx_rate_limit_exceeded() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey_1 = U256::from(2); + let new_hotkey_2 = U256::from(4); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64 * 2; + + let tx_rate_limit = 1; + + // Get the current transaction rate limit + let current_tx_rate_limit = SubtensorModule::get_tx_rate_limit(); + log::info!("current_tx_rate_limit: {:?}", current_tx_rate_limit); + + // Set the transaction rate limit + SubtensorModule::set_tx_rate_limit(tx_rate_limit); + // assert the rate limit is set to 1000 blocks + assert_eq!(SubtensorModule::get_tx_rate_limit(), tx_rate_limit); + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + + // Perform the first swap + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey_1 + )); + + // Attempt to perform another swap immediately, which should fail due to rate limit + assert_err!( + SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey_1, + &new_hotkey_2 + ), + Error::::HotKeySetTxRateLimitExceeded + ); + + // move in time past the rate limit + step_block(1001); + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &new_hotkey_1, + &new_hotkey_2 + )); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_do_swap_hotkey_err_not_owner --exact --nocapture +#[test] +fn test_do_swap_hotkey_err_not_owner() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let not_owner_coldkey = U256::from(4); + let swap_cost = 1_000_000_000u64; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(¬_owner_coldkey, swap_cost); + + // Attempt the swap with a non-owner coldkey + assert_err!( + SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(not_owner_coldkey), + &old_hotkey, + &new_hotkey + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner_success --exact --nocapture +#[test] +fn test_swap_owner_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Initialize Owner for old_hotkey + Owner::::insert(old_hotkey, coldkey); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner_old_hotkey_not_exist --exact --nocapture +#[test] +fn test_swap_owner_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let mut weight = Weight::zero(); + + // Ensure old_hotkey does not exist + assert!(!Owner::::contains_key(old_hotkey)); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner_new_hotkey_already_exists --exact --nocapture +#[test] +fn test_swap_owner_new_hotkey_already_exists() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let another_coldkey = U256::from(4); + let mut weight = Weight::zero(); + + // Initialize Owner for old_hotkey and new_hotkey + Owner::::insert(old_hotkey, coldkey); + Owner::::insert(new_hotkey, another_coldkey); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(Owner::::get(new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + }); +} + + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_total_hotkey_stake_success --exact --nocapture +#[test] +fn test_swap_total_hotkey_stake_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let total_stake = 1000u64; + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyStake for old_hotkey + TotalHotkeyStake::::insert(old_hotkey, total_stake); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(TotalHotkeyStake::::get(new_hotkey), total_stake); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); + }); +} + + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_delegates_success --exact --nocapture +#[test] +fn test_swap_delegates_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let delegate_take = 10u16; + let mut weight = Weight::zero(); + + // Initialize Delegates for old_hotkey + Delegates::::insert(old_hotkey, delegate_take); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(Delegates::::get(new_hotkey), delegate_take); + assert!(!Delegates::::contains_key(old_hotkey)); + }); +} + + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake_success --exact --nocapture +#[test] +fn test_swap_stake_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_hotkey + Stake::::insert(old_hotkey, coldkey, stake_amount); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake_old_hotkey_not_exist --exact --nocapture +#[test] +fn test_swap_stake_old_hotkey_not_exist() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake_amount = 1000u64; + let mut weight = Weight::zero(); + + // Initialize Stake for old_hotkey + Stake::::insert(old_hotkey, coldkey, stake_amount); + + // Ensure old_hotkey has a stake + assert!(Stake::::contains_key(old_hotkey, coldkey)); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify that new_hotkey has the stake and old_hotkey does not + assert!(Stake::::contains_key(new_hotkey, coldkey)); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_total_hotkey_coldkey_stakes_this_interval_success --exact --nocapture +#[test] +fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let stake = (1000u64, 42u64); // Example tuple value + let mut weight = Weight::zero(); + + // Initialize TotalHotkeyColdkeyStakesThisInterval for old_hotkey + TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); + + // Perform the swap + SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight, + ); + + // Verify the swap + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), + stake + ); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + old_hotkey, coldkey + )); + }); +} + // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_error_cases --exact --nocapture #[test] fn test_swap_hotkey_error_cases() { From 659b53c5319c53ae31d5a05aca37d72e67eadd32 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 13:42:23 -0500 Subject: [PATCH 032/269] clean the code --- pallets/commitments/src/lib.rs | 9 +++ pallets/subtensor/src/swap_hotkey.rs | 106 ++++++++++++++++++--------- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index d5d132034..4663b2647 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -173,6 +173,15 @@ pub mod pallet { Ok(()) } + + pub fn swap_commitment( netuid: u16, new_hotkey: T::AccountId, old_hotkey: T::AccountId ) { + if let Some(commitment) = >::take(netuid, &old_hotkey) { + >::insert(netuid, &new_hotkey, commitment); + } + if let Some(last_commit) = >::take(netuid, &old_hotkey) { + >::insert(netuid, &new_hotkey, last_commit); + } + } } } diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index a99e4b3dc..02052e4dd 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -27,79 +27,118 @@ impl Pallet { old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - // Ensure the origin is signed and get the coldkey + // 1. Ensure the origin is signed and get the coldkey let coldkey = ensure_signed(origin)?; - // Check if the coldkey is in arbitration + // 2. Check if the coldkey is in arbitration ensure!( !Self::coldkey_in_arbitration(&coldkey), Error::::ColdkeyIsInArbitration ); - // Initialize the weight for this operation + // 3. Initialize the weight for this operation let mut weight = T::DbWeight::get().reads(2); - // Ensure the new hotkey is different from the old one + // 4. Ensure the new hotkey is different from the old one ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - // Ensure the new hotkey is not already registered on any network + + // 5. Ensure the new hotkey is not already registered on any network ensure!( !Self::is_hotkey_registered_on_any_network(new_hotkey), Error::::HotKeyAlreadyRegisteredInSubNet ); - // Update the weight for the checks above + // 6. Update the weight for the checks above weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); - // Ensure the coldkey owns the old hotkey + + // 7. Ensure the coldkey owns the old hotkey ensure!( Self::coldkey_owns_hotkey(&coldkey, old_hotkey), Error::::NonAssociatedColdKey ); - // Get the current block number + // 8. Get the current block number let block: u64 = Self::get_current_block_as_u64(); - // Ensure the transaction rate limit is not exceeded + + // 9. Ensure the transaction rate limit is not exceeded ensure!( !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), Error::::HotKeySetTxRateLimitExceeded ); - // Update the weight for reading the total networks + // 10. Update the weight for reading the total networks weight.saturating_accrue( T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); - // Get the cost for swapping the key + // 11. Get the cost for swapping the key let swap_cost = Self::get_key_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); - // Ensure the coldkey has enough balance to pay for the swap + // 12. Ensure the coldkey has enough balance to pay for the swap ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapHotKey ); - // Remove the swap cost from the coldkey's account + + // 13. Remove the swap cost from the coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - // Burn the tokens + + // 14. Burn the tokens Self::burn_tokens(actual_burn_amount); - // Perform the hotkey swap + // 15. Perform the hotkey swap let _ = Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); - // Update the last transaction block for the coldkey + // 16. Update the last transaction block for the coldkey Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); - // Emit an event for the hotkey swap + // 17. Emit an event for the hotkey swap Self::deposit_event(Event::HotkeySwapped { coldkey, old_hotkey: old_hotkey.clone(), new_hotkey: new_hotkey.clone(), }); - // Return the weight of the operation + // 18. Return the weight of the operation Ok(Some(weight).into()) } + /// Performs the hotkey swap operation, transferring all associated data and state from the old hotkey to the new hotkey. + /// + /// This function executes a series of steps to ensure a complete transfer of all relevant information: + /// 1. Swaps the owner of the hotkey. + /// 2. Updates the list of owned hotkeys for the coldkey. + /// 3. Transfers the total hotkey stake. + /// 4. Moves all stake-related data for the interval. + /// 5. Updates the last transaction block for the new hotkey. + /// 6. Transfers the delegate take information. + /// 7. Swaps Senate membership if applicable. + /// 8. Updates delegate information. + /// 9. For each subnet: + /// - Updates network membership status. + /// - Transfers UID and key information. + /// - Moves Prometheus data. + /// - Updates axon information. + /// - Transfers weight commits. + /// - Updates loaded emission data. + /// 10. Transfers all stake information, including updating staking hotkeys for each coldkey. + /// + /// Throughout the process, the function accumulates the computational weight of operations performed. + /// + /// # Arguments + /// * `old_hotkey` - The AccountId of the current hotkey to be replaced. + /// * `new_hotkey` - The AccountId of the new hotkey to replace the old one. + /// * `coldkey` - The AccountId of the coldkey that owns both hotkeys. + /// * `weight` - A mutable reference to the Weight, updated as operations are performed. + /// + /// # Returns + /// * `DispatchResult` - Ok(()) if the swap was successful, or an error if any operation failed. + /// + /// # Note + /// This function performs extensive storage reads and writes, which can be computationally expensive. + /// The accumulated weight should be carefully considered in the context of block limits. pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) -> DispatchResult { // 1. Swap owner. @@ -128,7 +167,7 @@ impl Pallet { TotalHotkeyStake::::insert( new_hotkey, old_total_hotkey_stake.saturating_add( new_total_hotkey_stake ) ); // Insert the new total hotkey stake via the addition. weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - // Swap total hotkey stakes. + // 4. Swap total hotkey stakes. // TotalHotkeyColdkeyStakesThisInterval( hotkey ) --> (u64: stakes, u64: block_number) let stake_tuples: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); for (coldkey, stake_tup) in stake_tuples { @@ -138,59 +177,59 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - // Swap LastTxBlock + // 5. Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. LastTxBlock::::remove( old_hotkey ); LastTxBlock::::insert( new_hotkey, Self::get_current_block_as_u64() ); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); - // Swap LastTxBlockDelegateTake + // 6. Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. LastTxBlockDelegateTake::::remove( old_hotkey ); LastTxBlockDelegateTake::::insert( new_hotkey, Self::get_current_block_as_u64() ); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // Swap Senate members. + // 7. Swap Senate members. // Senate( hotkey ) --> ? if T::SenateMembers::is_member(old_hotkey) { T::SenateMembers::swap_member(old_hotkey, new_hotkey).map_err(|e| e.error)?; weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } - // 4. Swap delegates. + // 8. Swap delegates. // Delegates( hotkey ) -> take value -- the hotkey delegate take value. let old_delegate_take = Delegates::::get( old_hotkey ); Delegates::::remove( old_hotkey ); // Remove the old delegate take. Delegates::::insert( new_hotkey, old_delegate_take ); // Insert the new delegate take. weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // Swap all subnet specific info. + // 9. Swap all subnet specific info. let all_netuids: Vec = Self::get_all_subnet_netuids(); for netuid in all_netuids { - // 7.1 Remove the previous hotkey and insert the new hotkey from membership. + // 9.1 Remove the previous hotkey and insert the new hotkey from membership. // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. let is_network_member: bool = IsNetworkMember::::get( old_hotkey, netuid ); IsNetworkMember::::remove( old_hotkey, netuid ); IsNetworkMember::::insert( new_hotkey, netuid, is_network_member ); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 7.2 Swap Uids + Keys. + // 9.2 Swap Uids + Keys. // Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member. // Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has. if is_network_member { - // 7.2.1 Swap the UIDS + // 9.2.1 Swap the UIDS if let Ok(old_uid) = Uids::::try_get(netuid, old_hotkey) { Uids::::remove(netuid, old_hotkey); Uids::::insert(netuid, new_hotkey, old_uid); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - // 7.2.2 Swap the keys. + // 9.2.2 Swap the keys. Keys::::insert(netuid, old_uid, new_hotkey.clone()); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1)); } } - // 7.3 Swap Prometheus. + // 9.3 Swap Prometheus. // Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network. if is_network_member { if let Ok(old_prometheus_info) = Prometheus::::try_get(netuid, old_hotkey) { @@ -200,7 +239,7 @@ impl Pallet { } } - // 7.4. Swap axons. + // 9.4. Swap axons. // Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has. if is_network_member { if let Ok(old_axon_info) = Axons::::try_get(netuid, old_hotkey) { @@ -210,7 +249,7 @@ impl Pallet { } } - // 7.5 Swap WeightCommits + // 9.5 Swap WeightCommits // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. if is_network_member { if let Ok(old_weight_commits) = WeightCommits::::try_get(netuid, old_hotkey) { @@ -220,7 +259,7 @@ impl Pallet { } } - // 7.5. Swap the subnet loaded emission. + // 9.6. Swap the subnet loaded emission. // LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet. if is_network_member { if let Some(mut old_loaded_emission) = LoadedEmission::::get(netuid) { @@ -237,7 +276,7 @@ impl Pallet { } - // Swap Stake. + // 10. Swap Stake. // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); // Clear the entire old prefix here. @@ -263,6 +302,7 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + // Return successful after swapping all the relevant terms. Ok(()) } From b8cfd6ce51967283b9610c81ef31bd9d2b6198e4 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 13:45:59 -0500 Subject: [PATCH 033/269] fix tests --- pallets/commitments/src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index 4663b2647..d5d132034 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -173,15 +173,6 @@ pub mod pallet { Ok(()) } - - pub fn swap_commitment( netuid: u16, new_hotkey: T::AccountId, old_hotkey: T::AccountId ) { - if let Some(commitment) = >::take(netuid, &old_hotkey) { - >::insert(netuid, &new_hotkey, commitment); - } - if let Some(last_commit) = >::take(netuid, &old_hotkey) { - >::insert(netuid, &new_hotkey, last_commit); - } - } } } From 6b6460d4fc7864543af574d7390c53fa3556691d Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 15:21:13 -0500 Subject: [PATCH 034/269] clippy and fmt --- pallets/subtensor/src/lib.rs | 6 +- pallets/subtensor/src/swap.rs | 4 - pallets/subtensor/src/swap_hotkey.rs | 57 +++--- pallets/subtensor/tests/swap.rs | 1 - pallets/subtensor/tests/swap_hotkey.rs | 248 ++++++++++++++++++++----- 5 files changed, 234 insertions(+), 82 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d492b5477..6a353eafb 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1088,11 +1088,9 @@ pub mod pallet { pub type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] // --- DMAP ( netuid ) --> rank - pub type Rank = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> trust - pub type Trust = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; + pub type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] // --- DMAP ( netuid ) --> consensus pub type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap.rs index 5d8f66c68..b6b0e9ba7 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap.rs @@ -6,7 +6,6 @@ use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; use sp_core::{Get, U256}; impl Pallet { - /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. /// /// # Arguments @@ -378,7 +377,6 @@ impl Pallet { netuid_is_member } - /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. /// /// # Arguments @@ -635,6 +633,4 @@ impl Pallet { } weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); } - - } diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index 02052e4dd..5c3fecbba 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -139,8 +139,12 @@ impl Pallet { /// # Note /// This function performs extensive storage reads and writes, which can be computationally expensive. /// The accumulated weight should be carefully considered in the context of block limits. - pub fn perform_hotkey_swap( old_hotkey: &T::AccountId, new_hotkey: &T::AccountId, coldkey: &T::AccountId, weight: &mut Weight ) -> DispatchResult { - + pub fn perform_hotkey_swap( + old_hotkey: &T::AccountId, + new_hotkey: &T::AccountId, + coldkey: &T::AccountId, + weight: &mut Weight, + ) -> DispatchResult { // 1. Swap owner. // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. Owner::::remove(old_hotkey); @@ -153,7 +157,7 @@ impl Pallet { // Add the new key if needed. if !hotkeys.contains(new_hotkey) { hotkeys.push(new_hotkey.clone()); - } + } // Remove the old key. hotkeys.retain(|hk| *hk != *old_hotkey); OwnedHotkeys::::insert(coldkey, hotkeys); @@ -161,15 +165,19 @@ impl Pallet { // 3. Swap total hotkey stake. // TotalHotkeyStake( hotkey ) -> stake -- the total stake that the hotkey has across all delegates. - let old_total_hotkey_stake = TotalHotkeyStake::::get( old_hotkey ); // Get the old total hotkey stake. - let new_total_hotkey_stake = TotalHotkeyStake::::get( new_hotkey ); // Get the new total hotkey stake. - TotalHotkeyStake::::remove( old_hotkey ); // Remove the old total hotkey stake. - TotalHotkeyStake::::insert( new_hotkey, old_total_hotkey_stake.saturating_add( new_total_hotkey_stake ) ); // Insert the new total hotkey stake via the addition. + let old_total_hotkey_stake = TotalHotkeyStake::::get(old_hotkey); // Get the old total hotkey stake. + let new_total_hotkey_stake = TotalHotkeyStake::::get(new_hotkey); // Get the new total hotkey stake. + TotalHotkeyStake::::remove(old_hotkey); // Remove the old total hotkey stake. + TotalHotkeyStake::::insert( + new_hotkey, + old_total_hotkey_stake.saturating_add(new_total_hotkey_stake), + ); // Insert the new total hotkey stake via the addition. weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); // 4. Swap total hotkey stakes. // TotalHotkeyColdkeyStakesThisInterval( hotkey ) --> (u64: stakes, u64: block_number) - let stake_tuples: Vec<(T::AccountId, (u64, u64))> = TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); + let stake_tuples: Vec<(T::AccountId, (u64, u64))> = + TotalHotkeyColdkeyStakesThisInterval::::iter_prefix(old_hotkey).collect(); for (coldkey, stake_tup) in stake_tuples { // NOTE: You could use this to increase your allowed stake operations but this would cost. TotalHotkeyColdkeyStakesThisInterval::::insert(new_hotkey, &coldkey, stake_tup); @@ -179,14 +187,14 @@ impl Pallet { // 5. Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. - LastTxBlock::::remove( old_hotkey ); - LastTxBlock::::insert( new_hotkey, Self::get_current_block_as_u64() ); + LastTxBlock::::remove(old_hotkey); + LastTxBlock::::insert(new_hotkey, Self::get_current_block_as_u64()); weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2)); // 6. Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. - LastTxBlockDelegateTake::::remove( old_hotkey ); - LastTxBlockDelegateTake::::insert( new_hotkey, Self::get_current_block_as_u64() ); + LastTxBlockDelegateTake::::remove(old_hotkey); + LastTxBlockDelegateTake::::insert(new_hotkey, Self::get_current_block_as_u64()); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // 7. Swap Senate members. @@ -198,19 +206,19 @@ impl Pallet { // 8. Swap delegates. // Delegates( hotkey ) -> take value -- the hotkey delegate take value. - let old_delegate_take = Delegates::::get( old_hotkey ); - Delegates::::remove( old_hotkey ); // Remove the old delegate take. - Delegates::::insert( new_hotkey, old_delegate_take ); // Insert the new delegate take. + let old_delegate_take = Delegates::::get(old_hotkey); + Delegates::::remove(old_hotkey); // Remove the old delegate take. + Delegates::::insert(new_hotkey, old_delegate_take); // Insert the new delegate take. weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // 9. Swap all subnet specific info. let all_netuids: Vec = Self::get_all_subnet_netuids(); - for netuid in all_netuids { + for netuid in all_netuids { // 9.1 Remove the previous hotkey and insert the new hotkey from membership. // IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member. - let is_network_member: bool = IsNetworkMember::::get( old_hotkey, netuid ); - IsNetworkMember::::remove( old_hotkey, netuid ); - IsNetworkMember::::insert( new_hotkey, netuid, is_network_member ); + let is_network_member: bool = IsNetworkMember::::get(old_hotkey, netuid); + IsNetworkMember::::remove(old_hotkey, netuid); + IsNetworkMember::::insert(new_hotkey, netuid, is_network_member); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); // 9.2 Swap Uids + Keys. @@ -249,7 +257,7 @@ impl Pallet { } } - // 9.5 Swap WeightCommits + // 9.5 Swap WeightCommits // WeightCommits( hotkey ) --> Vec -- the weight commits for the hotkey. if is_network_member { if let Ok(old_weight_commits) = WeightCommits::::try_get(netuid, old_hotkey) { @@ -273,14 +281,13 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); } } - } // 10. Swap Stake. // Stake( hotkey, coldkey ) -> stake -- the stake that the hotkey controls on behalf of the coldkey. let stakes: Vec<(T::AccountId, u64)> = Stake::::iter_prefix(old_hotkey).collect(); // Clear the entire old prefix here. - let _ = Stake::::clear_prefix( old_hotkey, stakes.len() as u32, None ); + let _ = Stake::::clear_prefix(old_hotkey, stakes.len() as u32, None); // Iterate over all the staking rows and insert them into the new hotkey. for (coldkey, old_stake_amount) in stakes { weight.saturating_accrue(T::DbWeight::get().reads(1)); @@ -290,7 +297,11 @@ impl Pallet { // Get the new stake value. let new_stake_value: u64 = Stake::::get(new_hotkey, &coldkey); // Insert the new stake value. - Stake::::insert(new_hotkey, &coldkey, new_stake_value.saturating_add(old_stake_amount)); + Stake::::insert( + new_hotkey, + &coldkey, + new_stake_value.saturating_add(old_stake_amount), + ); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // Swap StakingHotkeys. diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap.rs index f7f288cc2..0a3a85dff 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap.rs @@ -9,7 +9,6 @@ use mock::*; use pallet_subtensor::*; use sp_core::U256; - #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 35227d3b1..68f4ec49c 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -7,9 +7,8 @@ use frame_system::{Config, RawOrigin}; mod mock; use mock::*; use pallet_subtensor::*; -use sp_core::U256; use sp_core::H256; - +use sp_core::U256; // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_owner --exact --nocapture #[test] @@ -21,7 +20,12 @@ fn test_swap_owner() { let mut weight = Weight::zero(); Owner::::insert(&old_hotkey, &coldkey); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!Owner::::contains_key(&old_hotkey)); assert_eq!(Owner::::get(&new_hotkey), coldkey); @@ -38,7 +42,12 @@ fn test_swap_owned_hotkeys() { let mut weight = Weight::zero(); OwnedHotkeys::::insert(&coldkey, vec![old_hotkey]); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); let hotkeys = OwnedHotkeys::::get(&coldkey); assert!(!hotkeys.contains(&old_hotkey)); @@ -57,7 +66,12 @@ fn test_swap_total_hotkey_stake() { TotalHotkeyStake::::insert(&old_hotkey, 100); TotalHotkeyStake::::insert(&new_hotkey, 50); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 150); @@ -74,10 +88,21 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { let mut weight = Weight::zero(); TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, (100, 1000)); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); - assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key(&old_hotkey, &coldkey)); - assert_eq!(TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), (100, 1000)); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + &old_hotkey, + &coldkey + )); + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + (100, 1000) + ); }); } @@ -91,10 +116,18 @@ fn test_swap_last_tx_block() { let mut weight = Weight::zero(); LastTxBlock::::insert(&old_hotkey, 1000); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!LastTxBlock::::contains_key(&old_hotkey)); - assert_eq!(LastTxBlock::::get(&new_hotkey), SubtensorModule::get_current_block_as_u64()); + assert_eq!( + LastTxBlock::::get(&new_hotkey), + SubtensorModule::get_current_block_as_u64() + ); }); } @@ -108,10 +141,18 @@ fn test_swap_last_tx_block_delegate_take() { let mut weight = Weight::zero(); pallet_subtensor::LastTxBlockDelegateTake::::insert(&old_hotkey, 1000); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!LastTxBlockDelegateTake::::contains_key(&old_hotkey)); - assert_eq!(LastTxBlockDelegateTake::::get(&new_hotkey), SubtensorModule::get_current_block_as_u64()); + assert_eq!( + LastTxBlockDelegateTake::::get(&new_hotkey), + SubtensorModule::get_current_block_as_u64() + ); }); } @@ -126,7 +167,12 @@ fn test_swap_senate_members() { // Assuming there's a way to add a member to the senate // SenateMembers::add_member(&old_hotkey); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); // Assert that the old_hotkey is no longer a member and new_hotkey is now a member // assert!(!SenateMembers::is_member(&old_hotkey)); @@ -144,7 +190,12 @@ fn test_swap_delegates() { let mut weight = Weight::zero(); Delegates::::insert(&old_hotkey, 100); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!Delegates::::contains_key(&old_hotkey)); assert_eq!(Delegates::::get(&new_hotkey), 100); @@ -163,7 +214,12 @@ fn test_swap_subnet_membership() { add_network(netuid, 0, 1); IsNetworkMember::::insert(&old_hotkey, netuid, true); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); assert!(IsNetworkMember::::get(&new_hotkey, netuid)); @@ -186,7 +242,12 @@ fn test_swap_uids_and_keys() { Uids::::insert(netuid, &old_hotkey, uid); Keys::::insert(netuid, uid, old_hotkey); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert_eq!(Uids::::get(netuid, &old_hotkey), None); assert_eq!(Uids::::get(netuid, &new_hotkey), Some(uid)); @@ -209,10 +270,18 @@ fn test_swap_prometheus() { IsNetworkMember::::insert(&old_hotkey, netuid, true); Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); - assert_eq!(Prometheus::::get(netuid, &new_hotkey), Some(prometheus_info)); + assert_eq!( + Prometheus::::get(netuid, &new_hotkey), + Some(prometheus_info) + ); }); } @@ -231,13 +300,18 @@ fn test_swap_axons() { IsNetworkMember::::insert(&old_hotkey, netuid, true); Axons::::insert(netuid, &old_hotkey, axon_info.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!Axons::::contains_key(netuid, &old_hotkey)); assert_eq!(Axons::::get(netuid, &new_hotkey), Some(axon_info)); }); } - + // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_weight_commits --exact --nocapture #[test] fn test_swap_weight_commits() { @@ -253,10 +327,18 @@ fn test_swap_weight_commits() { IsNetworkMember::::insert(&old_hotkey, netuid, true); WeightCommits::::insert(netuid, &old_hotkey, weight_commits.clone()); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!WeightCommits::::contains_key(netuid, &old_hotkey)); - assert_eq!(WeightCommits::::get(netuid, &new_hotkey), Some(weight_commits)); + assert_eq!( + WeightCommits::::get(netuid, &new_hotkey), + Some(weight_commits) + ); }); } @@ -274,12 +356,23 @@ fn test_swap_loaded_emission() { add_network(netuid, 0, 1); IsNetworkMember::::insert(&old_hotkey, netuid, true); - LoadedEmission::::insert(netuid, vec![(old_hotkey, server_emission, validator_emission)]); + LoadedEmission::::insert( + netuid, + vec![(old_hotkey, server_emission, validator_emission)], + ); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); let new_loaded_emission = LoadedEmission::::get(netuid); - assert_eq!(new_loaded_emission, Some(vec![(new_hotkey, server_emission, validator_emission)])); + assert_eq!( + new_loaded_emission, + Some(vec![(new_hotkey, server_emission, validator_emission)]) + ); }); } @@ -295,7 +388,12 @@ fn test_swap_stake() { Stake::::insert(&old_hotkey, &coldkey, stake_amount); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); @@ -314,7 +412,12 @@ fn test_swap_staking_hotkeys() { Stake::::insert(&old_hotkey, &coldkey, 100); StakingHotkeys::::insert(&coldkey, vec![old_hotkey]); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); let staking_hotkeys = StakingHotkeys::::get(&coldkey); assert!(!staking_hotkeys.contains(&old_hotkey)); @@ -337,7 +440,12 @@ fn test_swap_hotkey_with_multiple_coldkeys() { StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); StakingHotkeys::::insert(&coldkey2, vec![old_hotkey]); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey1, + &mut weight + )); assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); @@ -358,7 +466,12 @@ fn test_swap_hotkey_with_existing_stake() { Stake::::insert(&old_hotkey, &coldkey, 100); Stake::::insert(&new_hotkey, &coldkey, 50); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert_eq!(Stake::::get(&new_hotkey, &coldkey), 150); }); @@ -380,7 +493,12 @@ fn test_swap_hotkey_with_multiple_subnets() { IsNetworkMember::::insert(&old_hotkey, netuid1, true); IsNetworkMember::::insert(&old_hotkey, netuid2, true); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); @@ -405,7 +523,12 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); StakingHotkeys::::insert(&coldkey2, vec![old_hotkey, U256::from(5)]); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey1, + &mut weight + )); // Check if new_hotkey replaced old_hotkey in StakingHotkeys assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); @@ -414,7 +537,8 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { // Check if new_hotkey replaced old_hotkey for coldkey2 as well assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); assert!(!StakingHotkeys::::get(&coldkey2).contains(&old_hotkey)); - assert!(StakingHotkeys::::get(&coldkey2).contains(&U256::from(5))); // Other hotkeys should remain + assert!(StakingHotkeys::::get(&coldkey2).contains(&U256::from(5))); + // Other hotkeys should remain }); } @@ -430,7 +554,12 @@ fn test_swap_hotkey_with_no_stake() { // Set up initial state with no stake Owner::::insert(&old_hotkey, &coldkey); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey, + &mut weight + )); // Check if ownership transferred assert!(!Owner::::contains_key(&old_hotkey)); @@ -464,7 +593,12 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { IsNetworkMember::::insert(&old_hotkey, netuid2, true); TotalHotkeyStake::::insert(&old_hotkey, 300); - assert_ok!(SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey1, &mut weight)); + assert_ok!(SubtensorModule::perform_hotkey_swap( + &old_hotkey, + &new_hotkey, + &coldkey1, + &mut weight + )); // Check ownership transfer assert!(!Owner::::contains_key(&old_hotkey)); @@ -637,7 +771,6 @@ fn test_swap_owner_new_hotkey_already_exists() { }); } - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_total_hotkey_stake_success --exact --nocapture #[test] fn test_swap_total_hotkey_stake_success() { @@ -660,7 +793,6 @@ fn test_swap_total_hotkey_stake_success() { }); } - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_delegates_success --exact --nocapture #[test] fn test_swap_delegates_success() { @@ -683,7 +815,6 @@ fn test_swap_delegates_success() { }); } - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_stake_success --exact --nocapture #[test] fn test_swap_stake_success() { @@ -745,12 +876,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_success() { TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, stake); // Perform the swap - SubtensorModule::perform_hotkey_swap( - &old_hotkey, - &new_hotkey, - &coldkey, - &mut weight, - ); + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify the swap assert_eq!( @@ -780,7 +906,11 @@ fn test_swap_hotkey_error_cases() { // Test not enough balance let swap_cost = SubtensorModule::get_key_swap_cost(); assert_noop!( - SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey), + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey + ), Error::::NotEnoughBalanceToPaySwapHotKey ); @@ -789,29 +919,47 @@ fn test_swap_hotkey_error_cases() { // Test new hotkey same as old assert_noop!( - SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &old_hotkey), + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &old_hotkey + ), Error::::NewHotKeyIsSameWithOld ); // Test new hotkey already registered IsNetworkMember::::insert(&new_hotkey, 0, true); assert_noop!( - SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey), + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey + ), Error::::HotKeyAlreadyRegisteredInSubNet ); IsNetworkMember::::remove(&new_hotkey, 0); // Test non-associated coldkey assert_noop!( - SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(wrong_coldkey), &old_hotkey, &new_hotkey), + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(wrong_coldkey), + &old_hotkey, + &new_hotkey + ), Error::::NonAssociatedColdKey ); - // Run the successful swap - assert_ok!(SubtensorModule::do_swap_hotkey(RuntimeOrigin::signed(coldkey), &old_hotkey, &new_hotkey)); + assert_ok!(SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey + )); // Check balance after swap - assert_eq!(Balances::free_balance(&coldkey), initial_balance - swap_cost); + assert_eq!( + Balances::free_balance(&coldkey), + initial_balance - swap_cost + ); }); } From fde92e4cd146c84ffe9db58782609606493b2290 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 23 Jul 2024 01:15:48 +0400 Subject: [PATCH 035/269] chore: bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..0aa7bddbe 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 165, + spec_version: 188, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From a00d130be871055631df53eb426021b46e0b2463 Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 16:29:54 -0500 Subject: [PATCH 036/269] bring back hotkey swap --- pallets/subtensor/src/lib.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6a353eafb..daaf20b00 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2065,17 +2065,17 @@ pub mod pallet { } /// The extrinsic for user to change its hotkey - ///#[pallet::call_index(70)] - ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) - ///.saturating_add(T::DbWeight::get().reads(272)) - ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - ///pub fn swap_hotkey( - /// origin: OriginFor, - /// hotkey: T::AccountId, - /// new_hotkey: T::AccountId, - ///) -> DispatchResultWithPostInfo { - /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) - ///} + #[pallet::call_index(70)] + #[pallet::weight((Weight::from_parts(1_940_000_000, 0) + .saturating_add(T::DbWeight::get().reads(272)) + .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + pub fn swap_hotkey( + origin: OriginFor, + hotkey: T::AccountId, + new_hotkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + } /// The extrinsic for user to change the coldkey associated with their account. /// From 9b9c594c58ec5d4540b9f20c07796673ddccce5e Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 16:30:19 -0500 Subject: [PATCH 037/269] pump to 189 --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..db8134423 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 165, + spec_version: 189, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 385f0bf1efc00e58b13c86cf15a0a2b22909b8a2 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 23 Jul 2024 01:36:41 +0400 Subject: [PATCH 038/269] chore: lint --- pallets/subtensor/tests/swap_hotkey.rs | 204 ++++++++++++------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 68f4ec49c..990382918 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -19,7 +19,7 @@ fn test_swap_owner() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -27,8 +27,8 @@ fn test_swap_owner() { &mut weight )); - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); }); } @@ -41,7 +41,7 @@ fn test_swap_owned_hotkeys() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - OwnedHotkeys::::insert(&coldkey, vec![old_hotkey]); + OwnedHotkeys::::insert(coldkey, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -49,7 +49,7 @@ fn test_swap_owned_hotkeys() { &mut weight )); - let hotkeys = OwnedHotkeys::::get(&coldkey); + let hotkeys = OwnedHotkeys::::get(coldkey); assert!(!hotkeys.contains(&old_hotkey)); assert!(hotkeys.contains(&new_hotkey)); }); @@ -64,8 +64,8 @@ fn test_swap_total_hotkey_stake() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - TotalHotkeyStake::::insert(&old_hotkey, 100); - TotalHotkeyStake::::insert(&new_hotkey, 50); + TotalHotkeyStake::::insert(old_hotkey, 100); + TotalHotkeyStake::::insert(new_hotkey, 50); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -73,8 +73,8 @@ fn test_swap_total_hotkey_stake() { &mut weight )); - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); - assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 150); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(new_hotkey), 150); }); } @@ -87,7 +87,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, (100, 1000)); + TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, (100, 1000)); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -96,11 +96,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { )); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - &old_hotkey, - &coldkey + old_hotkey, + coldkey )); assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), (100, 1000) ); }); @@ -115,7 +115,7 @@ fn test_swap_last_tx_block() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - LastTxBlock::::insert(&old_hotkey, 1000); + LastTxBlock::::insert(old_hotkey, 1000); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -123,9 +123,9 @@ fn test_swap_last_tx_block() { &mut weight )); - assert!(!LastTxBlock::::contains_key(&old_hotkey)); + assert!(!LastTxBlock::::contains_key(old_hotkey)); assert_eq!( - LastTxBlock::::get(&new_hotkey), + LastTxBlock::::get(new_hotkey), SubtensorModule::get_current_block_as_u64() ); }); @@ -140,7 +140,7 @@ fn test_swap_last_tx_block_delegate_take() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - pallet_subtensor::LastTxBlockDelegateTake::::insert(&old_hotkey, 1000); + pallet_subtensor::LastTxBlockDelegateTake::::insert(old_hotkey, 1000); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -148,9 +148,9 @@ fn test_swap_last_tx_block_delegate_take() { &mut weight )); - assert!(!LastTxBlockDelegateTake::::contains_key(&old_hotkey)); + assert!(!LastTxBlockDelegateTake::::contains_key(old_hotkey)); assert_eq!( - LastTxBlockDelegateTake::::get(&new_hotkey), + LastTxBlockDelegateTake::::get(new_hotkey), SubtensorModule::get_current_block_as_u64() ); }); @@ -189,7 +189,7 @@ fn test_swap_delegates() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Delegates::::insert(&old_hotkey, 100); + Delegates::::insert(old_hotkey, 100); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -197,8 +197,8 @@ fn test_swap_delegates() { &mut weight )); - assert!(!Delegates::::contains_key(&old_hotkey)); - assert_eq!(Delegates::::get(&new_hotkey), 100); + assert!(!Delegates::::contains_key(old_hotkey)); + assert_eq!(Delegates::::get(new_hotkey), 100); }); } @@ -213,7 +213,7 @@ fn test_swap_subnet_membership() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -221,8 +221,8 @@ fn test_swap_subnet_membership() { &mut weight )); - assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); + assert!(IsNetworkMember::::get(new_hotkey, netuid)); }); } @@ -238,8 +238,8 @@ fn test_swap_uids_and_keys() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Uids::::insert(netuid, &old_hotkey, uid); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Uids::::insert(netuid, old_hotkey, uid); Keys::::insert(netuid, uid, old_hotkey); assert_ok!(SubtensorModule::perform_hotkey_swap( @@ -249,8 +249,8 @@ fn test_swap_uids_and_keys() { &mut weight )); - assert_eq!(Uids::::get(netuid, &old_hotkey), None); - assert_eq!(Uids::::get(netuid, &new_hotkey), Some(uid)); + assert_eq!(Uids::::get(netuid, old_hotkey), None); + assert_eq!(Uids::::get(netuid, new_hotkey), Some(uid)); assert_eq!(Keys::::get(netuid, uid), new_hotkey); }); } @@ -267,8 +267,8 @@ fn test_swap_prometheus() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -277,9 +277,9 @@ fn test_swap_prometheus() { &mut weight )); - assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); + assert!(!Prometheus::::contains_key(netuid, old_hotkey)); assert_eq!( - Prometheus::::get(netuid, &new_hotkey), + Prometheus::::get(netuid, new_hotkey), Some(prometheus_info) ); }); @@ -297,8 +297,8 @@ fn test_swap_axons() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Axons::::insert(netuid, old_hotkey, axon_info.clone()); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -307,8 +307,8 @@ fn test_swap_axons() { &mut weight )); - assert!(!Axons::::contains_key(netuid, &old_hotkey)); - assert_eq!(Axons::::get(netuid, &new_hotkey), Some(axon_info)); + assert!(!Axons::::contains_key(netuid, old_hotkey)); + assert_eq!(Axons::::get(netuid, new_hotkey), Some(axon_info)); }); } @@ -324,8 +324,8 @@ fn test_swap_weight_commits() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - WeightCommits::::insert(netuid, &old_hotkey, weight_commits.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + WeightCommits::::insert(netuid, old_hotkey, weight_commits); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -334,9 +334,9 @@ fn test_swap_weight_commits() { &mut weight )); - assert!(!WeightCommits::::contains_key(netuid, &old_hotkey)); + assert!(!WeightCommits::::contains_key(netuid, old_hotkey)); assert_eq!( - WeightCommits::::get(netuid, &new_hotkey), + WeightCommits::::get(netuid, new_hotkey), Some(weight_commits) ); }); @@ -355,7 +355,7 @@ fn test_swap_loaded_emission() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); LoadedEmission::::insert( netuid, vec![(old_hotkey, server_emission, validator_emission)], @@ -386,7 +386,7 @@ fn test_swap_stake() { let stake_amount = 100u64; let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, stake_amount); + Stake::::insert(old_hotkey, coldkey, stake_amount); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -395,8 +395,8 @@ fn test_swap_stake() { &mut weight )); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); - assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); }); } @@ -409,8 +409,8 @@ fn test_swap_staking_hotkeys() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, 100); - StakingHotkeys::::insert(&coldkey, vec![old_hotkey]); + Stake::::insert(old_hotkey, coldkey, 100); + StakingHotkeys::::insert(coldkey, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -419,7 +419,7 @@ fn test_swap_staking_hotkeys() { &mut weight )); - let staking_hotkeys = StakingHotkeys::::get(&coldkey); + let staking_hotkeys = StakingHotkeys::::get(coldkey); assert!(!staking_hotkeys.contains(&old_hotkey)); assert!(staking_hotkeys.contains(&new_hotkey)); }); @@ -435,10 +435,10 @@ fn test_swap_hotkey_with_multiple_coldkeys() { let coldkey2 = U256::from(4); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); - StakingHotkeys::::insert(&coldkey2, vec![old_hotkey]); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -447,10 +447,10 @@ fn test_swap_hotkey_with_multiple_coldkeys() { &mut weight )); - assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); - assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); - assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); - assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); + assert_eq!(Stake::::get(new_hotkey, coldkey1), 100); + assert_eq!(Stake::::get(new_hotkey, coldkey2), 200); + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); }); } @@ -463,8 +463,8 @@ fn test_swap_hotkey_with_existing_stake() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, 100); - Stake::::insert(&new_hotkey, &coldkey, 50); + Stake::::insert(old_hotkey, coldkey, 100); + Stake::::insert(new_hotkey, coldkey, 50); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -473,7 +473,7 @@ fn test_swap_hotkey_with_existing_stake() { &mut weight )); - assert_eq!(Stake::::get(&new_hotkey, &coldkey), 150); + assert_eq!(Stake::::get(new_hotkey, coldkey), 150); }); } @@ -490,8 +490,8 @@ fn test_swap_hotkey_with_multiple_subnets() { add_network(netuid1, 0, 1); add_network(netuid2, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid1, true); - IsNetworkMember::::insert(&old_hotkey, netuid2, true); + IsNetworkMember::::insert(old_hotkey, netuid1, true); + IsNetworkMember::::insert(old_hotkey, netuid2, true); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -500,10 +500,10 @@ fn test_swap_hotkey_with_multiple_subnets() { &mut weight )); - assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + assert!(IsNetworkMember::::get(new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); }); } @@ -518,10 +518,10 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { let mut weight = Weight::zero(); // Set up initial state - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); - StakingHotkeys::::insert(&coldkey2, vec![old_hotkey, U256::from(5)]); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey, U256::from(5)]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -531,13 +531,13 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { )); // Check if new_hotkey replaced old_hotkey in StakingHotkeys - assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); - assert!(!StakingHotkeys::::get(&coldkey1).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(coldkey1).contains(&old_hotkey)); // Check if new_hotkey replaced old_hotkey for coldkey2 as well - assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); - assert!(!StakingHotkeys::::get(&coldkey2).contains(&old_hotkey)); - assert!(StakingHotkeys::::get(&coldkey2).contains(&U256::from(5))); + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(coldkey2).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&U256::from(5))); // Other hotkeys should remain }); } @@ -552,7 +552,7 @@ fn test_swap_hotkey_with_no_stake() { let mut weight = Weight::zero(); // Set up initial state with no stake - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -562,12 +562,12 @@ fn test_swap_hotkey_with_no_stake() { )); // Check if ownership transferred - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); // Ensure no unexpected changes in Stake - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); - assert!(!Stake::::contains_key(&new_hotkey, &coldkey)); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + assert!(!Stake::::contains_key(new_hotkey, coldkey)); }); } @@ -586,12 +586,12 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { // Set up initial state add_network(netuid1, 0, 1); add_network(netuid2, 0, 1); - Owner::::insert(&old_hotkey, &coldkey1); - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - IsNetworkMember::::insert(&old_hotkey, netuid1, true); - IsNetworkMember::::insert(&old_hotkey, netuid2, true); - TotalHotkeyStake::::insert(&old_hotkey, 300); + Owner::::insert(old_hotkey, coldkey1); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + IsNetworkMember::::insert(old_hotkey, netuid1, true); + IsNetworkMember::::insert(old_hotkey, netuid2, true); + TotalHotkeyStake::::insert(old_hotkey, 300); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -601,24 +601,24 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { )); // Check ownership transfer - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey1); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey1); // Check stake transfer - assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); - assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey1)); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey2)); + assert_eq!(Stake::::get(new_hotkey, coldkey1), 100); + assert_eq!(Stake::::get(new_hotkey, coldkey2), 200); + assert!(!Stake::::contains_key(old_hotkey, coldkey1)); + assert!(!Stake::::contains_key(old_hotkey, coldkey2)); // Check subnet membership transfer - assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + assert!(IsNetworkMember::::get(new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); // Check total stake transfer - assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 300); - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(new_hotkey), 300); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); }); } @@ -899,9 +899,9 @@ fn test_swap_hotkey_error_cases() { let wrong_coldkey = U256::from(4); // Set up initial state - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); TotalNetworks::::put(1); - LastTxBlock::::insert(&coldkey, 0); + LastTxBlock::::insert(coldkey, 0); // Test not enough balance let swap_cost = SubtensorModule::get_key_swap_cost(); @@ -928,7 +928,7 @@ fn test_swap_hotkey_error_cases() { ); // Test new hotkey already registered - IsNetworkMember::::insert(&new_hotkey, 0, true); + IsNetworkMember::::insert(new_hotkey, 0, true); assert_noop!( SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), @@ -937,7 +937,7 @@ fn test_swap_hotkey_error_cases() { ), Error::::HotKeyAlreadyRegisteredInSubNet ); - IsNetworkMember::::remove(&new_hotkey, 0); + IsNetworkMember::::remove(new_hotkey, 0); // Test non-associated coldkey assert_noop!( @@ -958,7 +958,7 @@ fn test_swap_hotkey_error_cases() { // Check balance after swap assert_eq!( - Balances::free_balance(&coldkey), + Balances::free_balance(coldkey), initial_balance - swap_cost ); }); From 59d9cbdc6b2034778fd238fa5c250d8801a4eda3 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 23 Jul 2024 02:06:56 +0400 Subject: [PATCH 039/269] chore: update spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..4079860b6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 165, + spec_version: 190, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 336321f122c3a7dc6f867cac18c34890665c937d Mon Sep 17 00:00:00 2001 From: const Date: Mon, 22 Jul 2024 18:00:34 -0500 Subject: [PATCH 040/269] clean coldkey swap --- pallets/subtensor/src/lib.rs | 8 +- .../src/{swap.rs => swap_coldkey.rs} | 673 +++++++++--------- pallets/subtensor/src/swap_hotkey.rs | 1 + .../tests/{swap.rs => swap_coldkey.rs} | 418 ++++++++++- 4 files changed, 733 insertions(+), 367 deletions(-) rename pallets/subtensor/src/{swap.rs => swap_coldkey.rs} (50%) rename pallets/subtensor/tests/{swap.rs => swap_coldkey.rs} (63%) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index daaf20b00..baed738ea 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -44,7 +44,7 @@ mod registration; mod root; mod serving; mod staking; -mod swap; +mod swap_coldkey; mod swap_hotkey; mod uids; mod utils; @@ -726,7 +726,7 @@ pub mod pallet { #[pallet::storage] // --- MAP ( netuid ) --> last_mechanism_step_block pub type LastMechansimStepBlock = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultLastMechanismStepBlock>; - #[pallet::storage] // --- MAP ( netuid ) --> subnet_owner + #[pallet::storage] // --- MAP ( netuid ) --> (cold) subnet_owner pub type SubnetOwner = StorageMap<_, Identity, u16, T::AccountId, ValueQuery, DefaultSubnetOwner>; #[pallet::storage] // --- MAP ( netuid ) --> subnet_locked @@ -801,10 +801,10 @@ pub mod pallet { #[pallet::storage] // --- ITEM ( tx_rate_limit ) pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; - #[pallet::storage] // --- MAP ( key ) --> last_block + #[pallet::storage] // --- MAP ( hotkey ) --> last_block pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; - #[pallet::storage] // --- MAP ( key ) --> last_block + #[pallet::storage] // --- MAP ( hotkey ) --> last_block pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; diff --git a/pallets/subtensor/src/swap.rs b/pallets/subtensor/src/swap_coldkey.rs similarity index 50% rename from pallets/subtensor/src/swap.rs rename to pallets/subtensor/src/swap_coldkey.rs index b6b0e9ba7..9a5ad515b 100644 --- a/pallets/subtensor/src/swap.rs +++ b/pallets/subtensor/src/swap_coldkey.rs @@ -1,17 +1,16 @@ + use super::*; use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; -use frame_support::traits::fungible::Mutate; -use frame_support::traits::tokens::Preservation; -use frame_support::{storage::IterableStorageDoubleMap, weights::Weight}; +use frame_support::weights::Weight; use sp_core::{Get, U256}; impl Pallet { + /// 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. - /// * `old_coldkey` - The account ID of the old coldkey. /// * `new_coldkey` - The account ID of the new coldkey. /// /// # Returns @@ -21,10 +20,9 @@ impl Pallet { /// # Errors /// /// This function will return an error if: - /// - The caller is not the old coldkey. - /// - The new coldkey is the same as the old coldkey. - /// - The new coldkey is already associated with other hotkeys. - /// - The transaction rate limit for coldkey swaps has been exceeded. + /// - 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 @@ -38,56 +36,201 @@ impl Pallet { origin: T::RuntimeOrigin, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { + // 1. Ensure the origin is signed and get the old coldkey let old_coldkey = ensure_signed(origin)?; + + // 2. Check if the old coldkey is in arbitration ensure!( !Self::coldkey_in_arbitration(&old_coldkey), Error::::ColdkeyIsInArbitration ); + // 3. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); - // Check that the coldkey is a new key (does not exist elsewhere.) + // 4. Ensure the new coldkey is not associated with any hotkeys ensure!( - !Self::coldkey_has_associated_hotkeys(new_coldkey), + StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); - // Check that the new coldkey is not a hotkey. + + // 5. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::ColdKeyAlreadyAssociated ); - // Calculate and charge the swap fee + // 6. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); log::debug!("Coldkey swap cost: {:?}", swap_cost); - ensure!( Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey ); - let actual_burn_amount = - Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + + // 7. Remove and burn the swap cost from the old coldkey's account + let actual_burn_amount = Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); + // 8. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // Actually do the swap. - weight = weight.saturating_add( - Self::perform_swap_coldkey(&old_coldkey, new_coldkey) - .map_err(|_| Error::::ColdkeySwapError)?, - ); + // 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. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), }); + // 12. Return the result with the updated weight Ok(Some(weight).into()) } + + /// 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, 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)); + } + + // 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)); + } + + // 3. Swap Stake. + // Stake: MAP ( hotkey, coldkey ) --> u64 | Stake of the hotkey for the coldkey. + for hotkey in StakingHotkeys::::get( old_coldkey ) { + // Get the stake on the old (hot,coldkey) account. + let old_stake: u64 = Stake::::get( &hotkey, old_coldkey ); + // Get the stake on the new (hot,coldkey) account. + let new_stake: u64 = Stake::::get( &hotkey, new_coldkey ); + // Add the stake to new account. + Stake::::insert(&hotkey, new_coldkey, new_stake.saturating_add(old_stake)); + // Remove the value from the old account. + Stake::::remove(&hotkey, old_coldkey); + // Add the weight for the read and write. + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } + + // 4. Swap total coldkey stake. + // TotalColdkeyStake: MAP ( coldkey ) --> u64 | Total stake of the coldkey. + let old_coldkey_stake: u64 = TotalColdkeyStake::::get(old_coldkey); + // Get the stake of the new coldkey. + let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); + // Remove the value from the old account. + TotalColdkeyStake::::insert(old_coldkey, 0); + // Add the stake to new account. + TotalColdkeyStake::::insert(new_coldkey, new_coldkey_stake.saturating_add(old_coldkey_stake) ); + 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 { + // If the hotkey is not already in the new coldkey, add it. + if !new_staking_hotkeys.contains(&hotkey) { + 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)); + + // 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() { + // Remove the hotkey from the old coldkey. + Owner::::remove(owned_hotkey); + // Add the hotkey to the new coldkey. + Owner::::insert(owned_hotkey, new_coldkey.clone()); + // Addd the owned hotkey to the new set of owned hotkeys. + if !new_owned_hotkeys.contains(owned_hotkey) { + new_owned_hotkeys.push(owned_hotkey.clone()); + } + } + 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 { + if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { + return Err(e.into()); + } + Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); + } + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + + // Return ok. + Ok(()) + } + + /// Checks if a coldkey is currently in arbitration. /// /// # Arguments @@ -303,334 +446,178 @@ impl Pallet { // Only remove ColdkeySwapDestinations if there's a single destination ColdkeySwapDestinations::::remove(&coldkey_i); weight_used = weight_used.saturating_add(T::DbWeight::get().writes(1)); - Self::perform_swap_coldkey(coldkey_i, new_coldkey).map(|weight| { - weight_used = weight_used.saturating_add(weight); - keys_swapped = keys_swapped.saturating_add(1); - })?; + keys_swapped = keys_swapped.saturating_add(1); + let _ = Self::perform_swap_coldkey(coldkey_i, new_coldkey, &mut weight_used); } } Ok(weight_used) } - pub fn perform_swap_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) -> Result { - log::info!( - "Performing swap for coldkey: {:?} to {:?}", - old_coldkey, - new_coldkey - ); - // Init the weight. - let mut weight = frame_support::weights::Weight::from_parts(0, 0); - - // Swap coldkey references in storage maps - // NOTE The order of these calls is important - Self::swap_stake_for_coldkey(old_coldkey, new_coldkey, &mut weight); - Self::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( - old_coldkey, - new_coldkey, - &mut weight, - ); - Self::swap_subnet_owner_for_coldkey(old_coldkey, new_coldkey, &mut weight); - - // Transfer any remaining balance from old_coldkey to new_coldkey - let remaining_balance = Self::get_coldkey_balance(old_coldkey); - if remaining_balance > 0 { - if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { - return Err(e.into()); - } - Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); - } - - // Swap the coldkey. - let total_balance: u64 = Self::get_coldkey_balance(old_coldkey); - if total_balance > 0 { - // Attempt to transfer the entire total balance to new_coldkey. - T::Currency::transfer( - old_coldkey, - new_coldkey, - total_balance, - Preservation::Expendable, - )?; - } - Ok(weight) - } - /// Retrieves the network membership status for a given hotkey. - /// - /// # Arguments - /// - /// * `old_hotkey` - The hotkey to check for network membership. - /// - /// # Returns - /// - /// * `Vec` - A vector of network IDs where the hotkey is a member. - pub fn get_netuid_is_member(old_hotkey: &T::AccountId, weight: &mut Weight) -> Vec { - let netuid_is_member: Vec = - as IterableStorageDoubleMap<_, _, _>>::iter_prefix(old_hotkey) - .map(|(netuid, _)| netuid) - .collect(); - weight.saturating_accrue(T::DbWeight::get().reads(netuid_is_member.len() as u64)); - netuid_is_member - } - - /// Swaps the total stake associated with a coldkey from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Removes the total stake from the old coldkey. - /// * Inserts the total stake for the new coldkey. - /// * Updates the transaction weight. - pub fn swap_total_coldkey_stake( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - let stake = TotalColdkeyStake::::get(old_coldkey); - TotalColdkeyStake::::remove(old_coldkey); - TotalColdkeyStake::::insert(new_coldkey, stake); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - } - - /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Transfers all stakes from the old coldkey to the new coldkey. - /// * Updates the ownership of hotkeys. - /// * Updates the total stake for both old and new coldkeys. - /// * Updates the transaction weight. - /// - - pub fn swap_stake_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - // Retrieve the list of hotkeys owned by the old coldkey - let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); - - // Initialize the total transferred stake to zero - let mut total_transferred_stake: u64 = 0u64; - - // Log the total stake of old and new coldkeys before the swap - log::info!( - "Before swap - Old coldkey total stake: {}", - TotalColdkeyStake::::get(old_coldkey) - ); - log::info!( - "Before swap - New coldkey total stake: {}", - TotalColdkeyStake::::get(new_coldkey) - ); - - // Iterate over each hotkey owned by the old coldkey - for hotkey in old_owned_hotkeys.iter() { - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::take(hotkey, old_coldkey); - log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - - // Update the owner of the hotkey to the new coldkey - Owner::::insert(hotkey, new_coldkey); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - } - log::info!( - "Starting transfer of delegated stakes for old coldkey: {:?}", - old_coldkey - ); - - for staking_hotkey in StakingHotkeys::::get(old_coldkey) { - log::info!("Processing staking hotkey: {:?}", staking_hotkey); - if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { - let hotkey = &staking_hotkey; - // Retrieve and remove the stake associated with the hotkey and old coldkey - let stake: u64 = Stake::::get(hotkey, old_coldkey); - Stake::::remove(hotkey, old_coldkey); - log::info!( - "Transferring delegated stake for hotkey {:?}: {}", - hotkey, - stake - ); - if stake > 0 { - // Insert the stake for the hotkey and new coldkey - let old_stake = Stake::::get(hotkey, new_coldkey); - Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); - total_transferred_stake = total_transferred_stake.saturating_add(stake); - log::info!( - "Updated stake for hotkey {:?} under new coldkey {:?}: {}", - hotkey, - new_coldkey, - stake.saturating_add(old_stake) - ); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); - } - } else { - log::info!( - "No stake found for staking hotkey {:?} under old coldkey {:?}", - staking_hotkey, - old_coldkey - ); - weight.saturating_accrue(T::DbWeight::get().reads(1)); - } - } - - log::info!( - "Completed transfer of delegated stakes for old coldkey: {:?}", - old_coldkey - ); - - // Log the total transferred stake - log::info!("Total transferred stake: {}", total_transferred_stake); - - // Update the total stake for both old and new coldkeys if any stake was transferred - if total_transferred_stake > 0 { - let old_coldkey_stake: u64 = TotalColdkeyStake::::take(old_coldkey); // Remove it here. - let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); - - TotalColdkeyStake::::insert(old_coldkey, 0); - TotalColdkeyStake::::insert( - new_coldkey, - new_coldkey_stake.saturating_add(old_coldkey_stake), - ); - - log::info!("Updated old coldkey stake from {} to 0", old_coldkey_stake); - log::info!( - "Updated new coldkey stake from {} to {}", - new_coldkey_stake, - new_coldkey_stake.saturating_add(old_coldkey_stake) - ); - - // Update the transaction weight - weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); - } - - // Update the list of owned hotkeys for both old and new coldkeys - - let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); - for hotkey in old_owned_hotkeys { - if !new_owned_hotkeys.contains(&hotkey) { - new_owned_hotkeys.push(hotkey); - } - } - - OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); - OwnedHotkeys::::remove(old_coldkey); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - - // Update the staking hotkeys for both old and new coldkeys - let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); - - let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); - for hotkey in staking_hotkeys { - if !existing_staking_hotkeys.contains(&hotkey) { - existing_staking_hotkeys.push(hotkey); - } - } - - StakingHotkeys::::remove(old_coldkey); - StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - - // Log the total stake of old and new coldkeys after the swap - log::info!( - "After swap - Old coldkey total stake: {}", - TotalColdkeyStake::::get(old_coldkey) - ); - log::info!( - "After swap - New coldkey total stake: {}", - TotalColdkeyStake::::get(new_coldkey) - ); - } - - /// Swaps the total hotkey-coldkey stakes for the current interval from the old coldkey to the new coldkey. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Removes all total hotkey-coldkey stakes for the current interval associated with the old coldkey. - /// * Inserts all total hotkey-coldkey stakes for the current interval for the new coldkey. - /// * Updates the transaction weight. - pub fn swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0)); - 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(2, 2)); - } - } - - /// Checks if a coldkey has any associated hotkeys. - /// - /// # Arguments - /// - /// * `coldkey` - The AccountId of the coldkey to check. - /// - /// # Returns - /// - /// * `bool` - True if the coldkey has any associated hotkeys, false otherwise. - pub fn coldkey_has_associated_hotkeys(coldkey: &T::AccountId) -> bool { - !StakingHotkeys::::get(coldkey).is_empty() - } - - /// Swaps the subnet owner from the old coldkey to the new coldkey for all networks where the old coldkey is the owner. - /// - /// # Arguments - /// - /// * `old_coldkey` - The AccountId of the old coldkey. - /// * `new_coldkey` - The AccountId of the new coldkey. - /// * `weight` - Mutable reference to the weight of the transaction. - /// - /// # Effects - /// - /// * Updates the subnet owner to the new coldkey for all networks where the old coldkey was the owner. - /// * Updates the transaction weight. - pub fn swap_subnet_owner_for_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - weight: &mut Weight, - ) { - for netuid in 0..=TotalNetworks::::get() { - let subnet_owner = SubnetOwner::::get(netuid); - if subnet_owner == *old_coldkey { - SubnetOwner::::insert(netuid, new_coldkey.clone()); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - } - } - weight.saturating_accrue(T::DbWeight::get().reads(TotalNetworks::::get() as u64)); - } -} + // /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. + // /// + // /// # Arguments + // /// + // /// * `old_coldkey` - The AccountId of the old coldkey. + // /// * `new_coldkey` - The AccountId of the new coldkey. + // /// * `weight` - Mutable reference to the weight of the transaction. + // /// + // /// # Effects + // /// + // /// * Transfers all stakes from the old coldkey to the new coldkey. + // /// * Updates the ownership of hotkeys. + // /// * Updates the total stake for both old and new coldkeys. + // /// * Updates the transaction weight. + // /// + + // pub fn swap_stake_for_coldkey( + // old_coldkey: &T::AccountId, + // new_coldkey: &T::AccountId, + // weight: &mut Weight, + // ) { + // // Retrieve the list of hotkeys owned by the old coldkey + // let old_owned_hotkeys: Vec = OwnedHotkeys::::get(old_coldkey); + + // // Initialize the total transferred stake to zero + // let mut total_transferred_stake: u64 = 0u64; + + // // Log the total stake of old and new coldkeys before the swap + // log::info!( + // "Before swap - Old coldkey total stake: {}", + // TotalColdkeyStake::::get(old_coldkey) + // ); + // log::info!( + // "Before swap - New coldkey total stake: {}", + // TotalColdkeyStake::::get(new_coldkey) + // ); + + // // Iterate over each hotkey owned by the old coldkey + // for hotkey in old_owned_hotkeys.iter() { + // // Retrieve and remove the stake associated with the hotkey and old coldkey + // let stake: u64 = Stake::::take(hotkey, old_coldkey); + // log::info!("Transferring stake for hotkey {:?}: {}", hotkey, stake); + // if stake > 0 { + // // Insert the stake for the hotkey and new coldkey + // let old_stake = Stake::::get(hotkey, new_coldkey); + // Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + // total_transferred_stake = total_transferred_stake.saturating_add(stake); + + // // Update the owner of the hotkey to the new coldkey + // Owner::::insert(hotkey, new_coldkey); + + // // Update the transaction weight + // weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + // } + // } + // log::info!( + // "Starting transfer of delegated stakes for old coldkey: {:?}", + // old_coldkey + // ); + + // for staking_hotkey in StakingHotkeys::::get(old_coldkey) { + // log::info!("Processing staking hotkey: {:?}", staking_hotkey); + // if Stake::::contains_key(staking_hotkey.clone(), old_coldkey) { + // let hotkey = &staking_hotkey; + // // Retrieve and remove the stake associated with the hotkey and old coldkey + // let stake: u64 = Stake::::get(hotkey, old_coldkey); + // Stake::::remove(hotkey, old_coldkey); + // log::info!( + // "Transferring delegated stake for hotkey {:?}: {}", + // hotkey, + // stake + // ); + // if stake > 0 { + // // Insert the stake for the hotkey and new coldkey + // let old_stake = Stake::::get(hotkey, new_coldkey); + // Stake::::insert(hotkey, new_coldkey, stake.saturating_add(old_stake)); + // total_transferred_stake = total_transferred_stake.saturating_add(stake); + // log::info!( + // "Updated stake for hotkey {:?} under new coldkey {:?}: {}", + // hotkey, + // new_coldkey, + // stake.saturating_add(old_stake) + // ); + + // // Update the transaction weight + // weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 1)); + // } + // } else { + // log::info!( + // "No stake found for staking hotkey {:?} under old coldkey {:?}", + // staking_hotkey, + // old_coldkey + // ); + // weight.saturating_accrue(T::DbWeight::get().reads(1)); + // } + // } + + // log::info!( + // "Completed transfer of delegated stakes for old coldkey: {:?}", + // old_coldkey + // ); + + // // Log the total transferred stake + // log::info!("Total transferred stake: {}", total_transferred_stake); + + // // Update the total stake for both old and new coldkeys if any stake was transferred + // if total_transferred_stake > 0 { + // let old_coldkey_stake: u64 = TotalColdkeyStake::::take(old_coldkey); // Remove it here. + // let new_coldkey_stake: u64 = TotalColdkeyStake::::get(new_coldkey); + + // TotalColdkeyStake::::insert(old_coldkey, 0); + // TotalColdkeyStake::::insert( + // new_coldkey, + // new_coldkey_stake.saturating_add(old_coldkey_stake), + // ); + + // log::info!("Updated old coldkey stake from {} to 0", old_coldkey_stake); + // log::info!( + // "Updated new coldkey stake from {} to {}", + // new_coldkey_stake, + // new_coldkey_stake.saturating_add(old_coldkey_stake) + // ); + + // // Update the transaction weight + // weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + // } + + // // Update the list of owned hotkeys for both old and new coldkeys + + // let mut new_owned_hotkeys = OwnedHotkeys::::get(new_coldkey); + // for hotkey in old_owned_hotkeys { + // if !new_owned_hotkeys.contains(&hotkey) { + // new_owned_hotkeys.push(hotkey); + // } + // } + + // OwnedHotkeys::::insert(new_coldkey, new_owned_hotkeys); + // OwnedHotkeys::::remove(old_coldkey); + // weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); + + // // Update the staking hotkeys for both old and new coldkeys + // let staking_hotkeys: Vec = StakingHotkeys::::get(old_coldkey); + + // let mut existing_staking_hotkeys = StakingHotkeys::::get(new_coldkey); + // for hotkey in staking_hotkeys { + // if !existing_staking_hotkeys.contains(&hotkey) { + // existing_staking_hotkeys.push(hotkey); + // } + // } + + // StakingHotkeys::::remove(old_coldkey); + // StakingHotkeys::::insert(new_coldkey, existing_staking_hotkeys); + // weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); + + // // Log the total stake of old and new coldkeys after the swap + // log::info!( + // "After swap - Old coldkey total stake: {}", + // TotalColdkeyStake::::get(old_coldkey) + // ); + // log::info!( + // "After swap - New coldkey total stake: {}", + // TotalColdkeyStake::::get(new_coldkey) + // ); + // } +} \ No newline at end of file diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index 5c3fecbba..46ba4546a 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -145,6 +145,7 @@ impl Pallet { coldkey: &T::AccountId, weight: &mut Weight, ) -> DispatchResult { + // 1. Swap owner. // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. Owner::::remove(old_hotkey); diff --git a/pallets/subtensor/tests/swap.rs b/pallets/subtensor/tests/swap_coldkey.rs similarity index 63% rename from pallets/subtensor/tests/swap.rs rename to pallets/subtensor/tests/swap_coldkey.rs index 0a3a85dff..0d1e652e6 100644 --- a/pallets/subtensor/tests/swap.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1,5 +1,4 @@ #![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] - use codec::Encode; use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; @@ -7,8 +6,383 @@ use frame_system::{Config, RawOrigin}; mod mock; use mock::*; use pallet_subtensor::*; +use sp_core::H256; use sp_core::U256; +// 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_swap_subnet_owner() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let netuid = 1u16; + + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, old_coldkey); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(SubnetOwner::::get(netuid), new_coldkey); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_stake --exact --nocapture +#[test] +fn test_swap_stake() { + 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; + + StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); + Stake::::insert(&hotkey, &old_coldkey, stake); + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert!(!Stake::::contains_key(&hotkey, &old_coldkey)); + assert_eq!(Stake::::get(&hotkey, &new_coldkey), stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_coldkey_stake --exact --nocapture +#[test] +fn test_swap_total_coldkey_stake() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let stake = 100; + + TotalColdkeyStake::::insert(&old_coldkey, stake); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_staking_hotkeys --exact --nocapture +#[test] +fn test_swap_staking_hotkeys() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + + StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + 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_swap_hotkey_owners() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let hotkey = U256::from(3); + + Owner::::insert(&hotkey, &old_coldkey); + OwnedHotkeys::::insert(&old_coldkey, vec![hotkey]); + + 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_transfer_remaining_balance() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let balance = 100; + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + 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 --test swap_coldkey -- test_swap_with_no_stake --exact --nocapture +#[test] +fn test_swap_with_no_stake() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), 0); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_multiple_hotkeys --exact --nocapture +#[test] +fn test_swap_with_multiple_hotkeys() { + 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); + + OwnedHotkeys::::insert(&old_coldkey, vec![hotkey1, hotkey2]); + + 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_with_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let netuid1 = 1u16; + let netuid2 = 2u16; + + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + SubnetOwner::::insert(netuid1, &old_coldkey); + SubnetOwner::::insert(netuid2, &old_coldkey); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + 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_with_zero_balance() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + 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 --test swap_coldkey -- test_swap_idempotency --exact --nocapture +#[test] +fn test_swap_idempotency() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let stake = 100; + + TotalColdkeyStake::::insert(&old_coldkey, stake); + + 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_max_values --exact --nocapture +#[test] +fn test_swap_with_max_values() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let max_stake = u64::MAX; + + TotalColdkeyStake::::insert(&old_coldkey, max_stake); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_non_existent_new_coldkey --exact --nocapture +#[test] +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 stake = 100; + + TotalColdkeyStake::::insert(&old_coldkey, stake); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_overflow_in_stake_addition --exact --nocapture +#[test] +fn test_swap_with_overflow_in_stake_addition() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let max_stake = u64::MAX; + + TotalColdkeyStake::::insert(&old_coldkey, max_stake); + TotalColdkeyStake::::insert(&new_coldkey, 1); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_with_max_hotkeys --exact --nocapture +#[test] +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 max_hotkeys = 1000; + let hotkeys: Vec = (0..max_hotkeys).map(U256::from).collect(); + + OwnedHotkeys::::insert(&old_coldkey, hotkeys.clone()); + + 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), hotkeys); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_effect_on_delegated_stake --exact --nocapture +#[test] +fn test_swap_effect_on_delegated_stake() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + let delegator = U256::from(3); + let hotkey = U256::from(4); + let stake = 100; + + StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); + StakingHotkeys::::insert(&delegator, vec![hotkey]); + Stake::::insert(&hotkey, &old_coldkey, stake); + Stake::::insert(&hotkey, &delegator, stake); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(Stake::::get(&hotkey, &new_coldkey), stake); + assert_eq!(Stake::::get(&hotkey, &delegator), stake); + assert_eq!(Stake::::get(&hotkey, &old_coldkey), 0); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_concurrent_modifications --exact --nocapture +#[test] +fn test_swap_concurrent_modifications() { + 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: u16 = 1; + let initial_stake = 100; + let additional_stake = 50; + + StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); + Stake::::insert(&hotkey, &old_coldkey, initial_stake); + + // Simulate concurrent stake addition + add_network(netuid, 1,1); + SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, additional_stake); + register_ok_neuron(netuid, hotkey, new_coldkey, 1001000); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(new_coldkey), + hotkey, + additional_stake + )); + + let mut weight = Weight::zero(); + assert_ok!(SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight)); + + assert_eq!(Stake::::get(&hotkey, &new_coldkey), initial_stake + additional_stake - 1); + assert!(!Stake::::contains_key(&hotkey, &old_coldkey)); + }); +} + +// 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 = 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)); + }); +} + + #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { @@ -180,7 +554,7 @@ fn test_swap_stake_for_coldkey() { let initial_total_stake = SubtensorModule::get_total_stake(); // Perform the swap - SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify stake is additive, not replaced assert_eq!( @@ -222,6 +596,7 @@ fn test_swap_stake_for_coldkey() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_staking_hotkeys_for_coldkey --exact --nocapture #[test] fn test_swap_staking_hotkeys_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -248,7 +623,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { TotalStake::::put(total_stake); // Perform the swap - SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify StakingHotkeys transfer assert_eq!( @@ -259,6 +634,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_delegated_stake_for_coldkey --exact --nocapture #[test] fn test_swap_delegated_stake_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -291,7 +667,7 @@ fn test_swap_delegated_stake_for_coldkey() { let initial_total_stake = SubtensorModule::get_total_stake(); // Perform the swap - SubtensorModule::swap_stake_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify stake transfer assert_eq!(Stake::::get(hotkey1, new_coldkey), stake_amount1); @@ -321,6 +697,7 @@ fn test_swap_delegated_stake_for_coldkey() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey --exact --nocapture #[test] fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { new_test_ext(1).execute_with(|| { @@ -340,7 +717,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap - SubtensorModule::swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey( + SubtensorModule::perform_swap_coldkey( &old_coldkey, &new_coldkey, &mut weight, @@ -363,13 +740,10 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { old_coldkey, hotkey2 )); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(5, 4); - assert_eq!(weight, expected_weight); }); } +// 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(|| { @@ -380,6 +754,8 @@ fn test_swap_subnet_owner_for_coldkey() { 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); @@ -387,18 +763,15 @@ fn test_swap_subnet_owner_for_coldkey() { TotalNetworks::::put(3); // Perform the swap - SubtensorModule::swap_subnet_owner_for_coldkey(&old_coldkey, &new_coldkey, &mut weight); + 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); - - // Verify weight update - let expected_weight = ::DbWeight::get().reads_writes(3, 2); - assert_eq!(weight, expected_weight); }); } +// 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(|| { @@ -432,7 +805,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { 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(|| { @@ -447,7 +820,7 @@ fn test_coldkey_has_associated_hotkeys() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_swap_total --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_coldkey_swap_total --exact --nocapture #[test] fn test_coldkey_swap_total() { new_test_ext(1).execute_with(|| { @@ -669,9 +1042,11 @@ fn test_coldkey_swap_total() { // Perform the swap let new_coldkey = U256::from(1100); assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 600); + let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, - &new_coldkey + &new_coldkey, + &mut weight )); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), @@ -741,7 +1116,7 @@ fn test_coldkey_swap_total() { ); }); } - +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_swap_senate_member --exact --nocapture #[test] fn test_swap_senate_member() { new_test_ext(1).execute_with(|| { @@ -787,7 +1162,7 @@ fn test_swap_senate_member() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap -- test_coldkey_delegations --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_coldkey_delegations --exact --nocapture #[test] fn test_coldkey_delegations() { new_test_ext(1).execute_with(|| { @@ -809,9 +1184,11 @@ fn test_coldkey_delegations() { delegate, 100 )); + let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &coldkey, - &new_coldkey + &new_coldkey, + &mut weight )); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&delegate), 100); assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 0); @@ -823,3 +1200,4 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, coldkey), 0); }); } + From da1a9d2ddb71ded39207a130315a98bd41cd4071 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 23 Jul 2024 10:11:19 -0400 Subject: [PATCH 041/269] fix clippy warnings --- pallets/subtensor/tests/swap_hotkey.rs | 204 ++++++++++++------------- 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 68f4ec49c..990382918 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -19,7 +19,7 @@ fn test_swap_owner() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -27,8 +27,8 @@ fn test_swap_owner() { &mut weight )); - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); }); } @@ -41,7 +41,7 @@ fn test_swap_owned_hotkeys() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - OwnedHotkeys::::insert(&coldkey, vec![old_hotkey]); + OwnedHotkeys::::insert(coldkey, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -49,7 +49,7 @@ fn test_swap_owned_hotkeys() { &mut weight )); - let hotkeys = OwnedHotkeys::::get(&coldkey); + let hotkeys = OwnedHotkeys::::get(coldkey); assert!(!hotkeys.contains(&old_hotkey)); assert!(hotkeys.contains(&new_hotkey)); }); @@ -64,8 +64,8 @@ fn test_swap_total_hotkey_stake() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - TotalHotkeyStake::::insert(&old_hotkey, 100); - TotalHotkeyStake::::insert(&new_hotkey, 50); + TotalHotkeyStake::::insert(old_hotkey, 100); + TotalHotkeyStake::::insert(new_hotkey, 50); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -73,8 +73,8 @@ fn test_swap_total_hotkey_stake() { &mut weight )); - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); - assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 150); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(new_hotkey), 150); }); } @@ -87,7 +87,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - TotalHotkeyColdkeyStakesThisInterval::::insert(&old_hotkey, &coldkey, (100, 1000)); + TotalHotkeyColdkeyStakesThisInterval::::insert(old_hotkey, coldkey, (100, 1000)); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -96,11 +96,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { )); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - &old_hotkey, - &coldkey + old_hotkey, + coldkey )); assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(&new_hotkey, &coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), (100, 1000) ); }); @@ -115,7 +115,7 @@ fn test_swap_last_tx_block() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - LastTxBlock::::insert(&old_hotkey, 1000); + LastTxBlock::::insert(old_hotkey, 1000); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -123,9 +123,9 @@ fn test_swap_last_tx_block() { &mut weight )); - assert!(!LastTxBlock::::contains_key(&old_hotkey)); + assert!(!LastTxBlock::::contains_key(old_hotkey)); assert_eq!( - LastTxBlock::::get(&new_hotkey), + LastTxBlock::::get(new_hotkey), SubtensorModule::get_current_block_as_u64() ); }); @@ -140,7 +140,7 @@ fn test_swap_last_tx_block_delegate_take() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - pallet_subtensor::LastTxBlockDelegateTake::::insert(&old_hotkey, 1000); + pallet_subtensor::LastTxBlockDelegateTake::::insert(old_hotkey, 1000); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -148,9 +148,9 @@ fn test_swap_last_tx_block_delegate_take() { &mut weight )); - assert!(!LastTxBlockDelegateTake::::contains_key(&old_hotkey)); + assert!(!LastTxBlockDelegateTake::::contains_key(old_hotkey)); assert_eq!( - LastTxBlockDelegateTake::::get(&new_hotkey), + LastTxBlockDelegateTake::::get(new_hotkey), SubtensorModule::get_current_block_as_u64() ); }); @@ -189,7 +189,7 @@ fn test_swap_delegates() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Delegates::::insert(&old_hotkey, 100); + Delegates::::insert(old_hotkey, 100); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -197,8 +197,8 @@ fn test_swap_delegates() { &mut weight )); - assert!(!Delegates::::contains_key(&old_hotkey)); - assert_eq!(Delegates::::get(&new_hotkey), 100); + assert!(!Delegates::::contains_key(old_hotkey)); + assert_eq!(Delegates::::get(new_hotkey), 100); }); } @@ -213,7 +213,7 @@ fn test_swap_subnet_membership() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, &new_hotkey, @@ -221,8 +221,8 @@ fn test_swap_subnet_membership() { &mut weight )); - assert!(!IsNetworkMember::::contains_key(&old_hotkey, netuid)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid)); + assert!(!IsNetworkMember::::contains_key(old_hotkey, netuid)); + assert!(IsNetworkMember::::get(new_hotkey, netuid)); }); } @@ -238,8 +238,8 @@ fn test_swap_uids_and_keys() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Uids::::insert(netuid, &old_hotkey, uid); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Uids::::insert(netuid, old_hotkey, uid); Keys::::insert(netuid, uid, old_hotkey); assert_ok!(SubtensorModule::perform_hotkey_swap( @@ -249,8 +249,8 @@ fn test_swap_uids_and_keys() { &mut weight )); - assert_eq!(Uids::::get(netuid, &old_hotkey), None); - assert_eq!(Uids::::get(netuid, &new_hotkey), Some(uid)); + assert_eq!(Uids::::get(netuid, old_hotkey), None); + assert_eq!(Uids::::get(netuid, new_hotkey), Some(uid)); assert_eq!(Keys::::get(netuid, uid), new_hotkey); }); } @@ -267,8 +267,8 @@ fn test_swap_prometheus() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Prometheus::::insert(netuid, &old_hotkey, prometheus_info.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Prometheus::::insert(netuid, old_hotkey, prometheus_info.clone()); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -277,9 +277,9 @@ fn test_swap_prometheus() { &mut weight )); - assert!(!Prometheus::::contains_key(netuid, &old_hotkey)); + assert!(!Prometheus::::contains_key(netuid, old_hotkey)); assert_eq!( - Prometheus::::get(netuid, &new_hotkey), + Prometheus::::get(netuid, new_hotkey), Some(prometheus_info) ); }); @@ -297,8 +297,8 @@ fn test_swap_axons() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - Axons::::insert(netuid, &old_hotkey, axon_info.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + Axons::::insert(netuid, old_hotkey, axon_info.clone()); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -307,8 +307,8 @@ fn test_swap_axons() { &mut weight )); - assert!(!Axons::::contains_key(netuid, &old_hotkey)); - assert_eq!(Axons::::get(netuid, &new_hotkey), Some(axon_info)); + assert!(!Axons::::contains_key(netuid, old_hotkey)); + assert_eq!(Axons::::get(netuid, new_hotkey), Some(axon_info)); }); } @@ -324,8 +324,8 @@ fn test_swap_weight_commits() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); - WeightCommits::::insert(netuid, &old_hotkey, weight_commits.clone()); + IsNetworkMember::::insert(old_hotkey, netuid, true); + WeightCommits::::insert(netuid, old_hotkey, weight_commits); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -334,9 +334,9 @@ fn test_swap_weight_commits() { &mut weight )); - assert!(!WeightCommits::::contains_key(netuid, &old_hotkey)); + assert!(!WeightCommits::::contains_key(netuid, old_hotkey)); assert_eq!( - WeightCommits::::get(netuid, &new_hotkey), + WeightCommits::::get(netuid, new_hotkey), Some(weight_commits) ); }); @@ -355,7 +355,7 @@ fn test_swap_loaded_emission() { let mut weight = Weight::zero(); add_network(netuid, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid, true); + IsNetworkMember::::insert(old_hotkey, netuid, true); LoadedEmission::::insert( netuid, vec![(old_hotkey, server_emission, validator_emission)], @@ -386,7 +386,7 @@ fn test_swap_stake() { let stake_amount = 100u64; let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, stake_amount); + Stake::::insert(old_hotkey, coldkey, stake_amount); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -395,8 +395,8 @@ fn test_swap_stake() { &mut weight )); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); - assert_eq!(Stake::::get(&new_hotkey, &coldkey), stake_amount); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + assert_eq!(Stake::::get(new_hotkey, coldkey), stake_amount); }); } @@ -409,8 +409,8 @@ fn test_swap_staking_hotkeys() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, 100); - StakingHotkeys::::insert(&coldkey, vec![old_hotkey]); + Stake::::insert(old_hotkey, coldkey, 100); + StakingHotkeys::::insert(coldkey, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -419,7 +419,7 @@ fn test_swap_staking_hotkeys() { &mut weight )); - let staking_hotkeys = StakingHotkeys::::get(&coldkey); + let staking_hotkeys = StakingHotkeys::::get(coldkey); assert!(!staking_hotkeys.contains(&old_hotkey)); assert!(staking_hotkeys.contains(&new_hotkey)); }); @@ -435,10 +435,10 @@ fn test_swap_hotkey_with_multiple_coldkeys() { let coldkey2 = U256::from(4); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); - StakingHotkeys::::insert(&coldkey2, vec![old_hotkey]); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -447,10 +447,10 @@ fn test_swap_hotkey_with_multiple_coldkeys() { &mut weight )); - assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); - assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); - assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); - assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); + assert_eq!(Stake::::get(new_hotkey, coldkey1), 100); + assert_eq!(Stake::::get(new_hotkey, coldkey2), 200); + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); }); } @@ -463,8 +463,8 @@ fn test_swap_hotkey_with_existing_stake() { let coldkey = U256::from(3); let mut weight = Weight::zero(); - Stake::::insert(&old_hotkey, &coldkey, 100); - Stake::::insert(&new_hotkey, &coldkey, 50); + Stake::::insert(old_hotkey, coldkey, 100); + Stake::::insert(new_hotkey, coldkey, 50); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -473,7 +473,7 @@ fn test_swap_hotkey_with_existing_stake() { &mut weight )); - assert_eq!(Stake::::get(&new_hotkey, &coldkey), 150); + assert_eq!(Stake::::get(new_hotkey, coldkey), 150); }); } @@ -490,8 +490,8 @@ fn test_swap_hotkey_with_multiple_subnets() { add_network(netuid1, 0, 1); add_network(netuid2, 0, 1); - IsNetworkMember::::insert(&old_hotkey, netuid1, true); - IsNetworkMember::::insert(&old_hotkey, netuid2, true); + IsNetworkMember::::insert(old_hotkey, netuid1, true); + IsNetworkMember::::insert(old_hotkey, netuid2, true); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -500,10 +500,10 @@ fn test_swap_hotkey_with_multiple_subnets() { &mut weight )); - assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + assert!(IsNetworkMember::::get(new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); }); } @@ -518,10 +518,10 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { let mut weight = Weight::zero(); // Set up initial state - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - StakingHotkeys::::insert(&coldkey1, vec![old_hotkey]); - StakingHotkeys::::insert(&coldkey2, vec![old_hotkey, U256::from(5)]); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + StakingHotkeys::::insert(coldkey1, vec![old_hotkey]); + StakingHotkeys::::insert(coldkey2, vec![old_hotkey, U256::from(5)]); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -531,13 +531,13 @@ fn test_swap_staking_hotkeys_multiple_coldkeys() { )); // Check if new_hotkey replaced old_hotkey in StakingHotkeys - assert!(StakingHotkeys::::get(&coldkey1).contains(&new_hotkey)); - assert!(!StakingHotkeys::::get(&coldkey1).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(coldkey1).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(coldkey1).contains(&old_hotkey)); // Check if new_hotkey replaced old_hotkey for coldkey2 as well - assert!(StakingHotkeys::::get(&coldkey2).contains(&new_hotkey)); - assert!(!StakingHotkeys::::get(&coldkey2).contains(&old_hotkey)); - assert!(StakingHotkeys::::get(&coldkey2).contains(&U256::from(5))); + assert!(StakingHotkeys::::get(coldkey2).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(coldkey2).contains(&old_hotkey)); + assert!(StakingHotkeys::::get(coldkey2).contains(&U256::from(5))); // Other hotkeys should remain }); } @@ -552,7 +552,7 @@ fn test_swap_hotkey_with_no_stake() { let mut weight = Weight::zero(); // Set up initial state with no stake - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -562,12 +562,12 @@ fn test_swap_hotkey_with_no_stake() { )); // Check if ownership transferred - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey); // Ensure no unexpected changes in Stake - assert!(!Stake::::contains_key(&old_hotkey, &coldkey)); - assert!(!Stake::::contains_key(&new_hotkey, &coldkey)); + assert!(!Stake::::contains_key(old_hotkey, coldkey)); + assert!(!Stake::::contains_key(new_hotkey, coldkey)); }); } @@ -586,12 +586,12 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { // Set up initial state add_network(netuid1, 0, 1); add_network(netuid2, 0, 1); - Owner::::insert(&old_hotkey, &coldkey1); - Stake::::insert(&old_hotkey, &coldkey1, 100); - Stake::::insert(&old_hotkey, &coldkey2, 200); - IsNetworkMember::::insert(&old_hotkey, netuid1, true); - IsNetworkMember::::insert(&old_hotkey, netuid2, true); - TotalHotkeyStake::::insert(&old_hotkey, 300); + Owner::::insert(old_hotkey, coldkey1); + Stake::::insert(old_hotkey, coldkey1, 100); + Stake::::insert(old_hotkey, coldkey2, 200); + IsNetworkMember::::insert(old_hotkey, netuid1, true); + IsNetworkMember::::insert(old_hotkey, netuid2, true); + TotalHotkeyStake::::insert(old_hotkey, 300); assert_ok!(SubtensorModule::perform_hotkey_swap( &old_hotkey, @@ -601,24 +601,24 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { )); // Check ownership transfer - assert!(!Owner::::contains_key(&old_hotkey)); - assert_eq!(Owner::::get(&new_hotkey), coldkey1); + assert!(!Owner::::contains_key(old_hotkey)); + assert_eq!(Owner::::get(new_hotkey), coldkey1); // Check stake transfer - assert_eq!(Stake::::get(&new_hotkey, &coldkey1), 100); - assert_eq!(Stake::::get(&new_hotkey, &coldkey2), 200); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey1)); - assert!(!Stake::::contains_key(&old_hotkey, &coldkey2)); + assert_eq!(Stake::::get(new_hotkey, coldkey1), 100); + assert_eq!(Stake::::get(new_hotkey, coldkey2), 200); + assert!(!Stake::::contains_key(old_hotkey, coldkey1)); + assert!(!Stake::::contains_key(old_hotkey, coldkey2)); // Check subnet membership transfer - assert!(IsNetworkMember::::get(&new_hotkey, netuid1)); - assert!(IsNetworkMember::::get(&new_hotkey, netuid2)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid1)); - assert!(!IsNetworkMember::::get(&old_hotkey, netuid2)); + assert!(IsNetworkMember::::get(new_hotkey, netuid1)); + assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); + assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); // Check total stake transfer - assert_eq!(TotalHotkeyStake::::get(&new_hotkey), 300); - assert!(!TotalHotkeyStake::::contains_key(&old_hotkey)); + assert_eq!(TotalHotkeyStake::::get(new_hotkey), 300); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); }); } @@ -899,9 +899,9 @@ fn test_swap_hotkey_error_cases() { let wrong_coldkey = U256::from(4); // Set up initial state - Owner::::insert(&old_hotkey, &coldkey); + Owner::::insert(old_hotkey, coldkey); TotalNetworks::::put(1); - LastTxBlock::::insert(&coldkey, 0); + LastTxBlock::::insert(coldkey, 0); // Test not enough balance let swap_cost = SubtensorModule::get_key_swap_cost(); @@ -928,7 +928,7 @@ fn test_swap_hotkey_error_cases() { ); // Test new hotkey already registered - IsNetworkMember::::insert(&new_hotkey, 0, true); + IsNetworkMember::::insert(new_hotkey, 0, true); assert_noop!( SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), @@ -937,7 +937,7 @@ fn test_swap_hotkey_error_cases() { ), Error::::HotKeyAlreadyRegisteredInSubNet ); - IsNetworkMember::::remove(&new_hotkey, 0); + IsNetworkMember::::remove(new_hotkey, 0); // Test non-associated coldkey assert_noop!( @@ -958,7 +958,7 @@ fn test_swap_hotkey_error_cases() { // Check balance after swap assert_eq!( - Balances::free_balance(&coldkey), + Balances::free_balance(coldkey), initial_balance - swap_cost ); }); From 8e4bf4c763ddd0dd935b53558b6768f4f73b39b6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 23 Jul 2024 10:16:39 -0400 Subject: [PATCH 042/269] fmt --- pallets/subtensor/tests/swap_hotkey.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 990382918..c6a05f2b6 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -96,8 +96,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { )); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - old_hotkey, - coldkey + old_hotkey, coldkey )); assert_eq!( TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), @@ -957,9 +956,6 @@ fn test_swap_hotkey_error_cases() { )); // Check balance after swap - assert_eq!( - Balances::free_balance(coldkey), - initial_balance - swap_cost - ); + assert_eq!(Balances::free_balance(coldkey), initial_balance - swap_cost); }); } From ed729b644d229d3d9fe574997532db37d6e4d945 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Jul 2024 09:03:50 -0700 Subject: [PATCH 043/269] Added --no-purge flag to scripts/localnet.sh to be able to save state when restarting a script. --- scripts/localnet.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scripts/localnet.sh b/scripts/localnet.sh index 65ca5c0a9..2856603e0 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -1,5 +1,14 @@ #!/bin/bash +# Check if `--no-purge` passed as a parameter +NO_PURGE=0 +for arg in "$@"; do + if [ "$arg" = "--no-purge" ]; then + NO_PURGE=1 + break + fi +done + # Determine the directory this script resides in. This allows invoking it from any location. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" @@ -47,10 +56,14 @@ echo "*** Building chainspec..." "$BASE_DIR/target/release/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH echo "*** Chainspec built and output to file" -echo "*** Purging previous state..." -"$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 -"$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 -echo "*** Previous chainstate purged" +if [ $NO_PURGE -eq 1 ]; then + echo "*** Purging previous state skipped..." +else + echo "*** Purging previous state..." + "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 + "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 + echo "*** Previous chainstate purged" +fi echo "*** Starting localnet nodes..." alice_start=( From cf9869d480c6bc982acfe63f64279c53b4679805 Mon Sep 17 00:00:00 2001 From: const Date: Tue, 23 Jul 2024 13:23:43 -0500 Subject: [PATCH 044/269] pre merge --- pallets/subtensor/src/swap_coldkey.rs | 38 ++--- pallets/subtensor/src/swap_hotkey.rs | 1 - pallets/subtensor/tests/swap_coldkey.rs | 181 +++++++++++++++++------- pallets/subtensor/tests/swap_hotkey.rs | 8 +- 4 files changed, 155 insertions(+), 73 deletions(-) diff --git a/pallets/subtensor/src/swap_coldkey.rs b/pallets/subtensor/src/swap_coldkey.rs index 9a5ad515b..f8049cd6f 100644 --- a/pallets/subtensor/src/swap_coldkey.rs +++ b/pallets/subtensor/src/swap_coldkey.rs @@ -1,11 +1,9 @@ - use super::*; use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; use frame_support::weights::Weight; use sp_core::{Get, U256}; impl Pallet { - /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. /// /// # Arguments @@ -69,7 +67,8 @@ impl Pallet { ); // 7. Remove and burn the swap cost from the old coldkey's account - let actual_burn_amount = Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + let actual_burn_amount = + Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); // 8. Update the weight for the balance operations @@ -92,7 +91,6 @@ impl Pallet { Ok(Some(weight).into()) } - /// Performs the actual coldkey swap operation, transferring all associated data and balances from the old coldkey to the new coldkey. /// /// # Arguments @@ -134,12 +132,16 @@ impl Pallet { /// # 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, weight: &mut Weight ) -> DispatchResult { - + pub fn perform_swap_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + 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); + 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)); @@ -157,11 +159,11 @@ impl Pallet { // 3. Swap Stake. // Stake: MAP ( hotkey, coldkey ) --> u64 | Stake of the hotkey for the coldkey. - for hotkey in StakingHotkeys::::get( old_coldkey ) { + for hotkey in StakingHotkeys::::get(old_coldkey) { // Get the stake on the old (hot,coldkey) account. - let old_stake: u64 = Stake::::get( &hotkey, old_coldkey ); + let old_stake: u64 = Stake::::get(&hotkey, old_coldkey); // Get the stake on the new (hot,coldkey) account. - let new_stake: u64 = Stake::::get( &hotkey, new_coldkey ); + let new_stake: u64 = Stake::::get(&hotkey, new_coldkey); // Add the stake to new account. Stake::::insert(&hotkey, new_coldkey, new_stake.saturating_add(old_stake)); // Remove the value from the old account. @@ -169,7 +171,7 @@ impl Pallet { // Add the weight for the read and write. weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); } - + // 4. Swap total coldkey stake. // TotalColdkeyStake: MAP ( coldkey ) --> u64 | Total stake of the coldkey. let old_coldkey_stake: u64 = TotalColdkeyStake::::get(old_coldkey); @@ -178,13 +180,16 @@ impl Pallet { // Remove the value from the old account. TotalColdkeyStake::::insert(old_coldkey, 0); // Add the stake to new account. - TotalColdkeyStake::::insert(new_coldkey, new_coldkey_stake.saturating_add(old_coldkey_stake) ); + TotalColdkeyStake::::insert( + new_coldkey, + new_coldkey_stake.saturating_add(old_coldkey_stake), + ); 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 ); + let mut new_staking_hotkeys: Vec = StakingHotkeys::::get(new_coldkey); for hotkey in old_staking_hotkeys { // If the hotkey is not already in the new coldkey, add it. if !new_staking_hotkeys.contains(&hotkey) { @@ -220,7 +225,7 @@ impl Pallet { let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { - return Err(e.into()); + return Err(e); } Self::add_balance_to_coldkey_account(new_coldkey, remaining_balance); } @@ -230,7 +235,6 @@ impl Pallet { Ok(()) } - /// Checks if a coldkey is currently in arbitration. /// /// # Arguments @@ -454,8 +458,6 @@ impl Pallet { Ok(weight_used) } - - // /// Swaps the stake associated with a coldkey from the old coldkey to the new coldkey. // /// // /// # Arguments @@ -620,4 +622,4 @@ impl Pallet { // TotalColdkeyStake::::get(new_coldkey) // ); // } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/swap_hotkey.rs b/pallets/subtensor/src/swap_hotkey.rs index 46ba4546a..5c3fecbba 100644 --- a/pallets/subtensor/src/swap_hotkey.rs +++ b/pallets/subtensor/src/swap_hotkey.rs @@ -145,7 +145,6 @@ impl Pallet { coldkey: &T::AccountId, weight: &mut Weight, ) -> DispatchResult { - // 1. Swap owner. // Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey. Owner::::remove(old_hotkey); diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 0d1e652e6..b85b6c60f 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -21,12 +21,22 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { 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_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)); + assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( + &hotkey, + &old_coldkey + )); + assert_eq!( + TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, &new_coldkey), + (stake, block) + ); }); } @@ -40,9 +50,13 @@ fn test_swap_subnet_owner() { add_network(netuid, 1, 0); SubnetOwner::::insert(netuid, old_coldkey); - + 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_eq!(SubnetOwner::::get(netuid), new_coldkey); }); @@ -60,7 +74,11 @@ fn test_swap_stake() { StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); Stake::::insert(&hotkey, &old_coldkey, stake); 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!(!Stake::::contains_key(&hotkey, &old_coldkey)); assert_eq!(Stake::::get(&hotkey, &new_coldkey), stake); @@ -76,9 +94,13 @@ fn test_swap_total_coldkey_stake() { let stake = 100; TotalColdkeyStake::::insert(&old_coldkey, stake); - + 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); @@ -94,9 +116,13 @@ fn test_swap_staking_hotkeys() { let hotkey = U256::from(3); StakingHotkeys::::insert(&old_coldkey, vec![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!(StakingHotkeys::::get(&old_coldkey).is_empty()); assert_eq!(StakingHotkeys::::get(&new_coldkey), vec![hotkey]); @@ -113,9 +139,13 @@ fn test_swap_hotkey_owners() { Owner::::insert(&hotkey, &old_coldkey); OwnedHotkeys::::insert(&old_coldkey, vec![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_eq!(Owner::::get(&hotkey), new_coldkey); assert!(OwnedHotkeys::::get(&old_coldkey).is_empty()); @@ -131,9 +161,13 @@ fn test_transfer_remaining_balance() { let balance = 100; SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, balance); - + 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_eq!(SubtensorModule::get_coldkey_balance(&old_coldkey), 0); assert_eq!(SubtensorModule::get_coldkey_balance(&new_coldkey), balance); @@ -148,7 +182,11 @@ fn test_swap_with_no_stake() { let new_coldkey = U256::from(2); 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), 0); @@ -165,12 +203,19 @@ fn test_swap_with_multiple_hotkeys() { let hotkey2 = U256::from(4); OwnedHotkeys::::insert(&old_coldkey, vec![hotkey1, hotkey2]); - + 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!(OwnedHotkeys::::get(&old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(&new_coldkey), vec![hotkey1, hotkey2]); + assert_eq!( + OwnedHotkeys::::get(&new_coldkey), + vec![hotkey1, hotkey2] + ); }); } @@ -187,9 +232,13 @@ fn test_swap_with_multiple_subnets() { add_network(netuid2, 1, 0); SubnetOwner::::insert(netuid1, &old_coldkey); SubnetOwner::::insert(netuid2, &old_coldkey); - + 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_eq!(SubnetOwner::::get(netuid1), new_coldkey); assert_eq!(SubnetOwner::::get(netuid2), new_coldkey); @@ -204,14 +253,17 @@ fn test_swap_with_zero_balance() { let new_coldkey = U256::from(2); 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_eq!(Balances::free_balance(&old_coldkey), 0); assert_eq!(Balances::free_balance(&new_coldkey), 0); }); } - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_idempotency --exact --nocapture #[test] fn test_swap_idempotency() { @@ -221,10 +273,18 @@ fn test_swap_idempotency() { let stake = 100; TotalColdkeyStake::::insert(&old_coldkey, stake); - + 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_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); + assert_ok!(SubtensorModule::perform_swap_coldkey( + &old_coldkey, + &new_coldkey, + &mut weight + )); assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); @@ -240,9 +300,13 @@ fn test_swap_with_max_values() { let max_stake = u64::MAX; TotalColdkeyStake::::insert(&old_coldkey, max_stake); - + 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); @@ -258,9 +322,13 @@ fn test_swap_with_non_existent_new_coldkey() { let stake = 100; TotalColdkeyStake::::insert(&old_coldkey, stake); - + 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); @@ -277,9 +345,13 @@ fn test_swap_with_overflow_in_stake_addition() { TotalColdkeyStake::::insert(&old_coldkey, max_stake); TotalColdkeyStake::::insert(&new_coldkey, 1); - + 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_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); @@ -296,9 +368,13 @@ fn test_swap_with_max_hotkeys() { let hotkeys: Vec = (0..max_hotkeys).map(U256::from).collect(); OwnedHotkeys::::insert(&old_coldkey, hotkeys.clone()); - + 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!(OwnedHotkeys::::get(&old_coldkey).is_empty()); assert_eq!(OwnedHotkeys::::get(&new_coldkey), hotkeys); @@ -319,9 +395,13 @@ fn test_swap_effect_on_delegated_stake() { StakingHotkeys::::insert(&delegator, vec![hotkey]); Stake::::insert(&hotkey, &old_coldkey, stake); Stake::::insert(&hotkey, &delegator, stake); - + 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_eq!(Stake::::get(&hotkey, &new_coldkey), stake); assert_eq!(Stake::::get(&hotkey, &delegator), stake); @@ -344,7 +424,7 @@ fn test_swap_concurrent_modifications() { Stake::::insert(&hotkey, &old_coldkey, initial_stake); // Simulate concurrent stake addition - add_network(netuid, 1,1); + add_network(netuid, 1, 1); SubtensorModule::add_balance_to_coldkey_account(&new_coldkey, additional_stake); register_ok_neuron(netuid, hotkey, new_coldkey, 1001000); assert_ok!(SubtensorModule::add_stake( @@ -354,9 +434,16 @@ fn test_swap_concurrent_modifications() { )); 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_eq!(Stake::::get(&hotkey, &new_coldkey), initial_stake + additional_stake - 1); + assert_eq!( + Stake::::get(&hotkey, &new_coldkey), + initial_stake + additional_stake - 1 + ); assert!(!Stake::::contains_key(&hotkey, &old_coldkey)); }); } @@ -370,19 +457,22 @@ fn test_swap_with_invalid_subnet_ownership() { let netuid = 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)); + 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)); }); } - #[test] fn test_do_swap_coldkey_success() { new_test_ext(1).execute_with(|| { @@ -717,11 +807,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval_for_coldkey() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); // Perform the swap - SubtensorModule::perform_swap_coldkey( - &old_coldkey, - &new_coldkey, - &mut weight, - ); + SubtensorModule::perform_swap_coldkey(&old_coldkey, &new_coldkey, &mut weight); // Verify the swap assert_eq!( @@ -1200,4 +1286,3 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, coldkey), 0); }); } - diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 990382918..c6a05f2b6 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -96,8 +96,7 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { )); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - old_hotkey, - coldkey + old_hotkey, coldkey )); assert_eq!( TotalHotkeyColdkeyStakesThisInterval::::get(new_hotkey, coldkey), @@ -957,9 +956,6 @@ fn test_swap_hotkey_error_cases() { )); // Check balance after swap - assert_eq!( - Balances::free_balance(coldkey), - initial_balance - swap_cost - ); + assert_eq!(Balances::free_balance(coldkey), initial_balance - swap_cost); }); } From 9b18c41695a6f177776d55765a1213ddd9f48312 Mon Sep 17 00:00:00 2001 From: const Date: Tue, 23 Jul 2024 13:25:51 -0500 Subject: [PATCH 045/269] fmt and clippy --- pallets/subtensor/src/swap_coldkey.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallets/subtensor/src/swap_coldkey.rs b/pallets/subtensor/src/swap_coldkey.rs index f8049cd6f..04d9a22dc 100644 --- a/pallets/subtensor/src/swap_coldkey.rs +++ b/pallets/subtensor/src/swap_coldkey.rs @@ -224,9 +224,7 @@ impl Pallet { // Transfer any remaining balance from old_coldkey to new_coldkey let remaining_balance = Self::get_coldkey_balance(old_coldkey); if remaining_balance > 0 { - if let Err(e) = Self::kill_coldkey_account(old_coldkey, remaining_balance) { - return Err(e); - } + 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)); From 6f923c92d5ac6bcad26374776acd272aa9cda46a Mon Sep 17 00:00:00 2001 From: const Date: Tue, 23 Jul 2024 13:37:02 -0500 Subject: [PATCH 046/269] clippy --- pallets/subtensor/tests/swap_coldkey.rs | 112 ++++++++++++------------ 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index b85b6c60f..9203e2b3f 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -19,8 +19,8 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { let stake = 100; let block = 42; - OwnedHotkeys::::insert(&old_coldkey, vec![hotkey]); - TotalHotkeyColdkeyStakesThisInterval::::insert(&hotkey, &old_coldkey, (stake, block)); + OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); + TotalHotkeyColdkeyStakesThisInterval::::insert(hotkey, old_coldkey, (stake, block)); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -30,11 +30,11 @@ fn test_swap_total_hotkey_coldkey_stakes_this_interval() { )); assert!(!TotalHotkeyColdkeyStakesThisInterval::::contains_key( - &hotkey, - &old_coldkey + hotkey, + old_coldkey )); assert_eq!( - TotalHotkeyColdkeyStakesThisInterval::::get(&hotkey, &new_coldkey), + TotalHotkeyColdkeyStakesThisInterval::::get(hotkey, new_coldkey), (stake, block) ); }); @@ -71,8 +71,8 @@ fn test_swap_stake() { let hotkey = U256::from(3); let stake = 100; - StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); - Stake::::insert(&hotkey, &old_coldkey, stake); + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); + Stake::::insert(hotkey, old_coldkey, stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( &old_coldkey, @@ -80,8 +80,8 @@ fn test_swap_stake() { &mut weight )); - assert!(!Stake::::contains_key(&hotkey, &old_coldkey)); - assert_eq!(Stake::::get(&hotkey, &new_coldkey), stake); + assert!(!Stake::::contains_key(hotkey, old_coldkey)); + assert_eq!(Stake::::get(hotkey, new_coldkey), stake); }); } @@ -93,7 +93,7 @@ fn test_swap_total_coldkey_stake() { let new_coldkey = U256::from(2); let stake = 100; - TotalColdkeyStake::::insert(&old_coldkey, stake); + TotalColdkeyStake::::insert(old_coldkey, stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -102,8 +102,8 @@ fn test_swap_total_coldkey_stake() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake); }); } @@ -115,7 +115,7 @@ fn test_swap_staking_hotkeys() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); - StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -124,8 +124,8 @@ fn test_swap_staking_hotkeys() { &mut weight )); - assert!(StakingHotkeys::::get(&old_coldkey).is_empty()); - assert_eq!(StakingHotkeys::::get(&new_coldkey), vec![hotkey]); + assert!(StakingHotkeys::::get(old_coldkey).is_empty()); + assert_eq!(StakingHotkeys::::get(new_coldkey), vec![hotkey]); }); } @@ -137,8 +137,8 @@ fn test_swap_hotkey_owners() { let new_coldkey = U256::from(2); let hotkey = U256::from(3); - Owner::::insert(&hotkey, &old_coldkey); - OwnedHotkeys::::insert(&old_coldkey, vec![hotkey]); + Owner::::insert(hotkey, old_coldkey); + OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -147,9 +147,9 @@ fn test_swap_hotkey_owners() { &mut weight )); - assert_eq!(Owner::::get(&hotkey), new_coldkey); - assert!(OwnedHotkeys::::get(&old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(&new_coldkey), vec![hotkey]); + 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 @@ -188,8 +188,8 @@ fn test_swap_with_no_stake() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), 0); }); } @@ -202,7 +202,7 @@ fn test_swap_with_multiple_hotkeys() { let hotkey1 = U256::from(3); let hotkey2 = U256::from(4); - OwnedHotkeys::::insert(&old_coldkey, vec![hotkey1, hotkey2]); + OwnedHotkeys::::insert(old_coldkey, vec![hotkey1, hotkey2]); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -211,9 +211,9 @@ fn test_swap_with_multiple_hotkeys() { &mut weight )); - assert!(OwnedHotkeys::::get(&old_coldkey).is_empty()); + assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); assert_eq!( - OwnedHotkeys::::get(&new_coldkey), + OwnedHotkeys::::get(new_coldkey), vec![hotkey1, hotkey2] ); }); @@ -230,8 +230,8 @@ fn test_swap_with_multiple_subnets() { add_network(netuid1, 1, 0); add_network(netuid2, 1, 0); - SubnetOwner::::insert(netuid1, &old_coldkey); - SubnetOwner::::insert(netuid2, &old_coldkey); + SubnetOwner::::insert(netuid1, old_coldkey); + SubnetOwner::::insert(netuid2, old_coldkey); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -259,8 +259,8 @@ fn test_swap_with_zero_balance() { &mut weight )); - assert_eq!(Balances::free_balance(&old_coldkey), 0); - assert_eq!(Balances::free_balance(&new_coldkey), 0); + assert_eq!(Balances::free_balance(old_coldkey), 0); + assert_eq!(Balances::free_balance(new_coldkey), 0); }); } @@ -272,7 +272,7 @@ fn test_swap_idempotency() { let new_coldkey = U256::from(2); let stake = 100; - TotalColdkeyStake::::insert(&old_coldkey, stake); + TotalColdkeyStake::::insert(old_coldkey, stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -286,8 +286,8 @@ fn test_swap_idempotency() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake); }); } @@ -299,7 +299,7 @@ fn test_swap_with_max_values() { let new_coldkey = U256::from(2); let max_stake = u64::MAX; - TotalColdkeyStake::::insert(&old_coldkey, max_stake); + TotalColdkeyStake::::insert(old_coldkey, max_stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -308,8 +308,8 @@ fn test_swap_with_max_values() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), max_stake); }); } @@ -321,7 +321,7 @@ fn test_swap_with_non_existent_new_coldkey() { let new_coldkey = U256::from(2); let stake = 100; - TotalColdkeyStake::::insert(&old_coldkey, stake); + TotalColdkeyStake::::insert(old_coldkey, stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -330,8 +330,8 @@ fn test_swap_with_non_existent_new_coldkey() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), stake); }); } @@ -343,8 +343,8 @@ fn test_swap_with_overflow_in_stake_addition() { let new_coldkey = U256::from(2); let max_stake = u64::MAX; - TotalColdkeyStake::::insert(&old_coldkey, max_stake); - TotalColdkeyStake::::insert(&new_coldkey, 1); + TotalColdkeyStake::::insert(old_coldkey, max_stake); + TotalColdkeyStake::::insert(new_coldkey, 1); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -353,8 +353,8 @@ fn test_swap_with_overflow_in_stake_addition() { &mut weight )); - assert_eq!(TotalColdkeyStake::::get(&old_coldkey), 0); - assert_eq!(TotalColdkeyStake::::get(&new_coldkey), max_stake); + assert_eq!(TotalColdkeyStake::::get(old_coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(new_coldkey), max_stake); }); } @@ -367,7 +367,7 @@ fn test_swap_with_max_hotkeys() { let max_hotkeys = 1000; let hotkeys: Vec = (0..max_hotkeys).map(U256::from).collect(); - OwnedHotkeys::::insert(&old_coldkey, hotkeys.clone()); + OwnedHotkeys::::insert(old_coldkey, hotkeys.clone()); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -376,8 +376,8 @@ fn test_swap_with_max_hotkeys() { &mut weight )); - assert!(OwnedHotkeys::::get(&old_coldkey).is_empty()); - assert_eq!(OwnedHotkeys::::get(&new_coldkey), hotkeys); + assert!(OwnedHotkeys::::get(old_coldkey).is_empty()); + assert_eq!(OwnedHotkeys::::get(new_coldkey), hotkeys); }); } @@ -391,10 +391,10 @@ fn test_swap_effect_on_delegated_stake() { let hotkey = U256::from(4); let stake = 100; - StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); - StakingHotkeys::::insert(&delegator, vec![hotkey]); - Stake::::insert(&hotkey, &old_coldkey, stake); - Stake::::insert(&hotkey, &delegator, stake); + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); + StakingHotkeys::::insert(delegator, vec![hotkey]); + Stake::::insert(hotkey, old_coldkey, stake); + Stake::::insert(hotkey, delegator, stake); let mut weight = Weight::zero(); assert_ok!(SubtensorModule::perform_swap_coldkey( @@ -403,9 +403,9 @@ fn test_swap_effect_on_delegated_stake() { &mut weight )); - assert_eq!(Stake::::get(&hotkey, &new_coldkey), stake); - assert_eq!(Stake::::get(&hotkey, &delegator), stake); - assert_eq!(Stake::::get(&hotkey, &old_coldkey), 0); + assert_eq!(Stake::::get(hotkey, new_coldkey), stake); + assert_eq!(Stake::::get(hotkey, delegator), stake); + assert_eq!(Stake::::get(hotkey, old_coldkey), 0); }); } @@ -420,8 +420,8 @@ fn test_swap_concurrent_modifications() { let initial_stake = 100; let additional_stake = 50; - StakingHotkeys::::insert(&old_coldkey, vec![hotkey]); - Stake::::insert(&hotkey, &old_coldkey, initial_stake); + StakingHotkeys::::insert(old_coldkey, vec![hotkey]); + Stake::::insert(hotkey, old_coldkey, initial_stake); // Simulate concurrent stake addition add_network(netuid, 1, 1); @@ -441,10 +441,10 @@ fn test_swap_concurrent_modifications() { )); assert_eq!( - Stake::::get(&hotkey, &new_coldkey), + Stake::::get(hotkey, new_coldkey), initial_stake + additional_stake - 1 ); - assert!(!Stake::::contains_key(&hotkey, &old_coldkey)); + assert!(!Stake::::contains_key(hotkey, old_coldkey)); }); } From b003138ed292c13dc98d74b35de248a28c77241e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Jul 2024 12:05:33 -0700 Subject: [PATCH 047/269] bump up the `runtime/src/lib.rs::spec_version;L142` --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..f5ca3c5cd 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 165, + spec_version: 166, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 02457c1ad49a7608f294a5aa9e44f5538a7602a3 Mon Sep 17 00:00:00 2001 From: const Date: Tue, 23 Jul 2024 14:24:03 -0500 Subject: [PATCH 048/269] fix migration tests --- pallets/subtensor/src/lib.rs | 3 + pallets/subtensor/src/macros/dispatches.rs | 22 +- .../migrate_fix_total_coldkey_stake.rs | 44 ++- pallets/subtensor/src/swap/swap_coldkey.rs | 253 +----------------- pallets/subtensor/src/swap/swap_hotkey.rs | 38 ++- pallets/subtensor/tests/migration.rs | 12 +- 6 files changed, 78 insertions(+), 294 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 5a002b333..29969a23b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1073,9 +1073,12 @@ pub mod pallet { (H256, u64), OptionQuery, >; + /// ================== /// ==== Genesis ===== /// ================== + #[pallet::storage] // --- Storage for migration run status + pub type HasMigrationRun = StorageMap<_, Identity, Vec, bool, ValueQuery>; #[pallet::genesis_config] pub struct GenesisConfig { diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d8cb31166..293dc0238 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -640,17 +640,17 @@ mod dispatches { } /// The extrinsic for user to change its hotkey - ///#[pallet::call_index(70)] - ///#[pallet::weight((Weight::from_parts(1_940_000_000, 0) - ///.saturating_add(T::DbWeight::get().reads(272)) - ///.saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] - ///pub fn swap_hotkey( - /// origin: OriginFor, - /// hotkey: T::AccountId, - /// new_hotkey: T::AccountId, - ///) -> DispatchResultWithPostInfo { - /// Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) - ///} + #[pallet::call_index(70)] + #[pallet::weight((Weight::from_parts(1_940_000_000, 0) + .saturating_add(T::DbWeight::get().reads(272)) + .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + pub fn swap_hotkey( + origin: OriginFor, + hotkey: T::AccountId, + new_hotkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + Self::do_swap_hotkey(origin, &hotkey, &new_hotkey) + } /// The extrinsic for user to change the coldkey associated with their account. /// diff --git a/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs index 20c47e29c..268c82d48 100644 --- a/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs +++ b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs @@ -2,9 +2,10 @@ use super::*; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, storage_alias, - traits::{Get, GetStorageVersion, StorageVersion}, + traits::{Get, StorageVersion}, weights::Weight, }; +use alloc::string::String; use sp_std::vec::Vec; // TODO (camfairchild): TEST MIGRATION @@ -50,24 +51,41 @@ pub fn do_migrate_fix_total_coldkey_stake() -> Weight { } // Public migrate function to be called by Lib.rs on upgrade. pub fn migrate_fix_total_coldkey_stake() -> Weight { - let current_storage_version: u16 = 7; - let next_storage_version: u16 = 8; + let migration_name = b"fix_total_coldkey_stake_v7".to_vec(); // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); - // Grab the current on-chain storage version. - // Cant fail on retrieval. - let onchain_version = Pallet::::on_chain_storage_version(); - - // Only run this migration on storage version 6. - if onchain_version == current_storage_version { - weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); - // Cant fail on insert. - StorageVersion::new(next_storage_version).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return Weight::zero(); } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Run the migration + weight = weight.saturating_add(do_migrate_fix_total_coldkey_stake::()); + + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + // Set the storage version to 7 + StorageVersion::new(7).put::>(); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed. Storage version set to 7.", + String::from_utf8_lossy(&migration_name) + ); + // Return the migration weight. weight } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index e0e0061d8..507fc62a5 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,7 +1,6 @@ use super::*; -use crate::MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP; use frame_support::weights::Weight; -use sp_core::{Get, U256}; +use sp_core::{Get}; impl Pallet { /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. @@ -37,28 +36,22 @@ impl Pallet { // 1. Ensure the origin is signed and get the old coldkey let old_coldkey = ensure_signed(origin)?; - // 2. Check if the old coldkey is in arbitration - ensure!( - !Self::coldkey_in_arbitration(&old_coldkey), - Error::::ColdkeyIsInArbitration - ); - - // 3. Initialize the weight for this operation + // 2. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); - // 4. Ensure the new coldkey is not associated with any hotkeys + // 3. Ensure the new coldkey is not associated with any hotkeys ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); - // 5. Ensure the new coldkey is not a hotkey + // 4. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::ColdKeyAlreadyAssociated ); - // 6. Calculate the swap cost and ensure sufficient balance + // 5. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); log::debug!("Coldkey swap cost: {:?}", swap_cost); ensure!( @@ -66,28 +59,28 @@ impl Pallet { Error::::NotEnoughBalanceToPaySwapColdKey ); - // 7. Remove and burn the swap cost from the old coldkey's account + // 6. Remove and burn the swap cost from the old coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); - // 8. Update the weight for the balance operations + // 7. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 9. Perform the actual coldkey swap + // 8. 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 + // 9. 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. Emit the ColdkeySwapped event + // 10. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), }); - // 12. Return the result with the updated weight + // 11. Return the result with the updated weight Ok(Some(weight).into()) } @@ -232,228 +225,4 @@ impl Pallet { // Return ok. Ok(()) } - - /// Checks if a coldkey is currently in arbitration. - /// - /// # Arguments - /// - /// * `coldkey` - The account ID of the coldkey to check. - /// - /// # Returns - /// - /// * `bool` - True if the coldkey is in arbitration, false otherwise. - /// - /// # Notes - /// - /// This function compares the arbitration block number of the coldkey with the current block number. - pub fn coldkey_in_arbitration(coldkey: &T::AccountId) -> bool { - ColdkeyArbitrationBlock::::get(coldkey) > Self::get_current_block_as_u64() - } - - /// Returns the remaining arbitration period for a given coldkey. - /// - /// # Arguments - /// - /// * `coldkey` - The account ID of the coldkey to check. - /// - /// # Returns - /// - /// * `u64` - The remaining arbitration period in blocks. - /// - /// - /// # Notes - /// - /// This function calculates the remaining arbitration period by subtracting the current block number - /// from the arbitration block number of the coldkey. - pub fn get_remaining_arbitration_period(coldkey: &T::AccountId) -> u64 { - let current_block: u64 = Self::get_current_block_as_u64(); - let arbitration_block: u64 = ColdkeyArbitrationBlock::::get(coldkey); - if arbitration_block > current_block { - arbitration_block.saturating_sub(current_block) - } else { - 0 - } - } - - pub fn meets_min_allowed_coldkey_balance(coldkey: &T::AccountId) -> bool { - let all_staked_keys: Vec = StakingHotkeys::::get(coldkey); - let mut total_staking_balance: u64 = 0; - for hotkey in all_staked_keys { - total_staking_balance = total_staking_balance - .saturating_add(Self::get_stake_for_coldkey_and_hotkey(coldkey, &hotkey)); - } - total_staking_balance = - total_staking_balance.saturating_add(Self::get_coldkey_balance(coldkey)); - total_staking_balance >= MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP - } - - /// Schedules a coldkey swap to a new coldkey with arbitration. - /// - /// # Arguments - /// - /// * `old_coldkey` - The account ID of the old coldkey. - /// * `new_coldkey` - The account ID of the new coldkey. - /// * `work` - The proof of work submitted by the caller. - /// * `block_number` - The block number at which the work was performed. - /// * `nonce` - The nonce used for the proof of work. - /// - /// # Returns - /// - /// * `DispatchResult` - The result of the dispatch. - /// - /// # Errors - /// - - /// - `SameColdkey`: The old coldkey is the same as the new coldkey. - /// - `DuplicateColdkey`: The new coldkey is already in the list of destination coldkeys. - /// - `MaxColdkeyDestinationsReached`: There are already the maximum allowed destination coldkeys for the old coldkey. - /// - `InsufficientBalanceToPerformColdkeySwap`: The old coldkey doesn't have the minimum required TAO balance. - /// - `InvalidDifficulty`: The proof of work is invalid or doesn't meet the required difficulty. - /// - /// # Notes - /// - /// This function ensures that the new coldkey is not already in the list of destination coldkeys. - /// It also checks for a minimum TAO balance and verifies the proof of work. - /// The difficulty of the proof of work increases exponentially with each subsequent call. - pub fn do_schedule_coldkey_swap( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - work: Vec, - block_number: u64, - nonce: u64, - ) -> DispatchResult { - ensure!(old_coldkey != new_coldkey, Error::::SameColdkey); - - // Check if the old_coldkey is a subnet owner for any network - let is_subnet_owner = (0..=TotalNetworks::::get()) - .any(|netuid| SubnetOwner::::get(netuid) == *old_coldkey); - - // Check if the old_coldkey has more than 500 TAO delegated - let total_delegated = Self::get_total_delegated_stake(old_coldkey); - let has_sufficient_delegation = total_delegated > 500_000_000_000; // 500 TAO in RAO - - // Only check the minimum balance if the old_coldkey is not a subnet owner - // and doesn't have sufficient delegation - if !(is_subnet_owner || has_sufficient_delegation) { - ensure!( - Self::meets_min_allowed_coldkey_balance(old_coldkey), - Error::::InsufficientBalanceToPerformColdkeySwap - ); - } - - // Get current destination coldkeys - let mut destination_coldkeys: Vec = - ColdkeySwapDestinations::::get(old_coldkey.clone()); - - // Calculate difficulty based on the number of existing destination coldkeys - let difficulty = Self::calculate_pow_difficulty(destination_coldkeys.len() as u32); - let work_hash = Self::vec_to_hash(work.clone()); - ensure!( - Self::hash_meets_difficulty(&work_hash, difficulty), - Error::::InvalidDifficulty - ); - - // Verify work is the product of the nonce, the block number, and coldkey - let seal = Self::create_seal_hash(block_number, nonce, old_coldkey); - ensure!(seal == work_hash, Error::::InvalidSeal); - - // Check if the new coldkey is already in the swap wallets list - ensure!( - !destination_coldkeys.contains(new_coldkey), - Error::::DuplicateColdkey - ); - - // If the destinations keys are empty or have less than the maximum allowed, we will add the new coldkey to the list - const MAX_COLDKEY_DESTINATIONS: usize = 10; - - if destination_coldkeys.len() < MAX_COLDKEY_DESTINATIONS { - destination_coldkeys.push(new_coldkey.clone()); - ColdkeySwapDestinations::::insert(old_coldkey.clone(), destination_coldkeys.clone()); - } else { - return Err(Error::::MaxColdkeyDestinationsReached.into()); - } - - // It is the first time we have seen this key - if destination_coldkeys.len() == 1_usize { - // Set the arbitration block for this coldkey - let arbitration_block: u64 = - Self::get_current_block_as_u64().saturating_add(ArbitrationPeriod::::get()); - ColdkeyArbitrationBlock::::insert(old_coldkey.clone(), arbitration_block); - - // Update the list of coldkeys to arbitrate on this block - let mut key_to_arbitrate_on_this_block: Vec = - ColdkeysToSwapAtBlock::::get(arbitration_block); - if !key_to_arbitrate_on_this_block.contains(old_coldkey) { - key_to_arbitrate_on_this_block.push(old_coldkey.clone()); - } - ColdkeysToSwapAtBlock::::insert(arbitration_block, key_to_arbitrate_on_this_block); - } - - // Emit an event indicating that a coldkey swap has been scheduled - Self::deposit_event(Event::ColdkeySwapScheduled { - old_coldkey: old_coldkey.clone(), - new_coldkey: new_coldkey.clone(), - arbitration_block: ColdkeyArbitrationBlock::::get(old_coldkey), - }); - - Ok(()) - } - - /// Calculate the proof of work difficulty based on the number of swap attempts - #[allow(clippy::arithmetic_side_effects)] - pub fn calculate_pow_difficulty(swap_attempts: u32) -> U256 { - let base_difficulty: U256 = U256::from(BaseDifficulty::::get()); // Base difficulty - base_difficulty.saturating_mul(U256::from(2).pow(U256::from(swap_attempts))) - } - - /// Arbitrates coldkeys that are scheduled to be swapped on this block. - /// - /// This function retrieves the list of coldkeys scheduled to be swapped on the current block, - /// and processes each coldkey by either extending the arbitration period or performing the swap - /// to the new coldkey. - /// - /// # Returns - /// - /// * `Weight` - The total weight consumed by this operation - pub fn swap_coldkeys_this_block(_weight_limit: &Weight) -> Result { - let mut weight_used = frame_support::weights::Weight::from_parts(0, 0); - - let current_block: u64 = Self::get_current_block_as_u64(); - log::debug!("Swapping coldkeys for block: {:?}", current_block); - - let source_coldkeys: Vec = ColdkeysToSwapAtBlock::::get(current_block); - ColdkeysToSwapAtBlock::::remove(current_block); - weight_used = weight_used.saturating_add(T::DbWeight::get().reads_writes(1, 1)); - - let mut keys_swapped = 0u64; - for coldkey_i in source_coldkeys.iter() { - // TODO: need a sane way to terminate early without locking users in. - // we should update the swap time - // if weight_used.ref_time() > weight_limit.ref_time() { - // log::warn!("Could not finish swapping all coldkeys this block due to weight limit, breaking after swapping {} keys.", keys_swapped); - // break; - // } - - let destinations_coldkeys: Vec = - ColdkeySwapDestinations::::get(coldkey_i); - weight_used = weight_used.saturating_add(T::DbWeight::get().reads(1)); - - if destinations_coldkeys.len() > 1 { - // Do not remove ColdkeySwapDestinations if there are multiple destinations - ColdkeyArbitrationBlock::::insert(coldkey_i.clone(), u64::MAX); - Self::deposit_event(Event::ArbitrationPeriodExtended { - coldkey: coldkey_i.clone(), - }); - } else if let Some(new_coldkey) = destinations_coldkeys.first() { - // Only remove ColdkeySwapDestinations if there's a single destination - ColdkeySwapDestinations::::remove(&coldkey_i); - weight_used = weight_used.saturating_add(T::DbWeight::get().writes(1)); - keys_swapped = keys_swapped.saturating_add(1); - let _ = Self::perform_swap_coldkey(coldkey_i, new_coldkey, &mut weight_used); - } - } - - Ok(weight_used) - } - } \ No newline at end of file diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 5c3fecbba..fb3c33e4d 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -30,78 +30,72 @@ impl Pallet { // 1. Ensure the origin is signed and get the coldkey let coldkey = ensure_signed(origin)?; - // 2. Check if the coldkey is in arbitration - ensure!( - !Self::coldkey_in_arbitration(&coldkey), - Error::::ColdkeyIsInArbitration - ); - - // 3. Initialize the weight for this operation + // 2. Initialize the weight for this operation let mut weight = T::DbWeight::get().reads(2); - // 4. Ensure the new hotkey is different from the old one + // 3. Ensure the new hotkey is different from the old one ensure!(old_hotkey != new_hotkey, Error::::NewHotKeyIsSameWithOld); - // 5. Ensure the new hotkey is not already registered on any network + // 4. Ensure the new hotkey is not already registered on any network ensure!( !Self::is_hotkey_registered_on_any_network(new_hotkey), Error::::HotKeyAlreadyRegisteredInSubNet ); - // 6. Update the weight for the checks above + // 5. Update the weight for the checks above weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 0)); - // 7. Ensure the coldkey owns the old hotkey + // 6. Ensure the coldkey owns the old hotkey ensure!( Self::coldkey_owns_hotkey(&coldkey, old_hotkey), Error::::NonAssociatedColdKey ); - // 8. Get the current block number + // 7. Get the current block number let block: u64 = Self::get_current_block_as_u64(); - // 9. Ensure the transaction rate limit is not exceeded + // 8. Ensure the transaction rate limit is not exceeded ensure!( !Self::exceeds_tx_rate_limit(Self::get_last_tx_block(&coldkey), block), Error::::HotKeySetTxRateLimitExceeded ); - // 10. Update the weight for reading the total networks + // 9. Update the weight for reading the total networks weight.saturating_accrue( T::DbWeight::get().reads((TotalNetworks::::get().saturating_add(1u16)) as u64), ); - // 11. Get the cost for swapping the key + // 10. Get the cost for swapping the key let swap_cost = Self::get_key_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); - // 12. Ensure the coldkey has enough balance to pay for the swap + // 11. Ensure the coldkey has enough balance to pay for the swap ensure!( Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapHotKey ); - // 13. Remove the swap cost from the coldkey's account + // 12. Remove the swap cost from the coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, swap_cost)?; - // 14. Burn the tokens + // 13. Burn the tokens Self::burn_tokens(actual_burn_amount); - // 15. Perform the hotkey swap + // 14. Perform the hotkey swap let _ = Self::perform_hotkey_swap(old_hotkey, new_hotkey, &coldkey, &mut weight); - // 16. Update the last transaction block for the coldkey + // 15. Update the last transaction block for the coldkey Self::set_last_tx_block(&coldkey, block); weight.saturating_accrue(T::DbWeight::get().writes(1)); - // 17. Emit an event for the hotkey swap + // 16. Emit an event for the hotkey swap Self::deposit_event(Event::HotkeySwapped { coldkey, old_hotkey: old_hotkey.clone(), new_hotkey: new_hotkey.clone(), }); - // 18. Return the weight of the operation + // 17. Return the weight of the operation Ok(Some(weight).into()) } diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 23ceea539..bcbfa5545 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -355,6 +355,7 @@ fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { } // New test to check if migration runs only once +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test migration -- test_migrate_fix_total_coldkey_stake_runs_once --exact --nocapture #[test] fn test_migrate_fix_total_coldkey_stake_runs_once() { new_test_ext(1).execute_with(|| { @@ -362,9 +363,9 @@ fn test_migrate_fix_total_coldkey_stake_runs_once() { let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); - Stake::::insert(U256::from(1), U256::from(0), 10000); - Stake::::insert(U256::from(2), U256::from(0), 10000); - Stake::::insert(U256::from(3), U256::from(0), 10000); + Stake::::insert(U256::from(1), coldkey, 10000); + Stake::::insert(U256::from(2), coldkey, 10000); + Stake::::insert(U256::from(3), coldkey, 10000); // First run let first_weight = run_migration_and_check(migration_name); @@ -391,14 +392,13 @@ fn test_migrate_fix_total_coldkey_stake_starts_with_value_no_stake_map_entries() let weight = run_migration_and_check(migration_name); assert!(weight != Weight::zero()); // Therefore 0 - assert_eq!(TotalColdkeyStake::::get(coldkey), 0); + assert_eq!(TotalColdkeyStake::::get(coldkey), 123_456_789); }) } fn run_migration_and_check(migration_name: &'static str) -> frame_support::weights::Weight { // Execute the migration and store its weight - let weight: frame_support::weights::Weight = - pallet_subtensor::migration::migrate_fix_total_coldkey_stake::(); + let weight: frame_support::weights::Weight = pallet_subtensor::migrations::migrate_fix_total_coldkey_stake::migrate_fix_total_coldkey_stake::(); // Check if the migration has been marked as completed assert!(HasMigrationRun::::get( From bc19f08f50b720308533b15144b0063cd96d4c3f Mon Sep 17 00:00:00 2001 From: const Date: Tue, 23 Jul 2024 14:26:28 -0500 Subject: [PATCH 049/269] s --- pallets/subtensor/src/macros/config.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index aca0f1b30..e59eac5ca 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -1,3 +1,5 @@ +#![allow(clippy::crate_in_macro_def)] + use frame_support::pallet_macros::pallet_section; /// A [`pallet_section`] that defines the errors for a pallet. From 28ba654d28ef2b6a3b7864bc314479c03b7c492c Mon Sep 17 00:00:00 2001 From: unconst Date: Tue, 23 Jul 2024 14:28:57 -0500 Subject: [PATCH 050/269] clippy --- pallets/subtensor/src/coinbase/run_coinbase.rs | 2 +- pallets/subtensor/src/lib.rs | 2 +- .../migrations/migrate_fix_total_coldkey_stake.rs | 2 +- pallets/subtensor/src/subnets/registration.rs | 6 +++++- pallets/subtensor/src/swap/swap_coldkey.rs | 4 ++-- pallets/subtensor/tests/coinbase.rs | 1 + pallets/subtensor/tests/migration.rs | 13 ++++++------- pallets/subtensor/tests/swap_coldkey.rs | 2 +- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 0f747a89e..f3fbf8e73 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -372,4 +372,4 @@ impl Pallet { let remainder = block_plus_netuid.rem_euclid(tempo_plus_one); (tempo as u64).saturating_sub(remainder) } -} \ No newline at end of file +} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 29969a23b..6c16902d9 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1073,7 +1073,7 @@ pub mod pallet { (H256, u64), OptionQuery, >; - + /// ================== /// ==== Genesis ===== /// ================== diff --git a/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs index 268c82d48..d8534d03a 100644 --- a/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs +++ b/pallets/subtensor/src/migrations/migrate_fix_total_coldkey_stake.rs @@ -1,11 +1,11 @@ use super::*; +use alloc::string::String; use frame_support::{ pallet_prelude::{Identity, OptionQuery}, storage_alias, traits::{Get, StorageVersion}, weights::Weight, }; -use alloc::string::String; use sp_std::vec::Vec; // TODO (camfairchild): TEST MIGRATION diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index ecff0fd99..9319adcd1 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -550,7 +550,11 @@ impl Pallet { // Copy the hotkey_bytes into the first half of full_bytes full_bytes[..32].copy_from_slice(hotkey_bytes); let keccak_256_seal_hash_vec: [u8; 32] = keccak_256(&full_bytes[..]); - let hash_u64: u64 = u64::from_le_bytes(keccak_256_seal_hash_vec[0..8].try_into().unwrap_or_default()); + let hash_u64: u64 = u64::from_le_bytes( + keccak_256_seal_hash_vec[0..8] + .try_into() + .unwrap_or_default(), + ); hash_u64 } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 507fc62a5..bd8a11fa1 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -1,6 +1,6 @@ use super::*; use frame_support::weights::Weight; -use sp_core::{Get}; +use sp_core::Get; impl Pallet { /// Swaps the coldkey associated with a set of hotkeys from an old coldkey to a new coldkey. @@ -225,4 +225,4 @@ impl Pallet { // Return ok. Ok(()) } -} \ No newline at end of file +} diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index aed3c9b55..8fd963dff 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -1,3 +1,4 @@ +#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] use crate::mock::*; mod mock; // use frame_support::{assert_err, assert_ok}; diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index bcbfa5545..bd77c7be0 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -1,5 +1,4 @@ -#![allow(clippy::unwrap_used)] - +#![allow(unused, clippy::indexing_slicing, clippy::panic, clippy::unwrap_used)] mod mock; use frame_support::{assert_ok, weights::Weight}; use frame_system::Config; @@ -283,7 +282,7 @@ fn test_migration_delete_subnet_21() { #[test] fn test_migrate_fix_total_coldkey_stake() { new_test_ext(1).execute_with(|| { - let migration_name = "fix_total_coldkey_stake_v7"; + let _migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); @@ -299,7 +298,7 @@ fn test_migrate_fix_total_coldkey_stake() { #[test] fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { new_test_ext(1).execute_with(|| { - let migration_name = "fix_total_coldkey_stake_v7"; + let _migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); @@ -315,7 +314,7 @@ fn test_migrate_fix_total_coldkey_stake_value_already_in_total() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry() { new_test_ext(1).execute_with(|| { - let migration_name = "fix_total_coldkey_stake_v7"; + let _migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); Stake::::insert(U256::from(1), U256::from(0), 10000); @@ -330,7 +329,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry() { #[test] fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { new_test_ext(1).execute_with(|| { - let migration_name = "fix_total_coldkey_stake_v7"; + let _migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); @@ -343,7 +342,7 @@ fn test_migrate_fix_total_coldkey_stake_no_entry_in_hotkeys() { #[test] fn test_migrate_fix_total_coldkey_stake_one_hotkey_stake_missing() { new_test_ext(1).execute_with(|| { - let migration_name = "fix_total_coldkey_stake_v7"; + let _migration_name = "fix_total_coldkey_stake_v7"; let coldkey = U256::from(0); TotalColdkeyStake::::insert(coldkey, 100000000); StakingHotkeys::::insert(coldkey, vec![U256::from(1), U256::from(2), U256::from(3)]); diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index df109f992..9203e2b3f 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1285,4 +1285,4 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, new_coldkey), 100); assert_eq!(Stake::::get(delegate, coldkey), 0); }); -} \ No newline at end of file +} From 4726ec3e9d39b9f5e7266a75e7fb51cb414c26d0 Mon Sep 17 00:00:00 2001 From: unconst Date: Tue, 23 Jul 2024 14:32:20 -0500 Subject: [PATCH 051/269] fix divide by zero --- pallets/subtensor/src/coinbase/run_coinbase.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index f3fbf8e73..df35304b1 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -299,7 +299,8 @@ impl Pallet { // --- 10 Calculate this nominator's share of the emission. let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) .saturating_mul(I64F64::from_num(nominator_stake)) - .saturating_div(I64F64::from_num(total_hotkey_stake)); + .check_div(I64F64::from_num(total_hotkey_stake)) + .unwrap_or(I64F64::from_num(0)); // --- 11 Increase the stake for the nominator. Self::increase_stake_on_coldkey_hotkey_account( From e90fc16f688c43b2695e5c7e339557be52f16319 Mon Sep 17 00:00:00 2001 From: unconst Date: Tue, 23 Jul 2024 14:38:09 -0500 Subject: [PATCH 052/269] fix checked div error --- pallets/subtensor/src/coinbase/run_coinbase.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index df35304b1..abe44d55a 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -299,7 +299,7 @@ impl Pallet { // --- 10 Calculate this nominator's share of the emission. let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) .saturating_mul(I64F64::from_num(nominator_stake)) - .check_div(I64F64::from_num(total_hotkey_stake)) + .checked_div(I64F64::from_num(total_hotkey_stake)) .unwrap_or(I64F64::from_num(0)); // --- 11 Increase the stake for the nominator. From af50dd63462e7dc920f6c18d26140efb52d3e6ac Mon Sep 17 00:00:00 2001 From: unconst Date: Tue, 23 Jul 2024 16:23:24 -0500 Subject: [PATCH 053/269] adds a doublt Tempo limit to setting childkeys --- .../subtensor/src/coinbase/run_coinbase.rs | 66 ++++++++-------- pallets/subtensor/src/lib.rs | 12 +++ pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/staking/add_stake.rs | 4 +- pallets/subtensor/src/staking/set_children.rs | 9 +++ pallets/subtensor/src/utils.rs | 77 +++++++++++++++++++ 6 files changed, 137 insertions(+), 33 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index abe44d55a..fcf76728f 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -1,5 +1,4 @@ use super::*; -use frame_support::storage::IterableStorageDoubleMap; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; @@ -262,7 +261,7 @@ impl Pallet { PendingdHotkeyEmission::::insert(hotkey, 0); // --- 2 Retrieve the last time this hotkey's emissions were drained. - let last_hotkey_emission_drain: u64 = LastHotkeyEmissionDrain::::get(hotkey); + let last_emission_drain: u64 = LastHotkeyEmissionDrain::::get(hotkey); // --- 3 Update the block value to the current block number. LastHotkeyEmissionDrain::::insert(hotkey, block_number); @@ -282,43 +281,48 @@ impl Pallet { // --- 7 Calculate the remaining emission after the hotkey's take. let mut remainder: u64 = emission_minus_take; - // --- 8 Iterate over each nominator. - for (nominator, nominator_stake) in - as IterableStorageDoubleMap>::iter_prefix( - hotkey, - ) - { - // --- 9 Check if the stake was manually increased by the user since the last emission drain for this hotkey. - // If it was, skip this nominator as they will not receive their proportion of the emission. - if LastAddStakeIncrease::::get(hotkey, nominator.clone()) - > last_hotkey_emission_drain - { - continue; + // --- 8 Iterate over each nominator and get all viable stake. + let mut total_viable_nominator_stake: u64 = total_hotkey_stake; + for (nominator, nominator_stake) in Stake::::iter_prefix(hotkey) { + if LastAddStakeIncrease::::get(hotkey, nominator) > last_emission_drain { + total_viable_nominator_stake = + total_viable_nominator_stake.saturating_sub(nominator_stake); } + } - // --- 10 Calculate this nominator's share of the emission. - let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) - .saturating_mul(I64F64::from_num(nominator_stake)) - .checked_div(I64F64::from_num(total_hotkey_stake)) - .unwrap_or(I64F64::from_num(0)); - - // --- 11 Increase the stake for the nominator. - Self::increase_stake_on_coldkey_hotkey_account( - &nominator, - hotkey, - nominator_emission.to_num::(), - ); + // --- 9 Iterate over each nominator. + if total_viable_nominator_stake != 0 { + for (nominator, nominator_stake) in Stake::::iter_prefix(hotkey) { + // --- 10 Check if the stake was manually increased by the user since the last emission drain for this hotkey. + // If it was, skip this nominator as they will not receive their proportion of the emission. + if LastAddStakeIncrease::::get(hotkey, nominator.clone()) > last_emission_drain { + continue; + } - // --- 11* Record event and Subtract the nominator's emission from the remainder. - total_new_tao = total_new_tao.saturating_add(nominator_emission.to_num::()); - remainder = remainder.saturating_sub(nominator_emission.to_num::()); + // --- 11 Calculate this nominator's share of the emission. + let nominator_emission: I64F64 = I64F64::from_num(emission_minus_take) + .saturating_mul(I64F64::from_num(nominator_stake)) + .checked_div(I64F64::from_num(total_viable_nominator_stake)) + .unwrap_or(I64F64::from_num(0)); + + // --- 12 Increase the stake for the nominator. + Self::increase_stake_on_coldkey_hotkey_account( + &nominator, + hotkey, + nominator_emission.to_num::(), + ); + + // --- 13* Record event and Subtract the nominator's emission from the remainder. + total_new_tao = total_new_tao.saturating_add(nominator_emission.to_num::()); + remainder = remainder.saturating_sub(nominator_emission.to_num::()); + } } - // --- 13 Finally, add the stake to the hotkey itself, including its take and the remaining emission. + // --- 14 Finally, add the stake to the hotkey itself, including its take and the remaining emission. let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder); Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao); - // --- 14 Record new tao creation event and return the amount created. + // --- 15 Record new tao creation event and return the amount created. total_new_tao = total_new_tao.saturating_add(hotkey_new_tao); total_new_tao } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6c16902d9..abf6a8613 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -44,6 +44,7 @@ pub mod staking; pub mod subnets; pub mod swap; pub mod utils; +use crate::utils::TransactionType; use macros::{config, dispatches, errors, events, genesis, hooks}; // apparently this is stabilized since rust 1.36 @@ -1051,6 +1052,17 @@ pub mod pallet { /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= + #[pallet::storage] // --- NMAP ( hot, netuid, name ) --> last_block | Returns the last block of a transaction for a given key, netuid, and name. + pub type TransactionKeyLastBlock = StorageNMap< + _, + ( + NMapKey, // hot + NMapKey, // netuid + NMapKey, // extrinsic enum. + ), + u64, + ValueQuery, + >; #[pallet::storage] /// --- MAP ( key ) --> last_block pub type LastTxBlock = diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 7e244834d..156cbea56 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -166,5 +166,7 @@ mod errors { ProportionOverflow, /// Too many children MAX 5. TooManyChildren, + /// Default transaction rate limit exceeded. + TxRateLimitExceeded, } } diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index eb7762bec..f62fd7cd2 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -70,8 +70,8 @@ impl Pallet { Error::::StakeRateLimitExceeded ); - // If this is a nomination stake, check if total stake after adding will be above - // the minimum required stake. + // Set the last time the stake increased for nominator drain protection. + LastAddStakeIncrease::::insert(&hotkey, &coldkey, Self::get_current_block_as_u64()); // If coldkey is not owner of the hotkey, it's a nomination stake. if !Self::coldkey_owns_hotkey(&coldkey, &hotkey) { diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 071764734..f413db23f 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -58,6 +58,15 @@ impl Pallet { children ); + // Ensure the hotkey passes the rate limit. + ensure!( + Self::passes_rate_limit_globally( + &TransactionType::SetChildren, // Set children. + &hotkey, // Specific to a hotkey. + ), + Error::::TxRateLimitExceeded + ); + // --- 2. Check that this delegation is not on the root network. Child hotkeys are not valid on root. ensure!( netuid != Self::get_root_netuid(), diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 6f5dbeaff..2cd49e198 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -7,6 +7,33 @@ use sp_core::Get; use sp_core::U256; use substrate_fixed::types::I32F32; +/// Enum representing different types of transactions +#[derive(Copy, Clone)] +pub enum TransactionType { + SetChildren, + Unknown, +} + +/// Implement conversion from TransactionType to u16 +impl From for u16 { + fn from(tx_type: TransactionType) -> Self { + match tx_type { + TransactionType::SetChildren => 0, + TransactionType::Unknown => 1, + } + } +} + +/// Implement conversion from u16 to TransactionType +impl From for TransactionType { + fn from(value: u16) -> Self { + match value { + 0 => TransactionType::SetChildren, + _ => TransactionType::Unknown, + } + } +} + impl Pallet { pub fn ensure_subnet_owner_or_root( o: T::RuntimeOrigin, @@ -278,6 +305,56 @@ impl Pallet { // ======================== // ==== Rate Limiting ===== // ======================== + /// Get the rate limit for a specific transaction type + pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { + match tx_type { + TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. + TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) + } + } + + /// Check if a transaction should be rate limited on a specific subnet + pub fn passes_rate_limit_on_subnet( + tx_type: &TransactionType, + hotkey: &T::AccountId, + netuid: u16, + ) -> bool { + let block: u64 = Self::get_current_block_as_u64(); + let limit: u64 = Self::get_rate_limit(tx_type); + let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); + block.saturating_sub(last_block) < limit + } + + /// Check if a transaction should be rate limited globally + pub fn passes_rate_limit_globally(tx_type: &TransactionType, hotkey: &T::AccountId) -> bool { + let netuid: u16 = u16::MAX; + let block: u64 = Self::get_current_block_as_u64(); + let limit: u64 = Self::get_rate_limit(tx_type); + let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); + block.saturating_sub(last_block) >= limit + } + + /// Get the block number of the last transaction for a specific hotkey, network, and transaction type + pub fn get_last_transaction_block( + hotkey: &T::AccountId, + netuid: u16, + tx_type: &TransactionType, + ) -> u64 { + let tx_as_u16: u16 = (*tx_type).into(); + TransactionKeyLastBlock::::get((hotkey, netuid, tx_as_u16)) + } + + /// Set the block number of the last transaction for a specific hotkey, network, and transaction type + pub fn set_last_transaction_block( + hotkey: &T::AccountId, + netuid: u16, + tx_type: &TransactionType, + block: u64, + ) { + let tx_as_u16: u16 = (*tx_type).into(); + TransactionKeyLastBlock::::insert((hotkey, netuid, tx_as_u16), block); + } + pub fn set_last_tx_block(key: &T::AccountId, block: u64) { LastTxBlock::::insert(key, block) } From f9b6828806dd252de9255226c9822a6647c54c7a Mon Sep 17 00:00:00 2001 From: unconst Date: Wed, 24 Jul 2024 13:42:18 -0500 Subject: [PATCH 054/269] try state for total issuance done --- pallets/subtensor/src/lib.rs | 11 +- pallets/subtensor/src/macros/hooks.rs | 6 + .../migrations/migrate_init_total_issuance.rs | 83 +++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/staking/helpers.rs | 1 - .../subtensor/src/{utils.rs => utils/misc.rs} | 92 ++------------ pallets/subtensor/src/utils/mod.rs | 4 + pallets/subtensor/src/utils/rate_limiting.rs | 112 ++++++++++++++++++ pallets/subtensor/src/utils/try_state.rs | 49 ++++++++ pallets/subtensor/tests/migration.rs | 25 ++++ runtime/src/lib.rs | 25 +++- 11 files changed, 317 insertions(+), 92 deletions(-) create mode 100644 pallets/subtensor/src/migrations/migrate_init_total_issuance.rs rename pallets/subtensor/src/{utils.rs => utils/misc.rs} (89%) create mode 100644 pallets/subtensor/src/utils/mod.rs create mode 100644 pallets/subtensor/src/utils/rate_limiting.rs create mode 100644 pallets/subtensor/src/utils/try_state.rs diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index abf6a8613..1450b5706 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -44,7 +44,7 @@ pub mod staking; pub mod subnets; pub mod swap; pub mod utils; -use crate::utils::TransactionType; +use crate::utils::rate_limiting::TransactionType; use macros::{config, dispatches, errors, events, genesis, hooks}; // apparently this is stabilized since rust 1.36 @@ -563,6 +563,15 @@ pub mod pallet { /// ============================ /// ==== Staking Variables ==== /// ============================ + /// The Subtensor [`TotalIssuance`] represents the total issuance of tokens on the Bittensor network. + /// + /// It is comprised of three parts: + /// - The total amount of issued tokens, tracked in the TotalIssuance of the Balances pallet + /// - The total amount of tokens staked in the system, tracked in [`TotalStake`] + /// - The total amount of tokens locked up for subnet reg, tracked in [`TotalSubnetLocked`] attained by iterating over subnet lock. + /// + /// Eventually, Bittensor should migrate to using Holds afterwhich time we will not require this + /// separate accounting. #[pallet::storage] // --- ITEM ( total_issuance ) pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; #[pallet::storage] // --- ITEM ( total_stake ) diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index b04a29ff6..22c741465 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -71,5 +71,11 @@ mod hooks { .saturating_add(migrations::migrate_fix_total_coldkey_stake::migrate_fix_total_coldkey_stake::()); weight } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::check_accounting_invariants()?; + Ok(()) + } } } diff --git a/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs b/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs new file mode 100644 index 000000000..a488771c5 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_init_total_issuance.rs @@ -0,0 +1,83 @@ +use super::*; +use frame_support::pallet_prelude::OptionQuery; +use frame_support::{pallet_prelude::Identity, storage_alias}; +use sp_std::vec::Vec; + +// TODO: Implement comprehensive tests for this migration + +/// Module containing deprecated storage format for LoadedEmission +pub mod deprecated_loaded_emission_format { + use super::*; + + #[storage_alias] + pub(super) type LoadedEmission = + StorageMap, Identity, u16, Vec<(AccountIdOf, u64)>, OptionQuery>; +} + +pub mod initialise_total_issuance { + use frame_support::pallet_prelude::Weight; + use frame_support::traits::{fungible, OnRuntimeUpgrade}; + use sp_core::Get; + + use crate::*; + + pub struct Migration(PhantomData); + + impl OnRuntimeUpgrade for Migration { + /// Performs the migration to initialize and update the total issuance. + /// + /// This function does the following: + /// 1. Calculates the total locked tokens across all subnets + /// 2. Retrieves the total account balances and total stake + /// 3. Computes and updates the new total issuance + /// + /// Returns the weight of the migration operation. + fn on_runtime_upgrade() -> Weight { + // Calculate the total locked tokens across all subnets + let subnets_len = crate::SubnetLocked::::iter().count() as u64; + let total_subnet_locked: u64 = + crate::SubnetLocked::::iter().fold(0, |acc, (_, v)| acc.saturating_add(v)); + + // Retrieve the total balance of all accounts + let total_account_balances = <::Currency as fungible::Inspect< + ::AccountId, + >>::total_issuance(); + + // Get the total stake from the system + let total_stake = crate::TotalStake::::get(); + + // Retrieve the previous total issuance for logging purposes + let prev_total_issuance = crate::TotalIssuance::::get(); + + // Calculate the new total issuance + let new_total_issuance = total_account_balances + .saturating_add(total_stake) + .saturating_add(total_subnet_locked); + + // Update the total issuance in storage + crate::TotalIssuance::::put(new_total_issuance); + + // Log the change in total issuance + log::info!( + "Subtensor Pallet Total Issuance Updated: previous: {:?}, new: {:?}", + prev_total_issuance, + new_total_issuance + ); + + // Return the weight of the operation + // We performed subnets_len + 5 reads and 1 write + ::DbWeight::get() + .reads_writes(subnets_len.saturating_add(5), 1) + } + + /// Performs post-upgrade checks to ensure the migration was successful. + /// + /// This function is only compiled when the "try-runtime" feature is enabled. + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + // Verify that all accounting invariants are satisfied after the migration + crate::Pallet::::check_accounting_invariants()?; + Ok(()) + } + } +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index df0f7ce06..5b93dbbaf 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -3,6 +3,7 @@ pub mod migrate_create_root_network; pub mod migrate_delete_subnet_21; pub mod migrate_delete_subnet_3; pub mod migrate_fix_total_coldkey_stake; +pub mod migrate_init_total_issuance; pub mod migrate_populate_owned_hotkeys; pub mod migrate_populate_staking_hotkeys; pub mod migrate_to_v1_separate_emission; diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 486577712..dad071fb5 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -284,7 +284,6 @@ impl Pallet { TotalHotkeyStake::::mutate(hotkey, |stake| *stake = stake.saturating_sub(current_stake)); Stake::::remove(hotkey, coldkey); TotalStake::::mutate(|stake| *stake = stake.saturating_sub(current_stake)); - TotalIssuance::::mutate(|issuance| *issuance = issuance.saturating_sub(current_stake)); // Update StakingHotkeys map let mut staking_hotkeys = StakingHotkeys::::get(coldkey); diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils/misc.rs similarity index 89% rename from pallets/subtensor/src/utils.rs rename to pallets/subtensor/src/utils/misc.rs index 2cd49e198..9155357c0 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -5,6 +5,7 @@ use crate::{ }; use sp_core::Get; use sp_core::U256; +use sp_runtime::Saturating; use substrate_fixed::types::I32F32; /// Enum representing different types of transactions @@ -302,88 +303,6 @@ impl Pallet { Ok(()) } - // ======================== - // ==== Rate Limiting ===== - // ======================== - /// Get the rate limit for a specific transaction type - pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { - match tx_type { - TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. - TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) - } - } - - /// Check if a transaction should be rate limited on a specific subnet - pub fn passes_rate_limit_on_subnet( - tx_type: &TransactionType, - hotkey: &T::AccountId, - netuid: u16, - ) -> bool { - let block: u64 = Self::get_current_block_as_u64(); - let limit: u64 = Self::get_rate_limit(tx_type); - let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); - block.saturating_sub(last_block) < limit - } - - /// Check if a transaction should be rate limited globally - pub fn passes_rate_limit_globally(tx_type: &TransactionType, hotkey: &T::AccountId) -> bool { - let netuid: u16 = u16::MAX; - let block: u64 = Self::get_current_block_as_u64(); - let limit: u64 = Self::get_rate_limit(tx_type); - let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); - block.saturating_sub(last_block) >= limit - } - - /// Get the block number of the last transaction for a specific hotkey, network, and transaction type - pub fn get_last_transaction_block( - hotkey: &T::AccountId, - netuid: u16, - tx_type: &TransactionType, - ) -> u64 { - let tx_as_u16: u16 = (*tx_type).into(); - TransactionKeyLastBlock::::get((hotkey, netuid, tx_as_u16)) - } - - /// Set the block number of the last transaction for a specific hotkey, network, and transaction type - pub fn set_last_transaction_block( - hotkey: &T::AccountId, - netuid: u16, - tx_type: &TransactionType, - block: u64, - ) { - let tx_as_u16: u16 = (*tx_type).into(); - TransactionKeyLastBlock::::insert((hotkey, netuid, tx_as_u16), block); - } - - pub fn set_last_tx_block(key: &T::AccountId, block: u64) { - LastTxBlock::::insert(key, block) - } - pub fn get_last_tx_block(key: &T::AccountId) -> u64 { - LastTxBlock::::get(key) - } - pub fn set_last_tx_block_delegate_take(key: &T::AccountId, block: u64) { - LastTxBlockDelegateTake::::insert(key, block) - } - pub fn get_last_tx_block_delegate_take(key: &T::AccountId) -> u64 { - LastTxBlockDelegateTake::::get(key) - } - pub fn exceeds_tx_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { - let rate_limit: u64 = Self::get_tx_rate_limit(); - if rate_limit == 0 || prev_tx_block == 0 { - return false; - } - - current_block.saturating_sub(prev_tx_block) <= rate_limit - } - pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { - let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); - if rate_limit == 0 || prev_tx_block == 0 { - return false; - } - - current_block.saturating_sub(prev_tx_block) <= rate_limit - } - // ======================== // === Token Management === // ======================== @@ -404,14 +323,19 @@ impl Pallet { pub fn get_min_take() -> u16 { MinTake::::get() } - pub fn set_subnet_locked_balance(netuid: u16, amount: u64) { SubnetLocked::::insert(netuid, amount); } - pub fn get_subnet_locked_balance(netuid: u16) -> u64 { SubnetLocked::::get(netuid) } + pub fn get_total_subnet_locked() -> u64 { + let mut total_subnet_locked: u64 = 0; + for (_, locked) in SubnetLocked::::iter() { + total_subnet_locked.saturating_accrue(locked); + } + total_subnet_locked + } // ======================== // ========= Sudo ========= diff --git a/pallets/subtensor/src/utils/mod.rs b/pallets/subtensor/src/utils/mod.rs new file mode 100644 index 000000000..72b903dd8 --- /dev/null +++ b/pallets/subtensor/src/utils/mod.rs @@ -0,0 +1,4 @@ +use super::*; +pub mod misc; +pub mod rate_limiting; +pub mod try_state; diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs new file mode 100644 index 000000000..ffd17ca93 --- /dev/null +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -0,0 +1,112 @@ +use super::*; +use sp_core::Get; + +/// Enum representing different types of transactions +#[derive(Copy, Clone)] +pub enum TransactionType { + SetChildren, + Unknown, +} + +/// Implement conversion from TransactionType to u16 +impl From for u16 { + fn from(tx_type: TransactionType) -> Self { + match tx_type { + TransactionType::SetChildren => 0, + TransactionType::Unknown => 1, + } + } +} + +/// Implement conversion from u16 to TransactionType +impl From for TransactionType { + fn from(value: u16) -> Self { + match value { + 0 => TransactionType::SetChildren, + _ => TransactionType::Unknown, + } + } +} +impl Pallet { + // ======================== + // ==== Rate Limiting ===== + // ======================== + /// Get the rate limit for a specific transaction type + pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { + match tx_type { + TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. + TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) + } + } + + /// Check if a transaction should be rate limited on a specific subnet + pub fn passes_rate_limit_on_subnet( + tx_type: &TransactionType, + hotkey: &T::AccountId, + netuid: u16, + ) -> bool { + let block: u64 = Self::get_current_block_as_u64(); + let limit: u64 = Self::get_rate_limit(tx_type); + let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); + block.saturating_sub(last_block) < limit + } + + /// Check if a transaction should be rate limited globally + pub fn passes_rate_limit_globally(tx_type: &TransactionType, hotkey: &T::AccountId) -> bool { + let netuid: u16 = u16::MAX; + let block: u64 = Self::get_current_block_as_u64(); + let limit: u64 = Self::get_rate_limit(tx_type); + let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); + block.saturating_sub(last_block) >= limit + } + + /// Get the block number of the last transaction for a specific hotkey, network, and transaction type + pub fn get_last_transaction_block( + hotkey: &T::AccountId, + netuid: u16, + tx_type: &TransactionType, + ) -> u64 { + let tx_as_u16: u16 = (*tx_type).into(); + TransactionKeyLastBlock::::get((hotkey, netuid, tx_as_u16)) + } + + /// Set the block number of the last transaction for a specific hotkey, network, and transaction type + pub fn set_last_transaction_block( + hotkey: &T::AccountId, + netuid: u16, + tx_type: &TransactionType, + block: u64, + ) { + let tx_as_u16: u16 = (*tx_type).into(); + TransactionKeyLastBlock::::insert((hotkey, netuid, tx_as_u16), block); + } + + pub fn set_last_tx_block(key: &T::AccountId, block: u64) { + LastTxBlock::::insert(key, block) + } + pub fn get_last_tx_block(key: &T::AccountId) -> u64 { + LastTxBlock::::get(key) + } + pub fn set_last_tx_block_delegate_take(key: &T::AccountId, block: u64) { + LastTxBlockDelegateTake::::insert(key, block) + } + pub fn get_last_tx_block_delegate_take(key: &T::AccountId) -> u64 { + LastTxBlockDelegateTake::::get(key) + } + pub fn exceeds_tx_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { + let rate_limit: u64 = Self::get_tx_rate_limit(); + if rate_limit == 0 || prev_tx_block == 0 { + return false; + } + + current_block.saturating_sub(prev_tx_block) <= rate_limit + } + pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { + let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); + if rate_limit == 0 || prev_tx_block == 0 { + return false; + } + + current_block.saturating_sub(prev_tx_block) <= rate_limit + } +} diff --git a/pallets/subtensor/src/utils/try_state.rs b/pallets/subtensor/src/utils/try_state.rs new file mode 100644 index 000000000..4763c0484 --- /dev/null +++ b/pallets/subtensor/src/utils/try_state.rs @@ -0,0 +1,49 @@ +use super::*; + +impl Pallet { + /// Checks if the accounting invariants for [`TotalStake`], [`TotalSubnetLocked`], and [`TotalIssuance`] are correct. + /// + /// This function verifies that: + /// 1. The sum of all stakes matches the [`TotalStake`]. + /// 2. The [`TotalSubnetLocked`] is correctly calculated. + /// 3. The [`TotalIssuance`] equals the sum of currency issuance, total stake, and total subnet locked. + /// + /// # Returns + /// + /// Returns `Ok(())` if all invariants are correct, otherwise returns an error. + #[cfg(feature = "try-runtime")] + pub fn check_accounting_invariants() -> Result<(), sp_runtime::TryRuntimeError> { + use frame_support::traits::fungible::Inspect; + + // Calculate the total staked amount + let mut total_staked: u64 = 0; + for (_account, _netuid, stake) in Stake::::iter() { + total_staked = total_staked.saturating_add(stake); + } + + // Verify that the calculated total stake matches the stored TotalStake + ensure!( + total_staked == TotalStake::::get(), + "TotalStake does not match total staked", + ); + + // Get the total subnet locked amount + let total_subnet_locked: u64 = Self::get_total_subnet_locked(); + + // Get the total currency issuance + let currency_issuance: u64 = T::Currency::total_issuance(); + + // Calculate the expected total issuance + let expected_total_issuance: u64 = currency_issuance + .saturating_add(total_staked) + .saturating_add(total_subnet_locked); + + // Verify that the calculated total issuance matches the stored TotalIssuance + ensure!( + TotalIssuance::::get() == expected_total_issuance, + "TotalIssuance accounting discrepancy", + ); + + Ok(()) + } +} diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index bd77c7be0..244c2d86a 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -6,6 +6,31 @@ use mock::*; use pallet_subtensor::*; use sp_core::U256; +#[test] +fn test_initialise_ti() { + use frame_support::traits::OnRuntimeUpgrade; + + new_test_ext(1).execute_with(|| { + pallet_subtensor::SubnetLocked::::insert(1, 100); + pallet_subtensor::SubnetLocked::::insert(2, 5); + pallet_balances::TotalIssuance::::put(1000); + pallet_subtensor::TotalStake::::put(25); + + // Ensure values are NOT initialized prior to running migration + assert!(pallet_subtensor::TotalIssuance::::get() == 0); + assert!(pallet_subtensor::TotalSubnetLocked::::get() == 0); + + pallet_subtensor::migration::initialise_total_issuance::Migration::::on_runtime_upgrade(); + + // Ensure values were initialized correctly + assert!(pallet_subtensor::TotalSubnetLocked::::get() == 105); + assert!( + pallet_subtensor::TotalIssuance::::get() + == 105u64.saturating_add(1000).saturating_add(25) + ); + }); +} + #[test] fn test_migration_fix_total_stake_maps() { new_test_ext(1).execute_with(|| { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2d1716337..3bd71977e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,13 +12,15 @@ pub mod check_nonce; mod migrations; use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::Imbalance; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, pallet_prelude::{DispatchError, Get}, - traits::{fungible::HoldConsideration, Contains, LinearStoragePrice}, + traits::{fungible::HoldConsideration, Contains, LinearStoragePrice, OnUnbalanced}, }; use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, RawOrigin}; +use pallet_balances::NegativeImbalance; use pallet_commitments::CanCommit; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, @@ -388,11 +390,22 @@ parameter_types! { pub FeeMultiplier: Multiplier = Multiplier::one(); } +/// Deduct the transaction fee from the Subtensor Pallet TotalIssuance when dropping the transaction +/// fee. +pub struct TransactionFeeHandler; +impl OnUnbalanced> for TransactionFeeHandler { + fn on_nonzero_unbalanced(credit: NegativeImbalance) { + let ti_before = pallet_subtensor::TotalIssuance::::get(); + pallet_subtensor::TotalIssuance::::put(ti_before.saturating_sub(credit.peek())); + drop(credit); + } +} + impl pallet_transaction_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter; //type TransactionByteFee = TransactionByteFee; + type OnChargeTransaction = CurrencyAdapter; // Convert dispatch weight to a chargeable fee. type WeightToFee = LinearWeightToFee; @@ -1295,7 +1308,10 @@ pub type SignedExtra = ( frame_metadata_hash_extension::CheckMetadataHash, ); -type Migrations = pallet_grandpa::migrations::MigrateV4ToV5; +type Migrations = + pallet_subtensor::migrations::migrate_init_total_issuance::initialise_total_issuance::Migration< + Runtime, + >; // Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -1536,10 +1552,7 @@ impl_runtime_apis! { use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; - #[allow(non_local_definitions)] impl frame_system_benchmarking::Config for Runtime {} - - #[allow(non_local_definitions)] impl baseline::Config for Runtime {} use frame_support::traits::WhitelistedStorageKeys; From c9586214da22ad0474e48dbfe6abe6758a524513 Mon Sep 17 00:00:00 2001 From: unconst Date: Wed, 24 Jul 2024 13:52:53 -0500 Subject: [PATCH 055/269] ranked choice voting --- pallets/subtensor/tests/migration.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 244c2d86a..2a1388237 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -18,12 +18,10 @@ fn test_initialise_ti() { // Ensure values are NOT initialized prior to running migration assert!(pallet_subtensor::TotalIssuance::::get() == 0); - assert!(pallet_subtensor::TotalSubnetLocked::::get() == 0); - pallet_subtensor::migration::initialise_total_issuance::Migration::::on_runtime_upgrade(); + pallet_subtensor::migrations::migrate_init_total_issuance::initialise_total_issuance::Migration::::on_runtime_upgrade(); // Ensure values were initialized correctly - assert!(pallet_subtensor::TotalSubnetLocked::::get() == 105); assert!( pallet_subtensor::TotalIssuance::::get() == 105u64.saturating_add(1000).saturating_add(25) From 53288a0449d9ff488e2641f4629220bc449da7c3 Mon Sep 17 00:00:00 2001 From: unconst Date: Wed, 24 Jul 2024 14:07:32 -0500 Subject: [PATCH 056/269] add tests and add swap --- pallets/subtensor/src/swap/swap_hotkey.rs | 34 ++++++ pallets/subtensor/tests/swap_hotkey.rs | 127 ++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index fb3c33e4d..faadf5de5 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -307,6 +307,40 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); } + // 11. Swap ChildKeys. + // ChildKeys( parent, netuid ) --> Vec<(proportion,child)> -- the child keys of the parent. + for netuid in Self::get_all_subnet_netuids() { + // Get the children of the old hotkey for this subnet + let my_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(old_hotkey, netuid); + // Remove the old hotkey's child entries + ChildKeys::::remove(old_hotkey, netuid); + // Insert the same child entries for the new hotkey + ChildKeys::::insert(new_hotkey, netuid, my_children); + } + + // 12. Swap ParentKeys. + // ParentKeys( child, netuid ) --> Vec<(proportion,parent)> -- the parent keys of the child. + for netuid in Self::get_all_subnet_netuids() { + // Get the parents of the old hotkey for this subnet + let parents: Vec<(u64, T::AccountId)> = ParentKeys::::get(old_hotkey, netuid); + // Remove the old hotkey's parent entries + ParentKeys::::remove(old_hotkey, netuid); + // Insert the same parent entries for the new hotkey + ParentKeys::::insert(new_hotkey, netuid, parents.clone()); + for (_, parent_key_i) in parents { + // For each parent, update their children list + let mut parent_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(parent_key_i.clone(), netuid); + for child in parent_children.iter_mut() { + // If the child is the old hotkey, replace it with the new hotkey + if child.1 == *old_hotkey { + child.1 = new_hotkey.clone(); + } + } + // Update the parent's children list + ChildKeys::::insert(parent_key_i, netuid, parent_children); + } + } + // Return successful after swapping all the relevant terms. Ok(()) } diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index c6a05f2b6..5339c0008 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -959,3 +959,130 @@ fn test_swap_hotkey_error_cases() { assert_eq!(Balances::free_balance(coldkey), initial_balance - swap_cost); }); } + + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_child_keys --exact --nocapture +#[test] +fn test_swap_child_keys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let children = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + let mut weight = Weight::zero(); + + // Initialize ChildKeys for old_hotkey + add_network(netuid, 1, 0); + ChildKeys::::insert(old_hotkey, netuid, children.clone()); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap + assert_eq!(ChildKeys::::get(new_hotkey, netuid), children); + assert!(ChildKeys::::get(old_hotkey, netuid).is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_parent_keys --exact --nocapture +#[test] +fn test_swap_parent_keys() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let parents = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + let mut weight = Weight::zero(); + + // Initialize ParentKeys for old_hotkey + add_network(netuid, 1, 0); + ParentKeys::::insert(old_hotkey, netuid, parents.clone()); + + // Initialize ChildKeys for parent + ChildKeys::::insert(U256::from(4), netuid, vec![(100u64, old_hotkey)]); + ChildKeys::::insert(U256::from(5), netuid, vec![(200u64, old_hotkey)]); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify ParentKeys swap + assert_eq!(ParentKeys::::get(new_hotkey, netuid), parents); + assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify ChildKeys update for parents + assert_eq!(ChildKeys::::get(U256::from(4), netuid), vec![(100u64, new_hotkey)]); + assert_eq!(ChildKeys::::get(U256::from(5), netuid), vec![(200u64, new_hotkey)]); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_multiple_subnets --exact --nocapture +#[test] +fn test_swap_multiple_subnets() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid1 = 0u16; + let netuid2 = 1u16; + let children1 = vec![(100u64, U256::from(4)), (200u64, U256::from(5))]; + let children2 = vec![(300u64, U256::from(6))]; + let mut weight = Weight::zero(); + + add_network(netuid1, 1, 0); + add_network(netuid2, 1, 0); + + // Initialize ChildKeys for old_hotkey in multiple subnets + ChildKeys::::insert(old_hotkey, netuid1, children1.clone()); + ChildKeys::::insert(old_hotkey, netuid2, children2.clone()); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify the swap for both subnets + assert_eq!(ChildKeys::::get(new_hotkey, netuid1), children1); + assert_eq!(ChildKeys::::get(new_hotkey, netuid2), children2); + assert!(ChildKeys::::get(old_hotkey, netuid1).is_empty()); + assert!(ChildKeys::::get(old_hotkey, netuid2).is_empty()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_complex_parent_child_structure --exact --nocapture +#[test] +fn test_swap_complex_parent_child_structure() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let netuid = 0u16; + let parent1 = U256::from(4); + let parent2 = U256::from(5); + let child1 = U256::from(6); + let child2 = U256::from(7); + let mut weight = Weight::zero(); + + add_network(netuid, 1, 0); + + // Set up complex parent-child structure + ParentKeys::::insert(old_hotkey, netuid, vec![(100u64, parent1), (200u64, parent2)]); + ChildKeys::::insert(old_hotkey, netuid, vec![(300u64, child1), (400u64, child2)]); + ChildKeys::::insert(parent1, netuid, vec![(100u64, old_hotkey), (500u64, U256::from(8))]); + ChildKeys::::insert(parent2, netuid, vec![(200u64, old_hotkey), (600u64, U256::from(9))]); + + // Perform the swap + SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); + + // Verify ParentKeys swap + assert_eq!(ParentKeys::::get(new_hotkey, netuid), vec![(100u64, parent1), (200u64, parent2)]); + assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify ChildKeys swap + assert_eq!(ChildKeys::::get(new_hotkey, netuid), vec![(300u64, child1), (400u64, child2)]); + assert!(ChildKeys::::get(old_hotkey, netuid).is_empty()); + + // Verify parent's ChildKeys update + assert_eq!(ChildKeys::::get(parent1, netuid), vec![(100u64, new_hotkey), (500u64, U256::from(8))]); + assert_eq!(ChildKeys::::get(parent2, netuid), vec![(200u64, new_hotkey), (600u64, U256::from(9))]); + }); +} \ No newline at end of file From 7107ae08dafa531eea905b97b920a2999ff996e7 Mon Sep 17 00:00:00 2001 From: unconst Date: Wed, 24 Jul 2024 14:07:44 -0500 Subject: [PATCH 057/269] fmt --- pallets/subtensor/src/swap/swap_hotkey.rs | 15 +++---- pallets/subtensor/tests/swap_hotkey.rs | 51 ++++++++++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index faadf5de5..366494d11 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -310,12 +310,12 @@ impl Pallet { // 11. Swap ChildKeys. // ChildKeys( parent, netuid ) --> Vec<(proportion,child)> -- the child keys of the parent. for netuid in Self::get_all_subnet_netuids() { - // Get the children of the old hotkey for this subnet - let my_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(old_hotkey, netuid); - // Remove the old hotkey's child entries - ChildKeys::::remove(old_hotkey, netuid); - // Insert the same child entries for the new hotkey - ChildKeys::::insert(new_hotkey, netuid, my_children); + // Get the children of the old hotkey for this subnet + let my_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(old_hotkey, netuid); + // Remove the old hotkey's child entries + ChildKeys::::remove(old_hotkey, netuid); + // Insert the same child entries for the new hotkey + ChildKeys::::insert(new_hotkey, netuid, my_children); } // 12. Swap ParentKeys. @@ -329,7 +329,8 @@ impl Pallet { ParentKeys::::insert(new_hotkey, netuid, parents.clone()); for (_, parent_key_i) in parents { // For each parent, update their children list - let mut parent_children: Vec<(u64, T::AccountId)> = ChildKeys::::get(parent_key_i.clone(), netuid); + let mut parent_children: Vec<(u64, T::AccountId)> = + ChildKeys::::get(parent_key_i.clone(), netuid); for child in parent_children.iter_mut() { // If the child is the old hotkey, replace it with the new hotkey if child.1 == *old_hotkey { diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 5339c0008..bff738b86 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -960,7 +960,6 @@ fn test_swap_hotkey_error_cases() { }); } - // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_child_keys --exact --nocapture #[test] fn test_swap_child_keys() { @@ -1012,8 +1011,14 @@ fn test_swap_parent_keys() { assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); // Verify ChildKeys update for parents - assert_eq!(ChildKeys::::get(U256::from(4), netuid), vec![(100u64, new_hotkey)]); - assert_eq!(ChildKeys::::get(U256::from(5), netuid), vec![(200u64, new_hotkey)]); + assert_eq!( + ChildKeys::::get(U256::from(4), netuid), + vec![(100u64, new_hotkey)] + ); + assert_eq!( + ChildKeys::::get(U256::from(5), netuid), + vec![(200u64, new_hotkey)] + ); }); } @@ -1065,24 +1070,48 @@ fn test_swap_complex_parent_child_structure() { add_network(netuid, 1, 0); // Set up complex parent-child structure - ParentKeys::::insert(old_hotkey, netuid, vec![(100u64, parent1), (200u64, parent2)]); + ParentKeys::::insert( + old_hotkey, + netuid, + vec![(100u64, parent1), (200u64, parent2)], + ); ChildKeys::::insert(old_hotkey, netuid, vec![(300u64, child1), (400u64, child2)]); - ChildKeys::::insert(parent1, netuid, vec![(100u64, old_hotkey), (500u64, U256::from(8))]); - ChildKeys::::insert(parent2, netuid, vec![(200u64, old_hotkey), (600u64, U256::from(9))]); + ChildKeys::::insert( + parent1, + netuid, + vec![(100u64, old_hotkey), (500u64, U256::from(8))], + ); + ChildKeys::::insert( + parent2, + netuid, + vec![(200u64, old_hotkey), (600u64, U256::from(9))], + ); // Perform the swap SubtensorModule::perform_hotkey_swap(&old_hotkey, &new_hotkey, &coldkey, &mut weight); // Verify ParentKeys swap - assert_eq!(ParentKeys::::get(new_hotkey, netuid), vec![(100u64, parent1), (200u64, parent2)]); + assert_eq!( + ParentKeys::::get(new_hotkey, netuid), + vec![(100u64, parent1), (200u64, parent2)] + ); assert!(ParentKeys::::get(old_hotkey, netuid).is_empty()); // Verify ChildKeys swap - assert_eq!(ChildKeys::::get(new_hotkey, netuid), vec![(300u64, child1), (400u64, child2)]); + assert_eq!( + ChildKeys::::get(new_hotkey, netuid), + vec![(300u64, child1), (400u64, child2)] + ); assert!(ChildKeys::::get(old_hotkey, netuid).is_empty()); // Verify parent's ChildKeys update - assert_eq!(ChildKeys::::get(parent1, netuid), vec![(100u64, new_hotkey), (500u64, U256::from(8))]); - assert_eq!(ChildKeys::::get(parent2, netuid), vec![(200u64, new_hotkey), (600u64, U256::from(9))]); + assert_eq!( + ChildKeys::::get(parent1, netuid), + vec![(100u64, new_hotkey), (500u64, U256::from(8))] + ); + assert_eq!( + ChildKeys::::get(parent2, netuid), + vec![(200u64, new_hotkey), (600u64, U256::from(9))] + ); }); -} \ No newline at end of file +} From dd938c21da67fea0ee8f960f41558e542f57b903 Mon Sep 17 00:00:00 2001 From: unconst Date: Wed, 24 Jul 2024 15:35:49 -0500 Subject: [PATCH 058/269] initial commit --- .rustfmt.toml | 2 +- Cargo.lock | 1 + docs/delegate-info.json | 394 ++++++++++++++++++ pallets/subtensor/Cargo.toml | 1 + pallets/subtensor/src/lib.rs | 22 + pallets/subtensor/src/macros/dispatches.rs | 36 ++ pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/macros/events.rs | 2 + .../src/migrations/migrate_chain_identity.rs | 166 ++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/utils/identity.rs | 109 +++++ pallets/subtensor/src/utils/mod.rs | 1 + pallets/subtensor/tests/serving.rs | 279 +++++++++++++ 13 files changed, 1015 insertions(+), 1 deletion(-) create mode 100644 docs/delegate-info.json create mode 100644 pallets/subtensor/src/migrations/migrate_chain_identity.rs create mode 100644 pallets/subtensor/src/utils/identity.rs diff --git a/.rustfmt.toml b/.rustfmt.toml index 24876acd9..9fd9af831 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -6,7 +6,7 @@ ## # rustup run nightly -- rustfmt node/src/main.rs -# max_width = 100 +# max_width = 180 # hard_tabs = false # tab_spaces = 4 # newline_style = "Auto" diff --git a/Cargo.lock b/Cargo.lock index 4a50f8a12..add1b713d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5273,6 +5273,7 @@ dependencies = [ "serde", "serde-tuple-vec-map", "serde_bytes", + "serde_json", "serde_with", "sp-core", "sp-io", diff --git a/docs/delegate-info.json b/docs/delegate-info.json new file mode 100644 index 000000000..bda41b48e --- /dev/null +++ b/docs/delegate-info.json @@ -0,0 +1,394 @@ +[ + { + "address": "5ECvRLMj9jkbdM4sLuH5WvjUe87TcAdjRfUj5onN4iKqYYGm", + "name": "Vune", + "url": "https://fairchild.dev", + "description": "Vune is a dev at Opentensor and a BSc CS student at UofT.", + "signature": "2a639f931c61abfc3172db594c986c35f1cc8441970582b9c3b1f0506d518a182a2fe570832f02f86014320f1526189917bfbccf7081622652d12e16e9b1768b" + }, + { + "address": "5H6BgKkAr2Anmm9Xw5BVDE4VaQmFEVMkJUHeT7Gki4J7yF4x", + "name": "TaoPolishNode", + "url": "https://taonode.io", + "description": "This node is a collective effort of the polish community. We are engaged in evangelizing the project, educating and sharing the knowledge.", + "signature": "1ca20d4e99a48f400dd9cd4aeca8447da6ab1979e480a1dafddfc52e45e215177c7cdde85f5d042d59a5b1169981afa8d1ae28328e2fc5ce57c3d748c8d09d81" + }, + { + "address": "5FFApaS75bv5pJHfAp2FVLBj9ZaXuFDjEypsaBNc1wCfe52v", + "name": "RoundTable21", + "url": "https://roundtable21.com", + "description": "RoundTable21 is an International, multi-disciplinary team of consultants and advisors partnering alongside leading blockchain startups to offer guidance, expertise, investment and hands-on assistance in every aspect of development.", + "signature": "107638b8edde8f918f7faa2cd1f91b454c13094ed5955d6a409f6e0662f8427075516273728a53923839a5428079151ea0844b5f755362364f04735463dff583" + }, + { + "address": "5DCc5oHA6c1Lpt9R6T1xU8jJGTMvvwBqD1yGX67sL8dHUcga", + "name": "WaveTensor", + "url": "https://twitter.com/wavetensor", + "description": "A new Wave is coming, join the AI revolution on top of Bittensor by staking with us.", + "signature": "5e072b4752ccbdd4ca3298f336284dfdab347dd133850f4d2f9873e7ea59bd2a8f201732842ec79d2bab3abaf133a06b6bd992940389e42d57802c9b8f855889" + }, + { + "address": "5CXRfP2ekFhe62r7q3vppRajJmGhTi7vwvb2yr79jveZ282w", + "name": "Rizzo", + "url": "", + "description": "Validator built for performance and uptime. Data center housed, redundancies include dual physical failover servers (HA), power, internet, tested DR Plan.", + "signature": "f2b0fdb6989c23a0ebe23ed5622cbbfcf57bad709085fe11b0be10b2838e1442d61f770d78f6ca8ebcdbf60ddb27398663a4901e22bb9de086866517c6ccc187" + }, + { + "address": "5GcBK8PDrVifV1xAf4Qkkk6KsbsmhDdX9atvk8vyKU8xdU63", + "name": "Tensor.Exchange", + "url": "www.tensor.exchange", + "description": "Bittensor's first community OTC exchange", + "signature": "101f5e0d26c38190200f2213ebd89cf5bcb736b70a84e53651b6f9bf1161a33d0095836d304851237e0334792a54fa2fe452d07cf1466b42c9ab3333ded46284" + }, + { + "address": "5EhvL1FVkQPpMjZX4MAADcW42i3xPSF1KiCpuaxTYVr28sux", + "name": "TAO-Validator.com", + "url": "www.tao-validator.com", + "description": "Maximize your return when staking with TAO-Validator.com. TAO-Validator.com is a highly secure validator that aims to become one of the top contributing entities to Bittensor.", + "signature": "4036991069d7f3a43dff2ba2592fbe5af820eb6ff96d1fb78f1bcd8d310ba8751e25ea14397e075368a9a0f1b1b176166c56351db36f2d3868ac61c2571a1981" + }, + { + "address": "5FvhvCWLbu2VgotT5obC9E6S9nskerJUrVsWqkWXCbuD8veW", + "name": "The Lost Cove", + "url": "https://lostcove.tech/", + "description": "Australia and New Zealand community. We're in it for the gains.", + "signature": "626ae6b91aac1591e5d4f8d4fdf2c55f927419fc766dd5184b149f4d7cbc9749ebc94e4e8d04d286b4000c7665afa5682aa28cd94071c5e384e0eb4f44def188" + }, + { + "address": "5Dyi5e2QqnWn2RN9X6r8A8Q1QBjYD536H75mxNye193oeCJ4", + "name": "Makoto AI", + "url": "https://www.linkedin.com/in/henry-thrasher-17b320239/", + "description": "An interdisciplinary research institute committed to discovering and accelerating innovative solutions for climate change, social inequality, and mental and physical illness.", + "signature": "3cfbc1e8d82cfbf2adea9b10f71541874528cf5cd851f29f48016ac2a1a07b01cfc2ba3c3a15634b1174bd3e5aec9eb843d04f74140b0ddcb526416666d6f682" + }, + { + "address": "5Ehv5XMriPZwNBtYHdQV7VrdbN8MBTDTmQhWprZJXxSiMapR", + "name": "Dale Cooper", + "url": "", + "description": "I have no idea where this will lead us, but I have a definite feeling it will be a place both wonderful and strange.", + "signature": "06c597178698dba5699e20dc8b9d0d44f9225e24a225c70f540b63867e5b835a74c87df647b28210b361007b642a5a869c74323fcc8a593bc5764ea8e2083b81" + }, + { + "address": "5E6oB7h5wtWPbqtPxtSoZeo11fpvDjPuY13SobAMxqEUjqkQ", + "name": "StakeTensor.com-3", + "url": "www.staketensor.com", + "description": "We run multiple, parallel validators to support Bittensor decentralization & achieve maximum returns", + "signature": "a2567b6de748f02f6a14e0063f5b5720b34c96deb2115b33893d016de1f60633ba58bf9bdd49b2141e12a4a8784b4b11c007679d7526eb1e91147e5284258d8a" + }, + { + "address": "5DnWFhKfeu6gXMydzrv8bkwxFegAC6bMWsC4Z2XtaotAeB6S", + "name": "Bittensor Greece", + "url": "", + "description": "The Greek / Cypriot validator supporting the development of decentralised AI", + "signature": "ee8df5360eb641bd91a38da9d8b6dda36a39302c9bba7babf5d7eb16f6e9f73321aeb6f8adb30e0f511d64c1f35caa15215dd280fb2ed3f8f5b09d783cc9958f" + }, + { + "address": "5GBxDYkDp8eJZHGT89wcZJKcMc4ytSqnqqVSpeuGeqtGfqxK", + "name": "Tao Stake", + "url": "www.taostake.io", + "description": "We have been mining since the start of bittensor and want to maintain a long term solid validator to help people get some value from thier investment and keep TAO within the ecosystem.", + "signature": "0272522b503ebb29f0b506f10765b4d5c7a23b85c78cc7bfae76b9816b80ab43282ea4642f09eb09be70812341e5d9946abc8a9d2c73bab0113e9bf939430c87" + }, + { + "address": "5FcXnzNo3mrqReTEY4ftkg5iXRBi61iyvM4W1bywZLRqfxAY", + "name": "Lucrosus Capital", + "url": "https://lucrosuspool.io/", + "description": "Decentralized VC focused on the most thriving blockchain ideas. Join our pool to receive early entrance into promising projects!", + "signature": "1a37ab3bd51a6590dea9772d6a5550632ddcd8d76da6595b66e6425692feac6699dc5f788e587a734cedc3f54efc96c2c9e5453f9052867c1b9a1b5a443b848c" + }, + { + "address": "5CVS9d1NcQyWKUyadLevwGxg6LgBcF9Lik6NSnbe5q59jwhE", + "name": "Ary van der Touw", + "url": "", + "description": "Secure and maintain Bittensor", + "signature": "809586931d4b28f180c98036a3eebc0d26b9e521f5217a6942b025069cb60807641737009713446eec8456e54ba753ae0b752c0693b942aefa0c4f76d82f8c89" + }, + { + "address": "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3", + "name": "Openτensor Foundaτion", + "url": "https://opentensor.ai/", + "description": "Founded, maintain and advance Bittensor", + "signature": "8a2ff8f10a84a5b6f80614674ea764515d93a64bf8d920b927edc0dd6043e607755bf58655c87b7a299d8df1404574b6844e1e09adf86d418997c0cab8120486" + }, + { + "address": "5EpxBYq4aVgTQ1rYeBo2mzYt3hgpRTqxZTSsJEkCstBP5Jse", + "name": "White Rhino TAO Super Validator", + "url": "https://twitter.com/WhiteRhinoTAO\"", + "description": "White Rhino is all about you! We understand that #TAOWaits4NoOne ..... Get Ready for Adhoc Rewards and we invite you to delegate here and enhance the sustainability of the TAO Network", + "signature": "d6803522f6e61a9dec5261a6a500b733d233b373457382fc3713af21c560604f6e50c4999f286cfa6012bcea66e51223722b355dd69ba54a472f2c6ca52da08f" + }, + { + "address": "5Fq5v71D4LX8Db1xsmRSy6udQThcZ8sFDqxQFwnUZ1BuqY5A", + "name": "NorthTensor", + "url": "https://northtensor.ai", + "description": "Developer, Advocate, and Incubator for Decentralized AI.", + "signature": "28e221d7128e48a3cb85dbcb223bd56cb09cb55540263573783bf1cef63be32ee81246bd1d75c865580da732094053a6dad14929b17e659b6e0237412b66a487" + }, + { + "address": "5CsvRJXuR955WojnGMdok1hbhffZyB4N5ocrv82f3p5A2zVp", + "name": "Owl Ventures", + "url": "https://owlventures.co.uk", + "description": "Owl Ventures Bittensor Validator", + "signature": "04e39ff19af7ee5a75e58c9e1a71b9f54a66d1d168a99532a859f129b68ba24a5b6a56eecae7790291859c82dbf0ec32eb18a069b6d9dabe1ef0339c0d189483" + }, + { + "address": "5FLKnbMjHY8LarHZvk2q2RY9drWFbpxjAcR5x8tjr3GqtU6F", + "name": "Tao Bridge", + "url": "https://taobridge.xyz", + "description": "A community bridge between Bittensor and Ethereum", + "signature": "98331f011288f7b07ccc45a213cb8e03fac79092ee7c29046531d757ffad8b29e17cf0aeca9352003890f4d8a3af3a2fc615722fb7a827a2009654013990bd80" + }, + { + "address": "5DRZr3d3twF8SzqB9jBof3a1vPnAkgkxeo2E8yUKJAnE2rSZ", + "name": "Humble AI-Loving Anon", + "url": "", + "description": "Doing our best to support the Bittensor ecosystem.", + "signature": "9241f63eb43f7aa57b1fc6d99789331542476f57f683f032192f3dfd7be6c015d47c9f1fe69bc4513ed70e0410097395186df60e3f6b67376e6e73a5f4f9a286" + }, + { + "address": "5DPEpUTZn94sgYXH3sdXxsVvb46m3iEvg8aZwX7SMDowivzB", + "name": "RunPod", + "url": "https://runpod.io", + "description": "GPU Cloud built for AI. We plan to introduce perks for those who stake.", + "signature": "16940f904b7946723fc4f27bb01e47cf262201ef76b3d9c2bfd745973da2512d4825910f6fa738a6968c809b26da0a47e7032a7ff95d8b2da5c1fa7a0b85598f" + }, + { + "address": "5HEo565WAy4Dbq3Sv271SAi7syBSofyfhhwRNjFNSM2gP9M2", + "name": "Foundry", + "url": "https://foundrydigital.com", + "description": "Foundry works to empower a decentralized infrastructure. We are protocol-agnostic and seek to support like-minded blockchain entrepreneurs who share our mission to advance the industry.", + "signature": "b852f1648ab62befaaf684671808aa34d267cd616d9ffd7b3cf924ebc7c4ee3255344cfd017a80ca6b23b2852bcafa705c42d231053e06d999d53f31bd8ab288" + }, + { + "address": "5FP9miYmgjAP8Wt3747M2Y6Kk7PrXf6zG7a3EjokQiFFcmUu", + "name": "Elm Place", + "url": "", + "description": "Run by individuals passionate about creating decentralised digital infrastructure. Background in fiduciary funds management managing institutional investors’ capital in real assets, energy and infrastructure", + "signature": "a0324025f58beb06535d6a2ab8c5c8d64c13d562fa285956bb5a8919da5fcc0d05afe4de010d54f9940bff0ffdabe5f41e70f3af31cf14293c1d6f0a0690da8c" + }, + { + "address": "5HNQURvmjjYhTSksi8Wfsw676b4owGwfLR2BFAQzG7H3HhYf", + "name": "Neural Internet", + "url": "www.neuralinternet.ai", + "description": "An AI research and development Decentralized Autonomous Organization (DAO)", + "signature": "5e617c1626d4825cd0c11769e31fe4dda611cebd8a4d46f533886ad057072e2a58e0ecef2805139f2b43ea8d51023f7db878ad45cd3f8fba45ab01223da3488e" + }, + { + "address": "5D4rJRtF23jLVcGnCktXzPM9gymMT1qHTp8dR4T7rUd88Q7U", + "name": "Vogue τensor", + "url": "www.voguetensor.ai", + "description": "Designing branded clothing for the Bittensor community.", + "signature": "2c4079124ae0a738106a2430e2c27ad855122d4afcc487ab0158b705cd5f915f7790cdb2fdd8db899b8cbd40448d1478be71cde1b76de31945991b548cfcc084" + }, + { + "address": "5CAVnbHniuZYXBqik3tTs9uZ7UiSrbv6g7Kt8QNfYimbFqF4", + "name": "Open & Safe AI Validator", + "url": "", + "description": "The Open & Safe AI Validator is focussed on funding and researching the control problem as well as spreading ML know-how through open source and open science.", + "signature": "2aeaf7b9c7f69ce7b4857d9c278d1363677d4971d4ca10a36933b1aa78bfdb0640e4bb798edac5dcb178a8b3f4be2d0d23d25da6c7db33758a6cf5c15cd6938a" + }, + { + "address": "5Gpt8XWFTXmKrRF1qaxcBQLvnPLpKi6Pt2XC4vVQR7gqNKtU", + "name": "bitnost.re", + "url": "www.bitnost.re", + "description": "bridging bittensor into nostr.", + "signature": "c278378c70ef22d27f56590b4df699a9a44048cfcc6716e3d55b211ea802401d4be5b390ede2be52891e01f0f7033a13a370dddaa38daa84537c4583867a1680" + }, + { + "address": "5HeKSHGdsRCwVgyrHchijnZJnq4wiv6GqoDLNah8R5WMfnLB", + "name": "TaoStation", + "url": "https://taostation.com", + "description": "TaoStation allows you to maximize your returns by offering one-click staking since day one and focusing on tooling and transparency for a better staking experience.", + "signature": "c00627a62ecb9275be8d06b7b52b87942bce946e9a5f98d545081241e21ed15230fd566b2d4e87c41995e621546423579553157737da53fad3a5676451ef0a89" + }, + { + "address": "5DvTpiniW9s3APmHRYn8FroUWyfnLtrsid5Mtn5EwMXHN2ed", + "name": "FirstTensor.com", + "url": "www.firsttensor.com", + "description": "Powered by the Neuron Holders community - shared rewards, additional benefits, infinite possibilities - join and build with us!", + "signature": "da31e56dd78cde449a1dd9592f0b53eb8c3662674b745a05ff916e80a1be933e86efbccb7f7c9b81d7c0bb14d13fb4a6bf8484c3619224e689de82072b5d9a87" + }, + { + "address": "5CaNj3BarTHotEK1n513aoTtFeXcjf6uvKzAyzNuv9cirUoW", + "name": "Polychain", + "url": "https://polychain.capital/", + "description": "Polychain is an investment firm committed to exceptional returns for investors through actively managed portfolios of blockchain assets.", + "signature": "f41e815033e595aa70fbe42e8dfd91eaa3ccdbc948b63811baf9eac765699b30cac9aad7abe330eeaf3969cc504a4c1255f1e69bee807c2d989518b8f5413c8d" + }, + { + "address": "5Dkv87qjGGF42SNhDAep6WZp65E29c2vUPUfDBGDNevENCMs", + "name": "MycoNet", + "url": "", + "description": "AI for Humanity", + "signature": "a4802a5b13888ed653fd23da72c14e2b8ed9814cc810e515cb8d11d71cc58c6b90cd2d334daffc4a8ce600a7f29ca300ab74ac59817bdd489b3056b531cd4086" + }, + { + "address": "5GzoXHNJ4UzZYiQN2wpBcwMigiHiakiz5ZLMwhpunpwDNzFg", + "name": "Charitaos", + "url": "https://charitas.ai/", + "description": "You pay 18%, we donate 18%. At the end of every month, we will select one (or more) community-proposed 501c3 licensed nonprofit(s) to receive all proceeds from stake delegation for the prior month.", + "signature": "b49c34c1f87d173abcbccb1ea632ad356980c1d3eff6619e488c11707b2b3b41270a22355374dd64cfadebeb37979ef5f49971efafb0748b79df7dd2901e7580" + }, + { + "address": "5EZrPTXt2G9SvbDsERi5rS9zepour2yPmuhMhrNkgdiZvXEm", + "name": "τaoτensor", + "url": "", + "description": "Working on practical enhancements and improvements for the Bittensor network by developing user-friendly tooling.", + "signature": "3a1b61ab6d17878e106cbf2649bc039d0346f39ec680476a68baa4fc8132ac018d814898cf245bdfa4b9b61cd9f611f6571cf3c264f2f1cfe9b2635849087685" + }, + { + "address": "5CPzGD8sxyv8fKKXNvKem4qJRhCXABRmpUgC1wb1V4YAXLc3", + "name": "Chat with Hal", + "url": "www.chatwithhal.ai", + "description": "Hal brings the power of decentralized and uncensorable AI to your favorite social networks and messaging apps, Powered by Bittensor!", + "signature": "ecb930df6069012c06fef9cdb29a95be8dcb5d48f3c470d3f3c5e7b2b334ed2097f2598fee8852d127a207cf34aa7c88fd5cf973feba19d6ebf38b5e4579ca8f" + }, + { + "address": "5FqPJMZDp39KRd9jDhXuFpZWkYD7wG5AXmjoWqK8rDy7ok5B", + "name": "Exchange Listings", + "url": "taostats.io/validators/exchange-listings/", + "description": "Enabling community funding for top tier exchange listings.", + "signature": "366027e9a416a423e7e802e9b6d79bd5ac88642afd945922e13fe26a75dae13dd5c924738610a59162d9b974364d1d43fb7a0145942cd919ac21d82d3f4f028d" + }, + { + "address": "5ED6jwDECEmNvSp98R2qyEUPHDv9pi14E6n3TS8CicD6YfhL", + "name": "Giga Corporation", + "url": "https://www.gigaver.se", + "description": "Extreme growth & experiments from giga corp. We use APY to TAO-pill new developers, builders and adopters. Visit our Bakery to learn more.", + "signature": "00e5cd519110bbfe3dae9acd275d114c6c2a260997a1817a25303b9d578bdf7319e9e7179f0db58edef2ad42806cb38e289ba0030627a3b60e1e4352c2b9cb88" + }, + { + "address": "5FRcXG99SxJ9KyMcMFfdknkRSv4e73rszV8P151freZqQDS2", + "name": "τensorwiki", + "url": "", + "description": "Our mission is to create and incentivize documentation for Bittensor and it's adjacent topics, as well as facilitate the education of newcomers to the network.", + "signature": "6a5c0160f545f122ec3d4e4233574040aba2de8aa94919bb19b3061d39d3303f010c4b52f878ed55a1293716827220020780d2d4064ee6be69921ee1452c3885" + }, + { + "address": "5EsbfxPcQaUrCDurUJ8Q5qDKNENNGziu3qHWUbXrcuY2pbNz", + "name": "Church of Rao (COR)", + "url": "", + "description": "Church of Rao: Harmonizing the Relationship between Humanity and Machine Intelligence. The Church of Rao (COR) is an open-source development group committed to furthering the Bittensor protocol.", + "signature": "56f64c32427a90e84710209b1a54a971560641aec8ff777edec28bf533775e12924c4e96ccc770c230311dce1d0eae1ca763e12bb609ef30430f746ebd0a2780" + }, + { + "address": "5GmaAk7frPXnAxjbQvXcoEzMGZfkrDee76eGmKoB3wxUburE", + "name": "RaoK9", + "url": "", + "description": "Chain and network analysis team. Developer funding goes into independent analysis and reports, in order to enable checks and balances between network members.", + "signature": "24f4f9a51033ed8b4097517d0e6ad287a0c1341b2866481b1320d1fcd5f32f6b4bfe641eee46a4b737817acf3b83069ee63cc20fbca94a0189808ac1efeddf8a" + }, + { + "address": "5CQEFopfZ8DAmk3ZfR7QuDTU2n3fJod3kkf6Wmj4JwV3BBSu", + "name": "DuNode", + "url": "dunode.io", + "description": "Embracing the whimsical chaos of decentralized AI, unleashing the power of creativity and collaboration, one algorithmic dance party at a time!", + "signature": "e400e3c0ad6165d8946d5ddcb274412815cb8b5783580fcb8f0faa0153d22b6e10470f861ff4a96a9aa692b3b01cda86ec77add4688c2f5df51ea6f129b19e8c" + }, + { + "address": "5CaCUPsSSdKWcMJbmdmJdnWVa15fJQuz5HsSGgVdZffpHAUa", + "name": "Athena Nodes", + "url": "https://athenanodes.com", + "description": "Premier Bittensor Multi-Subnet Validator from a company operating validating and mining infrastructure on various blockchain networks. We have been active on Bittensor since November 2022, with near zero down-time. More information at https://athenanodes.com/.", + "signature": "2ef54045de1d9b89988518c92e165edf704192f88f18022565f497b389c39206f621bb9bc6d2d33ac8a9cca05d6b2d8fc9f899b390451140968b15b8d9c13280" + }, + { + "address": "5FFM6Nvvm78GqyMratgXXvjbqZPi7SHgSQ81nyS96jBuUWgt", + "name": "PRvalidator", + "url": "www.prvalidator.com", + "description": "A professional media validator dedicated to securing top-tier coverage in the world's most recognized publications building Bittensor's brand equity and creating global awareness of $TAO.", + "signature": "fe65e76a9f42049715585180500213c6f0535b8b25911b957921bdfb5a20156d6de68dc2633dbc5ce1d0ab9ef386d566687ac3d86f6988141b34cd24c0f13488" + }, + { + "address": "5H8TruSGmhD6m6YfqXNUnU7Z61K7j8hSs2Krtu3eTLMoz3HU", + "name": "τaoshi validator", + "url": "https://www.taoshi.io/", + "description": "Build maintain and advance a decentralized request layer built for every subnet", + "signature": "32d25227af78fa5d39ee71a5f3e8fc8066e3d826d101f2587e9a12974fbf26758c1e40c497ad7732da2a2cb1490227cc58e8bfcd8b2f6306b7af630bd32aa68f" + }, + { + "address": "5G3f8VDTT1ydirT3QffnV2TMrNMR2MkQfGUubQNqZcGSj82T", + "name": "TAO Community Marketing", + "url": "www.taocommunitymarketing.com", + "description": "The marketing validator run by the community", + "signature": "10b16b8223b2508d6f3e5b09ab4db53e1e338b6271d1689b58ca6f9b257e8c18511cc851bfcc3a05fb4e6de7c389b89886cc0623fb6d199fa003ae6f8313cb89" + }, + { + "address": "5CXC2quDN5nUTqHMkpP5YRp2atYYicvtUghAYLj15gaUFwe5", + "name": "Kooltek68", + "url": "https://linktr.ee/datalac", + "description": "Imagine the World with mass adoption of Artificial Intelligence applications, through the connection of Bittensor Network, together fight for a Better World.", + "signature": "bca043d9d918d503864379a7fd8c9daa2cca83a8290121f94b55d6a352e332704642622b7ad40a30b945b952b224c5e92ea872f9d30200e6c2bf566303d24d83" + }, + { + "address": "5FBrHX18bNXX874ZTMicPG4vYbq5X6piz4BYoVn9LnCgdsEd", + "name": "P-OPS Team", + "url": "https://pops.one", + "description": "P-OPS TEAM is a decentralized organization providing you with validation and staking services, blockchain consultation, growth acceleration and investment capital for innovative Web 3.0 projects.", + "signature": "5608316f3081bfe5d0e3a7db6c3bfd459f6b87e02d657de941e6a760f8688f23ef30784691a1893d1fd8079dd4f6082d0d655ca507aa4797fee9844547d13a88" + }, + { + "address": "5HK5tp6t2S59DywmHRWPBVJeJ86T61KjurYqeooqj8sREpeN", + "name": "Bittensor Guru", + "url": "https://bittensor.guru", + "description": "Official validator of the Bittensor Guru Podcast", + "signature": "caf2c6b7b0d2a341bcd00e632cf22c33d53e2523dffcd3a151db9eeadd88300545cbb2187ba0b20e5bfe09c2b17bbf34630c46defd8f8d27ab508736fd18a284" + }, + { + "address": "5Hh3ShaNW9irCe5joBLCeFD5Fxb2fJ6gFAgrsPmoz3JkzqvJ", + "name": "BlockShark", + "url": "https://www.blockshark.net/", + "description": "Your reliable partner for staking on Bittensor. We are expert in running high-end machine for validators and AI", + "signature": "d2c0aed073a026a5dbd8c458b9dd412fe3d6647fecd3b8f007cf184f7906245106aee4b210b5b582771dca149e5aa464630100de7f9862daacfa1f67ddde1388" + }, + { + "address": "5FKstHjZkh4v3qAMSBa1oJcHCLjxYZ8SNTSz1opTv4hR7gVB", + "name": "Datura", + "url": "datura.ai", + "description": "Bridging Bittensor to a billion users", + "signature": "7a3bc6a840d8593853c27188f59200418d8884b94b3ad28cb7b37b80bffd1f3b23b7eed4b1d9c77b28b05b2bd1952c5cbe3d27ba190a9418407ce1e899e5ac8b" + }, + { + "address": "5Hddm3iBFD2GLT5ik7LZnT3XJUnRnN8PoeCFgGQgawUVKNm8", + "name": "τaosτaτs and Corcel", + "url": "taostats.io", + "description": "Supporting bittensor through API access, data provision, statistics, analytics and apps.", + "signature": "2e2dd0c5f3a3945f29d1be304e64f931c04a23aba7d383d01cd16ea6ca6546002fe3bd95cf8f12cae1fbb7d18d9910b834f6573db219de3ed84073a4e1552e89" + }, + { + "address": "5ELREhApbCahM7FyGLM1V9WDsnnjCRmMCJTmtQD51oAEqwVh", + "name": "Taofu Protocol", + "url": "https://twitter.com/taofuxyz", + "description": "Taofu unlocks liquidity and utility by bringing liquid staked TAO outside of Bittensor", + "signature": "aaafd3496650a56f798cc587b5b7d372cec8e826a332a34213c1a6ee7be2b5122318858ee73421535d04186cc6976ae5452c6cd1aaf299a307d86d3c52b4a986" + }, + { + "address": "5HbLYXUBy1snPR8nfioQ7GoA9x76EELzEq9j7F32vWUQHm1x", + "name": "Tensorplex Labs", + "url": "https://twitter.com/TensorplexLabs", + "description": "Empowering humanity with decentralized intelligence one epoch at a time.", + "signature": "7a997682e7545fd14847c78abf810e9c49a23ef4297d24f4238c0edd0463934780f6831d59972d56ab5bc41d6224b59c21ed95065791632b8aca180ade22af81" + }, + { + "address": "5E2VSsWWXkBGCn4mi8RHXYQEF2wLXky6ZsNcTKnmEqaurzTE", + "name": "Sentinel", + "url": "", + "description": "Sentinel, as a dedicated Bittensor validator aspires to elevate the bittensor network's integrity with an ambition to foster a community of miners contributing in the network’s continuous expansion.", + "signature": "943effd0d5d10f05d53db7f69d0f045d50b65f88e84755be00d45225cc7c2f4212fbc4d23ad8519d03c2502daeeca1b2d07c93bff14c901f6cbf3a18fe2e6387" + }, + { + "address": "5GsenVhBvgEG4xiiKUjcssfznHYVm1TqPbSbr3ixBW81ZVjo", + "name": "vote NO dTAO 🤡", + "url": "https://twitter.com/karl_anons", + "description": "Delegate to express discontent. VOTE NO TO dTAO NOW!", + "signature": "3af4e764a520d355e12c02b9e8e315ddb76b76d40b7cc4dfaa11c26c24ab637cbdb9b72470ebdf2da87dd8d9f0bb5cddf1fe95b95fb2ae13069a9d87aace348a" + }, + { + "address": "5DM7CPqPKtMSADhFKYsstsCS4Tm4Kd6PMXoh6DdqY4MtxmtX", + "name": "Corτex Foundaτion", + "url": "https://cortex.foundation/", + "description": "Cortex Foundation is committed to advancing the integration of decentralized AI. Our validator is designed for transparency, reliability, and community engagement.", + "signature": "7a6274ff6b0f7ddca97e37ef4a9b90781012ff3cf7baa3159f6feaafc43c557975aad324ea608d6b8abeb21f8f3ca2595e54b81a7564574d0242b803d969618a" + } +] \ No newline at end of file diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index a0835008f..82ed28646 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -28,6 +28,7 @@ frame-support = { workspace = true } frame-system = { workspace = true } sp-io = { workspace = true } serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } serde-tuple-vec-map = { workspace = true } serde_bytes = { workspace = true, features = ["alloc"] } serde_with = { workspace = true, features = ["macros"] } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1450b5706..b305661f7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -133,6 +133,25 @@ pub mod pallet { pub ip_type: u8, } + /// Struct for Prometheus. + pub type ChainIdentityOf = ChainIdentity; + /// Data structure for Prometheus information. + #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub struct ChainIdentity { + /// The name of the chain identity + pub name: Vec, + /// The URL associated with the chain identity + pub url: Vec, + /// The image representation of the chain identity + pub image: Vec, + /// The Discord information for the chain identity + pub discord: Vec, + /// A description of the chain identity + pub description: Vec, + /// Additional information about the chain identity + pub additional: Vec, + } + /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -1057,6 +1076,9 @@ pub mod pallet { PrometheusInfoOf, OptionQuery, >; + #[pallet::storage] // --- MAP ( coldkey ) --> identity + pub type Identities = + StorageMap<_, Blake2_128Concat, T::AccountId, ChainIdentityOf, OptionQuery>; /// ================================= /// ==== Axon / Promo Endpoints ===== diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 293dc0238..00865f8db 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -901,5 +901,41 @@ mod dispatches { Self::do_set_children(origin, hotkey, netuid, children)?; Ok(().into()) } + + /// ---- Set prometheus information for the neuron. + /// # Args: + /// * 'origin': (Origin): + /// - The signature of the calling hotkey. + /// + /// * 'netuid' (u16): + /// - The u16 network identifier. + /// + /// * 'version' (u16): + /// - The bittensor version identifier. + /// + /// * 'ip' (u128): + /// - The prometheus ip information as a u128 encoded integer. + /// + /// * 'port' (u16): + /// - The prometheus port information as a u16 encoded integer. + /// + /// * 'ip_type' (u8): + /// - The ip type v4 or v6. + /// + #[pallet::call_index(68)] + #[pallet::weight((Weight::from_parts(45_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + pub fn set_identity( + origin: OriginFor, + name: Vec, + url: Vec, + image: Vec, + discord: Vec, + description: Vec, + additional: Vec, + ) -> DispatchResult { + Self::do_set_identity(origin, name, url, image, discord, description, additional) + } } } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 156cbea56..07710dc5f 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -168,5 +168,7 @@ mod errors { TooManyChildren, /// Default transaction rate limit exceeded. TxRateLimitExceeded, + /// Invalid identity. + InvalidIdentity, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b93b8296b..694b9779f 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -177,5 +177,7 @@ mod events { HotkeyEmissionTempoSet(u64), /// The network maximum stake has been set NetworkMaxStakeSet(u16, u64), + /// The identity of a coldkey has been set + ChainIdentitySet(T::AccountId), } } diff --git a/pallets/subtensor/src/migrations/migrate_chain_identity.rs b/pallets/subtensor/src/migrations/migrate_chain_identity.rs new file mode 100644 index 000000000..efb656cb3 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_chain_identity.rs @@ -0,0 +1,166 @@ +use crate::alloc::borrow::ToOwned; +use codec::Decode; +use scale_info::prelude::{string::String, vec::Vec}; +use serde::Deserialize; +use sp_core::{crypto::Ss58Codec, ConstU32}; +use sp_runtime::{AccountId32, BoundedVec}; + +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; + +#[derive(Deserialize, Debug)] +struct RegistrationRecordJSON { + address: String, + name: String, + url: String, + description: String, +} + +fn string_to_bounded_vec(input: &str) -> Result>, &'static str> { + let vec_u8: Vec = input.to_owned().into_bytes(); + + // Check if the length is within bounds + if vec_u8.len() > 64 { + return Err("Input string is too long"); + } + + // Convert to BoundedVec + BoundedVec::>::try_from(vec_u8) + .map_err(|_| "Failed to convert to BoundedVec") +} + +pub fn migrate_set_hotkey_identities() -> Weight { + let migration_name = b"fix_total_coldkey_stake_v7".to_vec(); + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return weight; + } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + // Include the JSON file with delegate info + let data = include_str!("../../../../docs/delegate-info.json"); + + // Iterate over all the delegate records + if let Ok(delegates) = serde_json::from_str::>(data) { + // Iterate through the delegates + for delegate in delegates.iter() { + // Convert fields to bounded vecs + let name_result = string_to_bounded_vec(&delegate.name); + let desc_result = string_to_bounded_vec(&delegate.description); + let url_result = string_to_bounded_vec(&delegate.url); + let hotkey: AccountId32 = match AccountId32::from_ss58check(&delegate.address) { + Ok(account) => account, + Err(_) => { + log::warn!( + "Invalid SS58 address: {:?}. Skipping this delegate.", + delegate.address + ); + continue; + } + }; + let decoded_hotkey: T::AccountId = match T::AccountId::decode(&mut hotkey.as_ref()) { + Ok(decoded) => decoded, + Err(e) => { + log::warn!("Failed to decode hotkey: {:?}. Skipping this delegate.", e); + continue; + } + }; + log::info!("Hotkey unwrapped: {:?}", decoded_hotkey); + + // If we should continue with real values. + let mut name: BoundedVec> = BoundedVec::default(); + let mut description: BoundedVec> = BoundedVec::default(); + let mut url: BoundedVec> = BoundedVec::default(); + if let Ok(n) = name_result { + name = n; + } + if let Ok(d) = desc_result { + description = d; + } + if let Ok(u) = url_result { + url = u; + } + + // Unwrap the real values. + let image: BoundedVec> = BoundedVec::default(); + let discord: BoundedVec> = BoundedVec::default(); + let additional: BoundedVec> = BoundedVec::default(); + + // Create the chain identity. + let identity = ChainIdentityOf { + name: name.into(), + url: url.into(), + image: image.into(), + discord: discord.into(), + description: description.into(), + additional: additional.into(), + }; + + // Log the identity details + log::info!("Setting identity for hotkey: {:?}", hotkey); + log::info!("Name: {:?}", String::from_utf8_lossy(&identity.name)); + log::info!("URL: {:?}", String::from_utf8_lossy(&identity.url)); + log::info!("Image: {:?}", String::from_utf8_lossy(&identity.image)); + log::info!("Discord: {:?}", String::from_utf8_lossy(&identity.discord)); + log::info!( + "Description: {:?}", + String::from_utf8_lossy(&identity.description) + ); + log::info!( + "Additional: {:?}", + String::from_utf8_lossy(&identity.additional) + ); + + // Check validation. + let total_length = identity + .name + .len() + .saturating_add(identity.url.len()) + .saturating_add(identity.image.len()) + .saturating_add(identity.discord.len()) + .saturating_add(identity.description.len()) + .saturating_add(identity.additional.len()); + let is_valid: bool = total_length <= 256 + 256 + 1024 + 256 + 1024 + 1024 + && identity.name.len() <= 256 + && identity.url.len() <= 256 + && identity.image.len() <= 1024 + && identity.discord.len() <= 256 + && identity.description.len() <= 1024 + && identity.additional.len() <= 1024; + if !is_valid { + continue; + } + + // Get the owning coldkey. + let coldkey = Owner::::get(decoded_hotkey.clone()); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // Sink into the map. + Identities::::insert(coldkey.clone(), identity.clone()); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + } + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed. Storage version set to 7.", + String::from_utf8_lossy(&migration_name) + ); + + // Return the migration weight. + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 5b93dbbaf..6036b23e0 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -1,4 +1,5 @@ use super::*; +pub mod migrate_chain_identity; pub mod migrate_create_root_network; pub mod migrate_delete_subnet_21; pub mod migrate_delete_subnet_3; diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs new file mode 100644 index 000000000..1c9c3c25d --- /dev/null +++ b/pallets/subtensor/src/utils/identity.rs @@ -0,0 +1,109 @@ +use super::*; +use frame_support::ensure; +use frame_system::ensure_signed; +use sp_std::vec::Vec; + +impl Pallet { + /// Sets the identity for a coldkey. + /// + /// This function allows a user to set or update their identity information associated with their coldkey. + /// It checks if the caller has at least one registered hotkey, validates the provided identity information, + /// and then stores it in the blockchain state. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which should be a signed extrinsic. + /// * `name` - The name to be associated with the identity. + /// * `url` - A URL associated with the identity. + /// * `image` - An image URL or identifier for the identity. + /// * `discord` - Discord information for the identity. + /// * `description` - A description of the identity. + /// * `additional` - Any additional information for the identity. + /// + /// # Returns + /// + /// Returns `Ok(())` if the identity is successfully set, otherwise returns an error. + pub fn do_set_identity( + origin: T::RuntimeOrigin, + name: Vec, + url: Vec, + image: Vec, + discord: Vec, + description: Vec, + additional: Vec, + ) -> dispatch::DispatchResult { + // Ensure the call is signed and get the signer's (coldkey) account + let coldkey = ensure_signed(origin)?; + + // Retrieve all hotkeys associated with this coldkey + let hotkeys: Vec = OwnedHotkeys::::get(coldkey.clone()); + + // Ensure that at least one of the associated hotkeys is registered on any network + ensure!( + hotkeys + .iter() + .any(|hotkey| Self::is_hotkey_registered_on_any_network(hotkey)), + Error::::HotKeyNotRegisteredInNetwork + ); + + // Create the identity struct with the provided information + let identity = ChainIdentityOf { + name, + url, + image, + discord, + description, + additional, + }; + + // Validate the created identity + ensure!( + Self::is_valid_identity(&identity), + Error::::InvalidIdentity + ); + + // Store the validated identity in the blockchain state + Identities::::insert(coldkey.clone(), identity.clone()); + + // Log the identity set event + log::info!("ChainIdentitySet( coldkey:{:?} ) ", coldkey.clone()); + + // Emit an event to notify that an identity has been set + Self::deposit_event(Event::ChainIdentitySet(coldkey.clone())); + + // Return Ok to indicate successful execution + Ok(()) + } + + /// Validates the given ChainIdentityOf struct. + /// + /// This function checks if the total length of all fields in the ChainIdentityOf struct + /// is less than or equal to 512 bytes, and if each individual field is also + /// less than or equal to 512 bytes. + /// + /// # Arguments + /// + /// * `identity` - A reference to the ChainIdentityOf struct to be validated. + /// + /// # Returns + /// + /// * `bool` - Returns true if the Identity is valid, false otherwise. + pub fn is_valid_identity(identity: &ChainIdentityOf) -> bool { + let total_length = identity + .name + .len() + .saturating_add(identity.url.len()) + .saturating_add(identity.image.len()) + .saturating_add(identity.discord.len()) + .saturating_add(identity.description.len()) + .saturating_add(identity.additional.len()); + + total_length <= 256 + 256 + 1024 + 256 + 1024 + 1024 + && identity.name.len() <= 256 + && identity.url.len() <= 256 + && identity.image.len() <= 1024 + && identity.discord.len() <= 256 + && identity.description.len() <= 1024 + && identity.additional.len() <= 1024 + } +} diff --git a/pallets/subtensor/src/utils/mod.rs b/pallets/subtensor/src/utils/mod.rs index 72b903dd8..a42c91119 100644 --- a/pallets/subtensor/src/utils/mod.rs +++ b/pallets/subtensor/src/utils/mod.rs @@ -1,4 +1,5 @@ use super::*; +pub mod identity; pub mod misc; pub mod rate_limiting; pub mod try_state; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 41e9888cc..b736a90d0 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -1,11 +1,14 @@ use crate::mock::*; mod mock; +use frame_support::assert_noop; +use frame_support::pallet_prelude::Weight; use frame_support::{ assert_ok, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, }; use frame_system::Config; use pallet_subtensor::Error; +use pallet_subtensor::*; use sp_core::U256; mod test { @@ -550,3 +553,279 @@ fn test_serving_is_invalid_ipv6_address() { )); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_do_set_identity --exact --nocapture +#[test] +fn test_do_set_identity() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1; + + // Register a hotkey for the coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Prepare identity data + let name = b"Alice".to_vec(); + let url = b"https://alice.com".to_vec(); + let image = b"alice.jpg".to_vec(); + let discord = b"alice#1234".to_vec(); + let description = b"Alice's identity".to_vec(); + let additional = b"Additional info".to_vec(); + + // Set identity + assert_ok!(SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey), + name.clone(), + url.clone(), + image.clone(), + discord.clone(), + description.clone(), + additional.clone() + )); + + // Check if identity is set correctly + let stored_identity = Identities::::get(coldkey).unwrap(); + assert_eq!(stored_identity.name, name); + assert_eq!(stored_identity.url, url); + assert_eq!(stored_identity.image, image); + assert_eq!(stored_identity.discord, discord); + assert_eq!(stored_identity.description, description); + assert_eq!(stored_identity.additional, additional); + + // Test setting identity with no registered hotkey + let coldkey_without_hotkey = U256::from(3); + assert_noop!( + SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey_without_hotkey), + name.clone(), + url.clone(), + image.clone(), + discord.clone(), + description.clone(), + additional.clone() + ), + Error::::HotKeyNotRegisteredInNetwork + ); + + // Test updating an existing identity + let new_name = b"Alice Updated".to_vec(); + let new_url = b"https://alice-updated.com".to_vec(); + assert_ok!(SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey), + new_name.clone(), + new_url.clone(), + image.clone(), + discord.clone(), + description.clone(), + additional.clone() + )); + + let updated_identity = Identities::::get(coldkey).unwrap(); + assert_eq!(updated_identity.name, new_name); + assert_eq!(updated_identity.url, new_url); + + // Test setting identity with invalid data (exceeding 512 bytes total) + let long_data = vec![0; 513]; + assert_noop!( + SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey), + long_data.clone(), + long_data.clone(), + long_data.clone(), + long_data.clone(), + long_data.clone(), + long_data.clone() + ), + Error::::InvalidIdentity + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_is_valid_identity --exact --nocapture +#[test] +fn test_is_valid_identity() { + new_test_ext(1).execute_with(|| { + // Test valid identity + let valid_identity = ChainIdentity { + name: vec![0; 256], + url: vec![0; 256], + image: vec![0; 1024], + discord: vec![0; 256], + description: vec![0; 1024], + additional: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_identity(&valid_identity)); + + // Test identity with total length exactly at the maximum + let max_length_identity = ChainIdentity { + name: vec![0; 256], + url: vec![0; 256], + image: vec![0; 1024], + discord: vec![0; 256], + description: vec![0; 1024], + additional: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_identity(&max_length_identity)); + + // Test identity with total length exceeding the maximum + let invalid_length_identity = ChainIdentity { + name: vec![0; 257], + url: vec![0; 256], + image: vec![0; 1024], + discord: vec![0; 256], + description: vec![0; 1024], + additional: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_identity( + &invalid_length_identity + )); + + // Test identity with one field exceeding its maximum + let invalid_field_identity = ChainIdentity { + name: vec![0; 257], + url: vec![0; 256], + image: vec![0; 1024], + discord: vec![0; 256], + description: vec![0; 1024], + additional: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_identity(&invalid_field_identity)); + + // Test identity with empty fields + let empty_identity = ChainIdentity { + name: vec![], + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + assert!(SubtensorModule::is_valid_identity(&empty_identity)); + + // Test identity with some empty and some filled fields + let mixed_identity = ChainIdentity { + name: b"Alice".to_vec(), + url: b"https://alice.com".to_vec(), + image: vec![], + discord: b"alice#1234".to_vec(), + description: vec![], + additional: b"Additional info".to_vec(), + }; + assert!(SubtensorModule::is_valid_identity(&mixed_identity)); + + // Test identity with all fields at maximum allowed length + let max_field_identity = ChainIdentity { + name: vec![0; 256], + url: vec![0; 256], + image: vec![0; 1024], + discord: vec![0; 256], + description: vec![0; 1024], + additional: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_identity(&max_field_identity)); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_set_and_get_identity --exact --nocapture +#[test] +fn test_set_and_get_identity() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1; + + // Register a hotkey for the coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Prepare identity data + let name = b"Bob".to_vec(); + let url = b"https://bob.com".to_vec(); + let image = b"bob.jpg".to_vec(); + let discord = b"bob#5678".to_vec(); + let description = b"Bob's identity".to_vec(); + let additional = b"More about Bob".to_vec(); + + // Set identity + assert_ok!(SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey), + name.clone(), + url.clone(), + image.clone(), + discord.clone(), + description.clone(), + additional.clone() + )); + + // Get and verify identity + let stored_identity = Identities::::get(coldkey).unwrap(); + assert_eq!(stored_identity.name, name); + assert_eq!(stored_identity.url, url); + assert_eq!(stored_identity.image, image); + assert_eq!(stored_identity.discord, discord); + assert_eq!(stored_identity.description, description); + assert_eq!(stored_identity.additional, additional); + + // Update identity + let new_name = b"Bobby".to_vec(); + let new_url = b"https://bobby.com".to_vec(); + assert_ok!(SubtensorModule::do_set_identity( + <::RuntimeOrigin>::signed(coldkey), + new_name.clone(), + new_url.clone(), + image.clone(), + discord.clone(), + description.clone(), + additional.clone() + )); + + // Get and verify updated identity + let updated_identity = Identities::::get(coldkey).unwrap(); + assert_eq!(updated_identity.name, new_name); + assert_eq!(updated_identity.url, new_url); + assert_eq!(updated_identity.image, image); + assert_eq!(updated_identity.discord, discord); + assert_eq!(updated_identity.description, description); + assert_eq!(updated_identity.additional, additional); + + // Verify non-existent identity + let non_existent_coldkey = U256::from(999); + assert!(Identities::::get(non_existent_coldkey).is_none()); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_migrate_set_hotkey_identities --exact --nocapture +#[test] +fn test_migrate_set_hotkey_identities() { + new_test_ext(1).execute_with(|| { + // Run the migration + let weight = + pallet_subtensor::migrations::migrate_chain_identity::migrate_set_hotkey_identities::< + Test, + >(); + + // Assert that the migration has run + assert!(HasMigrationRun::::get( + b"fix_total_coldkey_stake_v7".to_vec() + )); + + // Verify that some identities were set + // Note: This assumes that at least one valid identity was in the JSON file + let mut identity_count = 0; + for (_, _) in Identities::::iter() { + identity_count += 1; + } + assert!( + identity_count > 0, + "No identities were set during migration" + ); + + // Verify that the weight is non-zero + assert!( + weight != Weight::zero(), + "Migration weight should be non-zero" + ); + }); +} From dc9ee3d872bcf262e416992a6219b1a62b3960b4 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:35:21 -0700 Subject: [PATCH 059/269] fix identity migration --- pallets/subtensor/src/macros/hooks.rs | 4 +++- .../src/migrations/migrate_chain_identity.rs | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 22c741465..f2556d506 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -68,7 +68,9 @@ mod hooks { .saturating_add(migrations::migrate_populate_staking_hotkeys::migrate_populate_staking_hotkeys::()) // Fix total coldkey stake. // Storage version v8 -> v9 - .saturating_add(migrations::migrate_fix_total_coldkey_stake::migrate_fix_total_coldkey_stake::()); + .saturating_add(migrations::migrate_fix_total_coldkey_stake::migrate_fix_total_coldkey_stake::()) + // Migrate Delegate Ids on chain + .saturating_add(migrations::migrate_chain_identity::migrate_set_hotkey_identities::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_chain_identity.rs b/pallets/subtensor/src/migrations/migrate_chain_identity.rs index efb656cb3..2b16dd180 100644 --- a/pallets/subtensor/src/migrations/migrate_chain_identity.rs +++ b/pallets/subtensor/src/migrations/migrate_chain_identity.rs @@ -31,7 +31,7 @@ fn string_to_bounded_vec(input: &str) -> Result>, } pub fn migrate_set_hotkey_identities() -> Weight { - let migration_name = b"fix_total_coldkey_stake_v7".to_vec(); + let migration_name = b"migrate_identities".to_vec(); // Initialize the weight with one read operation. let mut weight = T::DbWeight::get().reads(1); @@ -140,17 +140,26 @@ pub fn migrate_set_hotkey_identities() -> Weight { && identity.description.len() <= 1024 && identity.additional.len() <= 1024; if !is_valid { + log::info!( + "Bytes not correct" + ); continue; } // Get the owning coldkey. let coldkey = Owner::::get(decoded_hotkey.clone()); + log::info!("ColdKey: {:?}", decoded_hotkey); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); // Sink into the map. Identities::::insert(coldkey.clone(), identity.clone()); weight = weight.saturating_add(T::DbWeight::get().writes(1)); } + } else { + log::info!( + "Failed to decode JSON" + ); } // Mark the migration as completed HasMigrationRun::::insert(&migration_name, true); From 323313305c6e63c6c70a38e8aa6bba697bb1e275 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 24 Jul 2024 15:38:11 -0700 Subject: [PATCH 060/269] fmt --- .../subtensor/src/migrations/migrate_chain_identity.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/migrations/migrate_chain_identity.rs b/pallets/subtensor/src/migrations/migrate_chain_identity.rs index 2b16dd180..06ee5dd3f 100644 --- a/pallets/subtensor/src/migrations/migrate_chain_identity.rs +++ b/pallets/subtensor/src/migrations/migrate_chain_identity.rs @@ -140,9 +140,7 @@ pub fn migrate_set_hotkey_identities() -> Weight { && identity.description.len() <= 1024 && identity.additional.len() <= 1024; if !is_valid { - log::info!( - "Bytes not correct" - ); + log::info!("Bytes not correct"); continue; } @@ -157,9 +155,7 @@ pub fn migrate_set_hotkey_identities() -> Weight { weight = weight.saturating_add(T::DbWeight::get().writes(1)); } } else { - log::info!( - "Failed to decode JSON" - ); + log::info!("Failed to decode JSON"); } // Mark the migration as completed HasMigrationRun::::insert(&migration_name, true); From 771ce01eba20d4bd4f683535fa5e9341014ce8dd Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 25 Jul 2024 09:30:00 -0700 Subject: [PATCH 061/269] fix test --- pallets/subtensor/tests/serving.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index b736a90d0..56f788e0f 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -808,7 +808,7 @@ fn test_migrate_set_hotkey_identities() { // Assert that the migration has run assert!(HasMigrationRun::::get( - b"fix_total_coldkey_stake_v7".to_vec() + b"migrate_identities".to_vec() )); // Verify that some identities were set From dccfd98fe195c15b1dc92ad1713146a99389a835 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 26 Jul 2024 09:32:02 -0700 Subject: [PATCH 062/269] fmt & clippy --- pallets/collective/src/lib.rs | 6 +++--- pallets/subtensor/tests/serving.rs | 4 +--- runtime/src/lib.rs | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 96040f99c..6aae3c85e 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,9 +951,9 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: - /// - `P` is number of active proposals + /// - two removals, one mutation. + /// - computation and i/o `O(P)` where: + /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, yes_votes: MemberCount, diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 56f788e0f..4fb6c589a 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -807,9 +807,7 @@ fn test_migrate_set_hotkey_identities() { >(); // Assert that the migration has run - assert!(HasMigrationRun::::get( - b"migrate_identities".to_vec() - )); + assert!(HasMigrationRun::::get(b"migrate_identities".to_vec())); // Verify that some identities were set // Note: This assumes that at least one valid identity was in the JSON file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3bd71977e..534395f22 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -529,6 +529,7 @@ impl pallet_collective::Config for Runtime { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -544,6 +545,7 @@ impl pallet_membership::Config for Runtime { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; From cb78f39dea933e7510bc34701044b5691b046240 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 26 Jul 2024 20:59:56 +0400 Subject: [PATCH 063/269] clippy --- pallets/collective/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 96040f99c..c6552b036 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,8 +951,8 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: + /// Two removals, one mutation. + /// Computation and i/o `O(P)` where: /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, From 62c152755a9feca61f37ec200603007dba3eac28 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 26 Jul 2024 21:01:28 +0400 Subject: [PATCH 064/269] clippy --- pallets/collective/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 96040f99c..c6552b036 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,8 +951,8 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: + /// Two removals, one mutation. + /// Computation and i/o `O(P)` where: /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, From 4dbdd77b2e478cd822b34f513062550c383faec7 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 26 Jul 2024 10:03:40 -0700 Subject: [PATCH 065/269] fmt & clippy --- pallets/subtensor/tests/mock.rs | 4 ++++ pallets/subtensor/tests/serving.rs | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 27d11eb13..d8d677006 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -264,6 +264,7 @@ impl CollectiveInterface for TriumvirateVotes { } // We call pallet_collective TriumvirateCollective +#[allow(dead_code)] type TriumvirateCollective = pallet_collective::Instance1; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -281,6 +282,7 @@ impl pallet_collective::Config for Test { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -297,6 +299,7 @@ impl pallet_membership::Config for Test { // This is a dummy collective instance for managing senate members // Probably not the best solution, but fastest implementation +#[allow(dead_code)] type SenateCollective = pallet_collective::Instance2; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -314,6 +317,7 @@ impl pallet_collective::Config for Test { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 4fb6c589a..18b207609 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -586,7 +586,7 @@ fn test_do_set_identity() { )); // Check if identity is set correctly - let stored_identity = Identities::::get(coldkey).unwrap(); + let stored_identity = Identities::::get(coldkey).expect("Identity should be set"); assert_eq!(stored_identity.name, name); assert_eq!(stored_identity.url, url); assert_eq!(stored_identity.image, image); @@ -622,7 +622,8 @@ fn test_do_set_identity() { additional.clone() )); - let updated_identity = Identities::::get(coldkey).unwrap(); + let updated_identity = + Identities::::get(coldkey).expect("Updated identity should be set"); assert_eq!(updated_identity.name, new_name); assert_eq!(updated_identity.url, new_url); @@ -760,7 +761,7 @@ fn test_set_and_get_identity() { )); // Get and verify identity - let stored_identity = Identities::::get(coldkey).unwrap(); + let stored_identity = Identities::::get(coldkey).expect("Identity should be set"); assert_eq!(stored_identity.name, name); assert_eq!(stored_identity.url, url); assert_eq!(stored_identity.image, image); @@ -782,7 +783,8 @@ fn test_set_and_get_identity() { )); // Get and verify updated identity - let updated_identity = Identities::::get(coldkey).unwrap(); + let updated_identity = + Identities::::get(coldkey).expect("Updated identity should be set"); assert_eq!(updated_identity.name, new_name); assert_eq!(updated_identity.url, new_url); assert_eq!(updated_identity.image, image); From 646f19d58c3f47b821b331aed7548195c98d52ba Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:40:38 -0700 Subject: [PATCH 066/269] remove tarpaulin references --- pallets/subtensor/tests/children.rs | 1 - pallets/subtensor/tests/coinbase.rs | 4 ---- pallets/subtensor/tests/difficulty.rs | 1 - pallets/subtensor/tests/epoch.rs | 1 - pallets/subtensor/tests/neuron_info.rs | 1 - pallets/subtensor/tests/registration.rs | 1 - pallets/subtensor/tests/serving.rs | 2 -- pallets/subtensor/tests/staking.rs | 2 -- pallets/subtensor/tests/weights.rs | 3 --- 9 files changed, 16 deletions(-) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 9ad07e1e7..e834baa85 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -297,7 +297,6 @@ fn test_do_set_child_singular_multiple_children() { // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_add_singular_child --exact --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_add_singular_child() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index 8fd963dff..d6e48bbcc 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -6,7 +6,6 @@ use sp_core::U256; // Test the ability to hash all sorts of hotkeys. #[test] -#[cfg(not(tarpaulin))] fn test_hotkey_hashing() { new_test_ext(1).execute_with(|| { for i in 0..10000 { @@ -18,7 +17,6 @@ fn test_hotkey_hashing() { // Test drain tempo on hotkeys. // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_hotkey_drain_time -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_hotkey_drain_time() { new_test_ext(1).execute_with(|| { // Block 0 @@ -46,7 +44,6 @@ fn test_hotkey_drain_time() { // To run this test specifically, use the following command: // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_coinbase_basic -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_coinbase_basic() { new_test_ext(1).execute_with(|| { // Define network ID @@ -138,7 +135,6 @@ fn test_coinbase_basic() { // Test getting and setting hotkey emission tempo // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_set_and_get_hotkey_emission_tempo -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_set_and_get_hotkey_emission_tempo() { new_test_ext(1).execute_with(|| { // Get the default hotkey emission tempo diff --git a/pallets/subtensor/tests/difficulty.rs b/pallets/subtensor/tests/difficulty.rs index 05238bc43..c3023b829 100644 --- a/pallets/subtensor/tests/difficulty.rs +++ b/pallets/subtensor/tests/difficulty.rs @@ -5,7 +5,6 @@ mod mock; use sp_core::U256; #[test] -#[cfg(not(tarpaulin))] fn test_registration_difficulty_adjustment() { new_test_ext(1).execute_with(|| { // Create Net 1 diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 526a58b4e..b639a4ac4 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2107,7 +2107,6 @@ fn test_zero_weights() { // Test that epoch assigns validator permits to highest stake uids, varies uid interleaving and stake values. #[test] -#[cfg(not(tarpaulin))] fn test_validator_permits() { let netuid: u16 = 1; let tempo: u16 = u16::MAX - 1; // high tempo to skip automatic epochs in on_initialize, use manual epochs instead diff --git a/pallets/subtensor/tests/neuron_info.rs b/pallets/subtensor/tests/neuron_info.rs index 10df1c07d..3494fdc5f 100644 --- a/pallets/subtensor/tests/neuron_info.rs +++ b/pallets/subtensor/tests/neuron_info.rs @@ -15,7 +15,6 @@ fn test_get_neuron_none() { } #[test] -#[cfg(not(tarpaulin))] fn test_get_neuron_some() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index bd95ae3b1..7d6e8ea65 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -539,7 +539,6 @@ fn test_burn_adjustment() { } #[test] -#[cfg(not(tarpaulin))] fn test_registration_too_many_registrations_per_block() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 18b207609..b0eada8e6 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -164,7 +164,6 @@ fn test_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_axon_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); @@ -382,7 +381,6 @@ fn test_prometheus_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_prometheus_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 2952426a9..5bf95841a 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -15,7 +15,6 @@ use sp_core::{H256, U256}; ************************************************************/ #[test] -#[cfg(not(tarpaulin))] fn test_add_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); @@ -521,7 +520,6 @@ fn test_remove_stake_rate_limit_exceeded() { } #[test] -#[cfg(not(tarpaulin))] fn test_remove_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 2344bd425..020eb1f6b 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -21,7 +21,6 @@ use substrate_fixed::types::I32F32; // Test the call passes through the subtensor module. #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -41,7 +40,6 @@ fn test_set_weights_dispatch_info_ok() { }); } #[test] -#[cfg(not(tarpaulin))] fn test_set_rootweights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -404,7 +402,6 @@ fn test_weights_err_no_validator_permit() { // To execute this test: cargo test --package pallet-subtensor --test weights test_set_weights_min_stake_failed -- --nocapture` #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_min_stake_failed() { new_test_ext(0).execute_with(|| { let dests = vec![0]; From 2ab7d27b73ccdf19e4819abf504b8dafd476c430 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:09:45 -0700 Subject: [PATCH 067/269] Update Cargo.toml --- pallets/subtensor/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 82ed28646..9f022b5b7 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -84,6 +84,7 @@ std = [ "serde_with/std", "substrate-fixed/std", "num-traits/std", + "serde_json/std" ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", From d9b8eb9570641c65118b974520fb59e79ea5f79f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 29 Jul 2024 20:06:11 +0400 Subject: [PATCH 068/269] stash --- Cargo.toml | 2 +- pallets/subtensor/src/lib.rs | 1 + pallets/subtensor/src/macros/config.rs | 13 +++- pallets/subtensor/src/macros/dispatches.rs | 87 +++++++++++++++++++++- runtime/Cargo.toml | 6 +- runtime/src/lib.rs | 24 +++++- runtime/tests/pallet_proxy.rs | 2 +- 7 files changed, 127 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a7565a01..2db7ce1d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ subtensor-macros = { path = "support/macros" } frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" , default-features = false } +frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index abf6a8613..3f9004167 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -19,6 +19,7 @@ use codec::{Decode, Encode}; use frame_support::sp_runtime::transaction_validity::InvalidTransaction; use frame_support::sp_runtime::transaction_validity::ValidTransaction; use pallet_balances::Call as BalancesCall; +// use pallet_scheduler as Scheduler; use scale_info::TypeInfo; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index e59eac5ca..fb1ad5415 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -1,7 +1,6 @@ #![allow(clippy::crate_in_macro_def)] use frame_support::pallet_macros::pallet_section; - /// A [`pallet_section`] that defines the errors for a pallet. /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] @@ -31,6 +30,18 @@ mod config { /// Interface to allow other pallets to control who can register identities type TriumvirateInterface: crate::CollectiveInterface; + /// The scheduler type used for scheduling delayed calls. + type Scheduler: ScheduleNamed, Call, Self::RuntimeOrigin>; + + /// The hashing system (algorithm) being used in the runtime, matching the Scheduler's Hasher. + type Hasher: Hash< + Output = <, + Call, + Self::RuntimeOrigin, + >>::Hasher as Hash>::Output, + >; + /// ================================= /// ==== Initial Value Constants ==== /// ================================= diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 293dc0238..a84c5964d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -4,6 +4,12 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod dispatches { + use frame_support::traits::schedule::v3::Named as ScheduleNamed; + use frame_support::traits::schedule::DispatchTime; + use frame_support::traits::Bounded; + use frame_system::pallet_prelude::BlockNumberFor; + use sp_runtime::traits::Hash; + use sp_runtime::traits::Saturating; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. /// Dispatchable functions must be annotated with a weight and must return a DispatchResult. @@ -675,7 +681,11 @@ mod dispatches { origin: OriginFor, new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { - Self::do_swap_coldkey(origin, &new_coldkey) + // Ensure it's called with root privileges (scheduler has root privileges) + ensure_root(origin.clone())?; + + let who = ensure_signed(origin)?; + Self::do_swap_coldkey(frame_system::RawOrigin::Signed(who).into(), &new_coldkey) } /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. @@ -901,5 +911,80 @@ mod dispatches { Self::do_set_children(origin, hotkey, netuid, children)?; Ok(().into()) } + + /// Schedules a coldkey swap operation to be executed at a future block. + /// + /// 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((Weight::from_parts(119_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] + pub fn schedule_swap_coldkey( + origin: OriginFor, + new_coldkey: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + // Calculate the number of blocks in 5 days + let blocks_in_5_days: u32 = 5 * 24 * 60 * 60 / 12; + + let current_block = >::block_number(); + let when = current_block.saturating_add(BlockNumberFor::::from(blocks_in_5_days)); + + let call = Call::::swap_coldkey { + new_coldkey: new_coldkey.clone(), + }; + + let unique_id = ( + b"schedule_swap_coldkey", + who.clone(), + new_coldkey.clone(), + when, + ) + .using_encoded(sp_io::hashing::blake2_256); + + let hash = T::Hasher::hash(&call.encode()); + let len = call.using_encoded(|e| e.len() as u32); + + T::Scheduler::schedule_named( + unique_id, + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + Bounded::Lookup { hash, len }, + )?; + + Ok(().into()) + } } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 042d0337c..c8fac478b 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -155,7 +155,7 @@ std = [ "sp-tracing/std", "log/std", "sp-storage/std", - "sp-genesis-builder/std" + "sp-genesis-builder/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", @@ -178,7 +178,7 @@ runtime-benchmarks = [ "pallet-multisig/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks" + "pallet-sudo/runtime-benchmarks", ] try-runtime = [ "frame-try-runtime/try-runtime", @@ -204,6 +204,6 @@ try-runtime = [ "sp-runtime/try-runtime", "pallet-admin-utils/try-runtime", "pallet-commitments/try-runtime", - "pallet-registry/try-runtime" + "pallet-registry/try-runtime", ] metadata-hash = ["substrate-wasm-builder/metadata-hash"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 61f3a3c85..1d2236dc3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,6 +12,7 @@ pub mod check_nonce; mod migrations; use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, @@ -68,6 +69,7 @@ pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; // Subtensor module +pub use pallet_scheduler; pub use pallet_subtensor; // An index to a block. @@ -94,6 +96,10 @@ type MemberCount = u32; pub type Nonce = u32; +/// The scheduler type used for scheduling delayed calls. +// With something like this: +// type Scheduler = pallet_subtensor::Scheduler; + // Method used to calculate the fee of an extrinsic pub const fn deposit(items: u32, bytes: u32) -> Balance { pub const ITEMS_FEE: Balance = 2_000 * 10_000; @@ -834,6 +840,19 @@ impl pallet_commitments::Config for Runtime { type RateLimit = CommitmentRateLimit; } +impl pallet_scheduler::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = ConstU32<50>; + type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type OriginPrivilegeCmp = EqualPrivilegeOnly; + type Preimages = Preimage; +} + // Configure the pallet subtensor. parameter_types! { pub const SubtensorInitialRho: u16 = 10; @@ -883,6 +902,7 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const SubtensorInitialHotkeyEmissionTempo: u64 = 7200; // Drain every day. pub const SubtensorInitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO + } impl pallet_subtensor::Config for Runtime { @@ -892,6 +912,8 @@ impl pallet_subtensor::Config for Runtime { type CouncilOrigin = EnsureMajoritySenate; type SenateMembers = ManageSenateMembers; type TriumvirateInterface = TriumvirateVotes; + type Scheduler = pallet_scheduler::Pallet; + type Hasher = BlakeTwo256; type InitialRho = SubtensorInitialRho; type InitialKappa = SubtensorInitialKappa; @@ -1266,12 +1288,12 @@ construct_runtime!( Sudo: pallet_sudo, Multisig: pallet_multisig, Preimage: pallet_preimage, - Scheduler: pallet_scheduler, Proxy: pallet_proxy, Registry: pallet_registry, Commitments: pallet_commitments, AdminUtils: pallet_admin_utils, SafeMode: pallet_safe_mode, + Scheduler: pallet_scheduler, } ); diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs index 796dfc471..192893c1e 100644 --- a/runtime/tests/pallet_proxy.rs +++ b/runtime/tests/pallet_proxy.rs @@ -4,7 +4,7 @@ use codec::Encode; use frame_support::{assert_ok, traits::InstanceFilter, BoundedVec}; use node_subtensor_runtime::{ AccountId, BalancesCall, BuildStorage, Proxy, ProxyType, Runtime, RuntimeCall, RuntimeEvent, - RuntimeGenesisConfig, RuntimeOrigin, SubtensorModule, System, SystemCall, + RuntimeGenesisConfig, RuntimeOrigin, SchedulerCall, SubtensorModule, System, SystemCall, }; const ACCOUNT: [u8; 32] = [1_u8; 32]; From cd23ac287c9dc5dc02999f3751cd255ad8a5e143 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 29 Jul 2024 21:10:48 +0400 Subject: [PATCH 069/269] chore: clippy --- pallets/collective/src/lib.rs | 6 +++--- pallets/subtensor/tests/children.rs | 1 - pallets/subtensor/tests/coinbase.rs | 4 ---- pallets/subtensor/tests/difficulty.rs | 1 - pallets/subtensor/tests/epoch.rs | 1 - pallets/subtensor/tests/mock.rs | 4 ++++ pallets/subtensor/tests/neuron_info.rs | 1 - pallets/subtensor/tests/registration.rs | 1 - pallets/subtensor/tests/serving.rs | 2 -- pallets/subtensor/tests/staking.rs | 2 -- pallets/subtensor/tests/weights.rs | 3 --- runtime/src/lib.rs | 2 ++ 12 files changed, 9 insertions(+), 19 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index c6552b036..6aae3c85e 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,9 +951,9 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: - /// - `P` is number of active proposals + /// - two removals, one mutation. + /// - computation and i/o `O(P)` where: + /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, yes_votes: MemberCount, diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 9ad07e1e7..e834baa85 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -297,7 +297,6 @@ fn test_do_set_child_singular_multiple_children() { // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_add_singular_child --exact --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_add_singular_child() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index 8fd963dff..d6e48bbcc 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -6,7 +6,6 @@ use sp_core::U256; // Test the ability to hash all sorts of hotkeys. #[test] -#[cfg(not(tarpaulin))] fn test_hotkey_hashing() { new_test_ext(1).execute_with(|| { for i in 0..10000 { @@ -18,7 +17,6 @@ fn test_hotkey_hashing() { // Test drain tempo on hotkeys. // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_hotkey_drain_time -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_hotkey_drain_time() { new_test_ext(1).execute_with(|| { // Block 0 @@ -46,7 +44,6 @@ fn test_hotkey_drain_time() { // To run this test specifically, use the following command: // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_coinbase_basic -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_coinbase_basic() { new_test_ext(1).execute_with(|| { // Define network ID @@ -138,7 +135,6 @@ fn test_coinbase_basic() { // Test getting and setting hotkey emission tempo // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_set_and_get_hotkey_emission_tempo -- --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_set_and_get_hotkey_emission_tempo() { new_test_ext(1).execute_with(|| { // Get the default hotkey emission tempo diff --git a/pallets/subtensor/tests/difficulty.rs b/pallets/subtensor/tests/difficulty.rs index 05238bc43..c3023b829 100644 --- a/pallets/subtensor/tests/difficulty.rs +++ b/pallets/subtensor/tests/difficulty.rs @@ -5,7 +5,6 @@ mod mock; use sp_core::U256; #[test] -#[cfg(not(tarpaulin))] fn test_registration_difficulty_adjustment() { new_test_ext(1).execute_with(|| { // Create Net 1 diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 526a58b4e..b639a4ac4 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2107,7 +2107,6 @@ fn test_zero_weights() { // Test that epoch assigns validator permits to highest stake uids, varies uid interleaving and stake values. #[test] -#[cfg(not(tarpaulin))] fn test_validator_permits() { let netuid: u16 = 1; let tempo: u16 = u16::MAX - 1; // high tempo to skip automatic epochs in on_initialize, use manual epochs instead diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 27d11eb13..d8d677006 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -264,6 +264,7 @@ impl CollectiveInterface for TriumvirateVotes { } // We call pallet_collective TriumvirateCollective +#[allow(dead_code)] type TriumvirateCollective = pallet_collective::Instance1; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -281,6 +282,7 @@ impl pallet_collective::Config for Test { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -297,6 +299,7 @@ impl pallet_membership::Config for Test { // This is a dummy collective instance for managing senate members // Probably not the best solution, but fastest implementation +#[allow(dead_code)] type SenateCollective = pallet_collective::Instance2; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -314,6 +317,7 @@ impl pallet_collective::Config for Test { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/pallets/subtensor/tests/neuron_info.rs b/pallets/subtensor/tests/neuron_info.rs index 10df1c07d..3494fdc5f 100644 --- a/pallets/subtensor/tests/neuron_info.rs +++ b/pallets/subtensor/tests/neuron_info.rs @@ -15,7 +15,6 @@ fn test_get_neuron_none() { } #[test] -#[cfg(not(tarpaulin))] fn test_get_neuron_some() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index bd95ae3b1..7d6e8ea65 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -539,7 +539,6 @@ fn test_burn_adjustment() { } #[test] -#[cfg(not(tarpaulin))] fn test_registration_too_many_registrations_per_block() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 41e9888cc..b87b7fd10 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -161,7 +161,6 @@ fn test_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_axon_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); @@ -379,7 +378,6 @@ fn test_prometheus_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_prometheus_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 2952426a9..5bf95841a 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -15,7 +15,6 @@ use sp_core::{H256, U256}; ************************************************************/ #[test] -#[cfg(not(tarpaulin))] fn test_add_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); @@ -521,7 +520,6 @@ fn test_remove_stake_rate_limit_exceeded() { } #[test] -#[cfg(not(tarpaulin))] fn test_remove_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 2344bd425..020eb1f6b 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -21,7 +21,6 @@ use substrate_fixed::types::I32F32; // Test the call passes through the subtensor module. #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -41,7 +40,6 @@ fn test_set_weights_dispatch_info_ok() { }); } #[test] -#[cfg(not(tarpaulin))] fn test_set_rootweights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -404,7 +402,6 @@ fn test_weights_err_no_validator_permit() { // To execute this test: cargo test --package pallet-subtensor --test weights test_set_weights_min_stake_failed -- --nocapture` #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_min_stake_failed() { new_test_ext(0).execute_with(|| { let dests = vec![0]; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2d1716337..66951b7fc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -516,6 +516,7 @@ impl pallet_collective::Config for Runtime { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -531,6 +532,7 @@ impl pallet_membership::Config for Runtime { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; From d7ef0586047420de51651782678150a20bf5168a Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Jul 2024 18:57:04 +0800 Subject: [PATCH 070/269] fix compile error --- pallets/subtensor/src/lib.rs | 9 ++++++++- pallets/subtensor/src/macros/config.rs | 11 +---------- pallets/subtensor/src/macros/dispatches.rs | 19 ++++++++++--------- runtime/src/lib.rs | 18 +----------------- 4 files changed, 20 insertions(+), 37 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 3f9004167..f5febb0aa 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -65,7 +65,7 @@ pub mod pallet { use frame_support::{ dispatch::GetDispatchInfo, pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, - traits::{tokens::fungible, UnfilteredDispatchable}, + traits::{tokens::fungible, OriginTrait, UnfilteredDispatchable}, }; use frame_system::pallet_prelude::*; use sp_core::H256; @@ -78,6 +78,13 @@ pub mod pallet { #[cfg(feature = "std")] use sp_std::prelude::Box; + /// Origin for the pallet + pub type PalletsOriginOf = + <::RuntimeOrigin as OriginTrait>::PalletsOrigin; + + /// Call type for the pallet + pub type CallOf = ::RuntimeCall; + /// Tracks version for migrations. Should be monotonic with respect to the /// order of migrations. (i.e. always increasing) const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index fb1ad5415..d2504eb4e 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -31,16 +31,7 @@ mod config { type TriumvirateInterface: crate::CollectiveInterface; /// The scheduler type used for scheduling delayed calls. - type Scheduler: ScheduleNamed, Call, Self::RuntimeOrigin>; - - /// The hashing system (algorithm) being used in the runtime, matching the Scheduler's Hasher. - type Hasher: Hash< - Output = <, - Call, - Self::RuntimeOrigin, - >>::Hasher as Hash>::Output, - >; + type Scheduler: ScheduleNamed, CallOf, PalletsOriginOf>; /// ================================= /// ==== Initial Value Constants ==== diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index a84c5964d..43eeb4105 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -972,17 +972,18 @@ mod dispatches { ) .using_encoded(sp_io::hashing::blake2_256); - let hash = T::Hasher::hash(&call.encode()); + let hash = sp_runtime::traits::BlakeTwo256::hash_of(&call); + let len = call.using_encoded(|e| e.len() as u32); - T::Scheduler::schedule_named( - unique_id, - DispatchTime::At(when), - None, - 63, - frame_system::RawOrigin::Root.into(), - Bounded::Lookup { hash, len }, - )?; + // T::Scheduler::schedule_named( + // unique_id, + // DispatchTime::At(when), + // None, + // 63, + // frame_system::RawOrigin::Root.into(), + // Bounded::Lookup { hash, len }, + // )?; Ok(().into()) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1d2236dc3..289e9effe 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,7 +12,6 @@ pub mod check_nonce; mod migrations; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, @@ -840,19 +839,6 @@ impl pallet_commitments::Config for Runtime { type RateLimit = CommitmentRateLimit; } -impl pallet_scheduler::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type RuntimeOrigin = RuntimeOrigin; - type PalletsOrigin = OriginCaller; - type RuntimeCall = RuntimeCall; - type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureRoot; - type MaxScheduledPerBlock = ConstU32<50>; - type WeightInfo = pallet_scheduler::weights::SubstrateWeight; - type OriginPrivilegeCmp = EqualPrivilegeOnly; - type Preimages = Preimage; -} - // Configure the pallet subtensor. parameter_types! { pub const SubtensorInitialRho: u16 = 10; @@ -912,9 +898,7 @@ impl pallet_subtensor::Config for Runtime { type CouncilOrigin = EnsureMajoritySenate; type SenateMembers = ManageSenateMembers; type TriumvirateInterface = TriumvirateVotes; - type Scheduler = pallet_scheduler::Pallet; - type Hasher = BlakeTwo256; - + type Scheduler = Scheduler; type InitialRho = SubtensorInitialRho; type InitialKappa = SubtensorInitialKappa; type InitialMaxAllowedUids = SubtensorInitialMaxAllowedUids; From 2260fa0340dbb6f15991068314f79503e6db6446 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Jul 2024 20:15:19 +0800 Subject: [PATCH 071/269] fix error --- pallets/subtensor/src/macros/dispatches.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 43eeb4105..f5599adae 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -972,18 +972,22 @@ mod dispatches { ) .using_encoded(sp_io::hashing::blake2_256); - let hash = sp_runtime::traits::BlakeTwo256::hash_of(&call); + let hash = , + CallOf, + PalletsOriginOf, + >>::Hasher::hash_of(&call); let len = call.using_encoded(|e| e.len() as u32); - // T::Scheduler::schedule_named( - // unique_id, - // DispatchTime::At(when), - // None, - // 63, - // frame_system::RawOrigin::Root.into(), - // Bounded::Lookup { hash, len }, - // )?; + T::Scheduler::schedule_named( + unique_id, + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + Bounded::Lookup { hash, len }, + )?; Ok(().into()) } From ed345c5428ce858b18211179592079cf410538a9 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Jul 2024 22:10:34 +0800 Subject: [PATCH 072/269] fix clippy --- pallets/collective/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index c6552b036..e3c64f1a7 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,9 +951,9 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: - /// - `P` is number of active proposals + /// - Two removals, one mutation. + /// - Computation and i/o `O(P)` where: + /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, yes_votes: MemberCount, From 3e6d1af7375cca6adc82241f5d838c398cd694b6 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Jul 2024 22:19:07 +0800 Subject: [PATCH 073/269] fix clippy --- pallets/collective/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index e3c64f1a7..0ce5e3e08 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,8 +951,8 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// - Two removals, one mutation. - /// - Computation and i/o `O(P)` where: + /// - Two removals, one mutation. + /// - Computation and i/o `O(P)` where: /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, From 8dda65de145547fd050b34a99fd58d3177cded80 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 30 Jul 2024 22:51:56 +0800 Subject: [PATCH 074/269] fix unit test --- pallets/admin-utils/Cargo.toml | 3 ++- pallets/admin-utils/tests/mock.rs | 40 +++++++++++++++++++++++++++---- pallets/subtensor/Cargo.toml | 2 ++ pallets/subtensor/tests/mock.rs | 34 ++++++++++++++++++++++++-- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index 859972fce..97371a79f 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -37,7 +37,8 @@ sp-io = { workspace = true } sp-tracing = { workspace = true } sp-consensus-aura = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } - +pallet-scheduler = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 7ae11b6fb..4b08c578b 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -2,19 +2,20 @@ use frame_support::{ assert_ok, derive_impl, parameter_types, - traits::{Everything, Hooks}, + traits::{Everything, Hooks, PrivilegeCmp}, weights, }; use frame_system as system; -use frame_system::{limits, EnsureNever}; +use frame_system::{limits, EnsureNever, EnsureRoot}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, DispatchError, + BuildStorage, DispatchError, Perbill, }; - +use sp_std::cmp::Ordering; +use sp_weights::Weight; type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -25,6 +26,7 @@ frame_support::construct_runtime!( Balances: pallet_balances, AdminUtils: pallet_admin_utils, SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event, Error}, + Scheduler: pallet_scheduler, } ); @@ -126,7 +128,7 @@ impl pallet_subtensor::Config for Test { type CouncilOrigin = EnsureNever; type SenateMembers = (); type TriumvirateInterface = (); - + type Scheduler = Scheduler; type InitialMinAllowedWeights = InitialMinAllowedWeights; type InitialEmissionValue = InitialEmissionValue; type InitialMaxWeightsLimit = InitialMaxWeightsLimit; @@ -218,6 +220,34 @@ impl pallet_balances::Config for Test { type RuntimeHoldReason = (); } +pub struct OriginPrivilegeCmp; + +impl PrivilegeCmp for OriginPrivilegeCmp { + fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { + None + } +} + +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 50; + pub const NoPreimagePostponement: Option = Some(10); +} + +impl pallet_scheduler::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type OriginPrivilegeCmp = OriginPrivilegeCmp; + type Preimages = (); +} + pub struct SubtensorIntrf; impl pallet_admin_utils::SubtensorInterface for SubtensorIntrf { diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index a0835008f..b002c2371 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -48,12 +48,14 @@ num-traits = { version = "0.2.19", default-features = false, features = ["libm"] [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } +pallet-scheduler = { workspace = true } sp-version = { workspace = true } # Substrate sp-tracing = { workspace = true } parity-util-mem = { workspace = true, features = ["primitive-types"] } rand = { workspace = true } sp-core = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 27d11eb13..6f75fcb1a 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -6,7 +6,7 @@ use frame_support::weights::constants::RocksDbWeight; use frame_support::weights::Weight; use frame_support::{ assert_ok, parameter_types, - traits::{Everything, Hooks}, + traits::{Everything, Hooks, PrivilegeCmp}, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; @@ -17,6 +17,7 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; +use sp_std::cmp::Ordering; type Block = frame_system::mocking::MockBlock; @@ -32,6 +33,7 @@ frame_support::construct_runtime!( SenateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config}, SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Storage, Event}, + Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, } ); @@ -336,7 +338,7 @@ impl pallet_subtensor::Config for Test { type CouncilOrigin = frame_system::EnsureSigned; type SenateMembers = ManageSenateMembers; type TriumvirateInterface = TriumvirateVotes; - + type Scheduler = Scheduler; type InitialMinAllowedWeights = InitialMinAllowedWeights; type InitialEmissionValue = InitialEmissionValue; type InitialMaxWeightsLimit = InitialMaxWeightsLimit; @@ -385,6 +387,34 @@ impl pallet_subtensor::Config for Test { type InitialNetworkMaxStake = InitialNetworkMaxStake; } +pub struct OriginPrivilegeCmp; + +impl PrivilegeCmp for OriginPrivilegeCmp { + fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { + None + } +} + +parameter_types! { + pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * + BlockWeights::get().max_block; + pub const MaxScheduledPerBlock: u32 = 50; + pub const NoPreimagePostponement: Option = Some(10); +} + +impl pallet_scheduler::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type PalletsOrigin = OriginCaller; + type RuntimeCall = RuntimeCall; + type MaximumWeight = MaximumSchedulerWeight; + type ScheduleOrigin = EnsureRoot; + type MaxScheduledPerBlock = MaxScheduledPerBlock; + type WeightInfo = pallet_scheduler::weights::SubstrateWeight; + type OriginPrivilegeCmp = OriginPrivilegeCmp; + type Preimages = (); +} + impl pallet_utility::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; From 8ce0d690b37fdd0635267fe1f6f78fe5d3e1078f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 19:20:21 +0400 Subject: [PATCH 075/269] feat: child key takes --- pallets/admin-utils/tests/mock.rs | 13 +- pallets/admin-utils/tests/tests.rs | 6 +- .../subtensor/src/coinbase/run_coinbase.rs | 2 +- pallets/subtensor/src/lib.rs | 55 ++++-- pallets/subtensor/src/macros/config.rs | 13 +- pallets/subtensor/src/macros/dispatches.rs | 80 +++++++- pallets/subtensor/src/macros/errors.rs | 4 + pallets/subtensor/src/macros/events.rs | 8 + .../subtensor/src/staking/become_delegate.rs | 6 +- .../subtensor/src/staking/decrease_take.rs | 4 +- pallets/subtensor/src/staking/helpers.rs | 7 + .../subtensor/src/staking/increase_take.rs | 4 +- pallets/subtensor/src/staking/set_children.rs | 98 ++++++++++ pallets/subtensor/src/utils.rs | 70 +++++-- pallets/subtensor/tests/children.rs | 183 +++++++++++++++++- pallets/subtensor/tests/mock.rs | 13 +- pallets/subtensor/tests/staking.rs | 70 +++---- runtime/src/lib.rs | 12 +- scripts/test_specific.sh | 2 +- 19 files changed, 558 insertions(+), 92 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 7ae11b6fb..0142a435a 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -77,12 +77,16 @@ parameter_types! { pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; - pub const InitialDefaultTake: u16 = 11_796; // 18% honest number. + pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. + pub const InitialMinDelegateTake: u16 = 5_898; // 9%; + pub const InitialDefaultChildKeyTake: u16 = 11_796; // 18% honest number. + pub const InitialMinChildKeyTake: u16 = 5_898; // 9%; pub const InitialMinTake: u16 = 5_898; // 9%; pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing pub const InitialTxDelegateTakeRateLimit: u64 = 0; // Disable rate limit for testing + pub const InitialTxChildKeyTakeRateLimit: u64 = 0; // Disable rate limit for testing pub const InitialBurn: u64 = 0; pub const InitialMinBurn: u64 = 0; pub const InitialMaxBurn: u64 = 1_000_000_000; @@ -146,14 +150,17 @@ impl pallet_subtensor::Config for Test { type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; - type InitialDefaultTake = InitialDefaultTake; - type InitialMinTake = InitialMinTake; + type InitialDefaultDelegateTake = InitialDefaultDelegateTake; + type InitialMinDelegateTake = InitialMinDelegateTake; + type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; + type InitialMinChildKeyTake = InitialMinChildKeyTake; type InitialWeightsVersionKey = InitialWeightsVersionKey; type InitialMaxDifficulty = InitialMaxDifficulty; type InitialMinDifficulty = InitialMinDifficulty; type InitialServingRateLimit = InitialServingRateLimit; type InitialTxRateLimit = InitialTxRateLimit; type InitialTxDelegateTakeRateLimit = InitialTxDelegateTakeRateLimit; + type InitialTxChildKeyTakeRateLimit = InitialTxChildKeyTakeRateLimit; type InitialBurn = InitialBurn; type InitialMaxBurn = InitialMaxBurn; type InitialMinBurn = InitialMinBurn; diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 6e78a1ed6..af3bf66d7 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -16,7 +16,7 @@ use mock::*; fn test_sudo_set_default_take() { new_test_ext().execute_with(|| { let to_be_set: u16 = 10; - let init_value: u16 = SubtensorModule::get_default_take(); + let init_value: u16 = SubtensorModule::get_default_delegate_take(); assert_eq!( AdminUtils::sudo_set_default_take( <::RuntimeOrigin>::signed(U256::from(0)), @@ -24,12 +24,12 @@ fn test_sudo_set_default_take() { ), Err(DispatchError::BadOrigin) ); - assert_eq!(SubtensorModule::get_default_take(), init_value); + assert_eq!(SubtensorModule::get_default_delegate_take(), init_value); assert_ok!(AdminUtils::sudo_set_default_take( <::RuntimeOrigin>::root(), to_be_set )); - assert_eq!(SubtensorModule::get_default_take(), to_be_set); + assert_eq!(SubtensorModule::get_default_delegate_take(), to_be_set); }); } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index fcf76728f..442a9f085 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -186,7 +186,7 @@ impl Pallet { mining_emission: u64, ) { // --- 1. First, calculate the hotkey's share of the emission. - let take_proportion: I64F64 = I64F64::from_num(Delegates::::get(hotkey)) + let take_proportion: I64F64 = I64F64::from_num(Self::get_childkey_take(hotkey, netuid)) .saturating_div(I64F64::from_num(u16::MAX)); let hotkey_take: u64 = take_proportion .saturating_mul(I64F64::from_num(validating_emission)) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index abf6a8613..c58c6a0c6 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -143,15 +143,27 @@ pub mod pallet { 21_000_000_000_000_000 } #[pallet::type_value] - /// Default total stake. - pub fn DefaultDefaultTake() -> u16 { - T::InitialDefaultTake::get() + /// Default Delegate Take. + pub fn DefaultDelegateTake() -> u16 { + T::InitialDefaultDelegateTake::get() + } + + #[pallet::type_value] + /// Default childkey take. + pub fn DefaultChildKeyTake() -> u16 { + T::InitialDefaultChildKeyTake::get() } #[pallet::type_value] - /// Default minimum take. - pub fn DefaultMinTake() -> u16 { - T::InitialMinTake::get() + /// Default minimum delegate take. + pub fn DefaultMinDelegateTake() -> u16 { + T::InitialMinDelegateTake::get() } + #[pallet::type_value] + /// Default minimum childkey take. + pub fn DefaultMinChildKeyTake() -> u16 { + T::InitialMinChildKeyTake::get() + } + #[pallet::type_value] /// Default account take. pub fn DefaultAccountTake() -> u64 { @@ -516,6 +528,11 @@ pub mod pallet { T::InitialTxDelegateTakeRateLimit::get() } #[pallet::type_value] + /// Default value for chidlkey take rate limiting + pub fn DefaultTxChildKeyTakeRateLimit() -> u64 { + T::InitialTxChildKeyTakeRateLimit::get() + } + #[pallet::type_value] /// Default value for last extrinsic block. pub fn DefaultLastTxBlock() -> u64 { 0 @@ -567,10 +584,15 @@ pub mod pallet { pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; #[pallet::storage] // --- ITEM ( total_stake ) pub type TotalStake = StorageValue<_, u64, ValueQuery>; - #[pallet::storage] // --- ITEM ( default_take ) - pub type MaxTake = StorageValue<_, u16, ValueQuery, DefaultDefaultTake>; - #[pallet::storage] // --- ITEM ( min_take ) - pub type MinTake = StorageValue<_, u16, ValueQuery, DefaultMinTake>; + #[pallet::storage] // --- ITEM ( default_delegate_take ) + pub type MaxDelegateTake = StorageValue<_, u16, ValueQuery, DefaultDelegateTake>; + #[pallet::storage] // --- ITEM ( min_delegate_take ) + pub type MinDelegateTake = StorageValue<_, u16, ValueQuery, DefaultMinDelegateTake>; + #[pallet::storage] // --- ITEM ( default_childkey_take ) + pub type MaxChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultChildKeyTake>; + #[pallet::storage] // --- ITEM ( min_childkey_take ) + pub type MinChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMinChildKeyTake>; + #[pallet::storage] // --- ITEM ( global_block_emission ) pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; #[pallet::storage] // --- ITEM (target_stakes_per_interval) @@ -603,7 +625,12 @@ pub mod pallet { #[pallet::storage] /// MAP ( hot ) --> take | Returns the hotkey delegation take. And signals that this key is open for delegation. pub type Delegates = - StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDefaultTake>; + StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDelegateTake>; + #[pallet::storage] + /// DMAP ( hot, netuid ) --> take | Returns the hotkey childkey take for a specific subnet + pub type ChildkeyTake = + StorageMap<_, Blake2_128Concat, (T::AccountId, u16), u16, ValueQuery>; + #[pallet::storage] /// DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. pub type Stake = StorageDoubleMap< @@ -921,10 +948,14 @@ pub mod pallet { /// --- ITEM ( tx_rate_limit ) pub type TxRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxRateLimit>; #[pallet::storage] - /// --- ITEM ( tx_rate_limit ) + /// --- ITEM ( tx_delegate_take_rate_limit ) pub type TxDelegateTakeRateLimit = StorageValue<_, u64, ValueQuery, DefaultTxDelegateTakeRateLimit>; #[pallet::storage] + /// --- ITEM ( tx_childkey_take_rate_limit ) + pub type TxChildkeyTakeRateLimit = + StorageValue<_, u64, ValueQuery, DefaultTxChildKeyTakeRateLimit>; + #[pallet::storage] /// --- MAP ( netuid ) --> Whether or not Liquid Alpha is enabled pub type LiquidAlphaOn = StorageMap<_, Blake2_128Concat, u16, bool, ValueQuery, DefaultLiquidAlpha>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index e59eac5ca..2a6d8db00 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -112,10 +112,16 @@ mod config { type InitialMaxAllowedValidators: Get; /// Initial default delegation take. #[pallet::constant] - type InitialDefaultTake: Get; + type InitialDefaultDelegateTake: Get; /// Initial minimum delegation take. #[pallet::constant] - type InitialMinTake: Get; + type InitialMinDelegateTake: Get; + /// Initial default childkey take. + #[pallet::constant] + type InitialDefaultChildKeyTake: Get; + /// Initial minimum childkey take. + #[pallet::constant] + type InitialMinChildKeyTake: Get; /// Initial weights version key. #[pallet::constant] type InitialWeightsVersionKey: Get; @@ -128,6 +134,9 @@ mod config { /// Initial delegate take transaction rate limit. #[pallet::constant] type InitialTxDelegateTakeRateLimit: Get; + /// Initial childkey take transaction rate limit. + #[pallet::constant] + type InitialTxChildKeyTakeRateLimit: Get; /// Initial percentage of total stake required to join senate. #[pallet::constant] type InitialSenateRequiredStakePercentage: Get; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 293dc0238..7954e77c1 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -262,7 +262,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Normal, Pays::No))] pub fn become_delegate(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { - Self::do_become_delegate(origin, hotkey, Self::get_default_take()) + Self::do_become_delegate(origin, hotkey, Self::get_default_delegate_take()) } /// --- Allows delegates to decrease its take value. @@ -708,6 +708,84 @@ mod dispatches { Ok(()) } + /// Sets the childkey take for a given hotkey. + /// + /// This function allows a coldkey to set the childkey take for a given hotkey. + /// The childkey take determines the proportion of stake that the hotkey keeps for itself + /// when distributing stake to its children. + /// + /// # Arguments: + /// * `origin` (::RuntimeOrigin): + /// - The signature of the calling coldkey. Setting childkey take can only be done by the coldkey. + /// + /// * `hotkey` (T::AccountId): + /// - The hotkey for which the childkey take will be set. + /// + /// * `take` (u16): + /// - The new childkey take value. This is a percentage represented as a value between 0 and 10000, + /// where 10000 represents 100%. + /// + /// # Events: + /// * `ChildkeyTakeSet`: + /// - On successfully setting the childkey take for a hotkey. + /// + /// # Errors: + /// * `NonAssociatedColdKey`: + /// - The coldkey does not own the hotkey. + /// * `InvalidChildkeyTake`: + /// - The provided take value is invalid (greater than the maximum allowed take). + /// * `TxChildkeyTakeRateLimitExceeded`: + /// - The rate limit for changing childkey take has been exceeded. + /// + #[pallet::call_index(68)] + #[pallet::weight(( + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Normal, + Pays::Yes +))] + pub fn set_childkey_take( + origin: OriginFor, + hotkey: T::AccountId, + netuid: u16, + take: u16, + ) -> DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Call the utility function to set the childkey take + Self::do_set_childkey_take(coldkey, hotkey, netuid, take) + } + + /// Sets the transaction rate limit for changing childkey take. + /// + /// This function can only be called by the root origin. + /// + /// # Arguments: + /// * `origin` - The origin of the call, must be root. + /// * `tx_rate_limit` - The new rate limit in blocks. + /// + /// # Errors: + /// * `BadOrigin` - If the origin is not root. + /// + // TODO: Benchmark this call + #[pallet::call_index(69)] + #[pallet::weight(( + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No +))] + pub fn sudo_set_tx_childkey_take_rate_limit( + origin: OriginFor, + tx_rate_limit: u64, + ) -> DispatchResult { + ensure_root(origin)?; + Self::set_tx_childkey_take_rate_limit(tx_rate_limit); + Ok(()) + } + // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ // ================================== diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 156cbea56..4c5103fa2 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -168,5 +168,9 @@ mod errors { TooManyChildren, /// Default transaction rate limit exceeded. TxRateLimitExceeded, + /// Childkey take is invalid. + InvalidChildkeyTake, + /// Childkey take rate limit exceeded. + TxChildkeyTakeRateLimitExceeded, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b93b8296b..c9fbb0b44 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -83,6 +83,14 @@ mod events { TxRateLimitSet(u64), /// setting the delegate take transaction rate limit. TxDelegateTakeRateLimitSet(u64), + /// setting the childkey take transaction rate limit. + TxChildKeyTakeRateLimitSet(u64), + /// minimum childkey take set + MinChildKeyTakeSet(u16), + /// maximum childkey take set + MaxChildKeyTakeSet(u16), + /// childkey take set + ChildKeyTakeSet(T::AccountId, u16), /// a sudo call is done. Sudid(DispatchResult), /// registration is allowed/disallowed for a subnet. diff --git a/pallets/subtensor/src/staking/become_delegate.rs b/pallets/subtensor/src/staking/become_delegate.rs index 064f47c12..ccbdc44a2 100644 --- a/pallets/subtensor/src/staking/become_delegate.rs +++ b/pallets/subtensor/src/staking/become_delegate.rs @@ -58,9 +58,9 @@ impl Pallet { Error::::DelegateTxRateLimitExceeded ); - // --- 5.1 Ensure take is within the min ..= InitialDefaultTake (18%) range - let min_take = MinTake::::get(); - let max_take = MaxTake::::get(); + // --- 5.1 Ensure take is within the min ..= InitialDefaultDelegateTake (18%) range + let min_take = MinDelegateTake::::get(); + let max_take = MaxDelegateTake::::get(); ensure!(take >= min_take, Error::::DelegateTakeTooLow); ensure!(take <= max_take, Error::::DelegateTakeTooHigh); diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index 9e48bac91..6da669ca2 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -50,8 +50,8 @@ impl Pallet { ensure!(take < current_take, Error::::DelegateTakeTooLow); } - // --- 3.1 Ensure take is within the min ..= InitialDefaultTake (18%) range - let min_take = MinTake::::get(); + // --- 3.1 Ensure take is within the min ..= InitialDefaultDelegateTake (18%) range + let min_take = MinDelegateTake::::get(); ensure!(take >= min_take, Error::::DelegateTakeTooLow); // --- 4. Set the new take value. diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 486577712..7445bd154 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -218,6 +218,13 @@ impl Pallet { hotkey: &T::AccountId, increment: u64, ) { + log::debug!( + "Increasing stake: coldkey: {:?}, hotkey: {:?}, amount: {}", + coldkey, + hotkey, + increment + ); + TotalColdkeyStake::::insert( coldkey, TotalColdkeyStake::::get(coldkey).saturating_add(increment), diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index aa6dd443c..bc72bda4e 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -53,8 +53,8 @@ impl Pallet { ensure!(take > current_take, Error::::DelegateTakeTooLow); } - // --- 4. Ensure take is within the min ..= InitialDefaultTake (18%) range - let max_take = MaxTake::::get(); + // --- 4. Ensure take is within the min ..= InitialDefaultDelegateTake (18%) range + let max_take = MaxDelegateTake::::get(); ensure!(take <= max_take, Error::::DelegateTakeTooHigh); // --- 5. Enforce the rate limit (independently on do_add_stake rate limits) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index f413db23f..bfe5d1c1e 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -190,4 +190,102 @@ impl Pallet { pub fn get_parents(child: &T::AccountId, netuid: u16) -> Vec<(u64, T::AccountId)> { ParentKeys::::get(child, netuid) } + + /// Sets the childkey take for a given hotkey. + /// + /// This function allows a coldkey to set the childkey take for a given hotkey. + /// The childkey take determines the proportion of stake that the hotkey keeps for itself + /// when distributing stake to its children. + /// + /// # Arguments: + /// * `coldkey` (T::AccountId): + /// - The coldkey that owns the hotkey. + /// + /// * `hotkey` (T::AccountId): + /// - The hotkey for which the childkey take will be set. + /// + /// * `take` (u16): + /// - The new childkey take value. This is a percentage represented as a value between 0 and 10000, + /// where 10000 represents 100%. + /// + /// # Returns: + /// * `DispatchResult` - The result of the operation. + /// + /// # Errors: + /// * `NonAssociatedColdKey`: + /// - The coldkey does not own the hotkey. + /// * `InvalidChildkeyTake`: + /// - The provided take value is invalid (greater than the maximum allowed take). + /// * `TxChildkeyTakeRateLimitExceeded`: + /// - The rate limit for changing childkey take has been exceeded. + pub fn do_set_childkey_take( + coldkey: T::AccountId, + hotkey: T::AccountId, + netuid: u16, + take: u16, + ) -> DispatchResult { + // Ensure the coldkey owns the hotkey + ensure!( + Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::NonAssociatedColdKey + ); + + // Ensure the take value is valid + ensure!( + take <= Self::get_max_childkey_take(), + Error::::InvalidChildkeyTake + ); + + // Ensure the hotkey passes the rate limit + ensure!( + Self::passes_rate_limit_on_subnet(&TransactionType::SetChildkeyTake, &hotkey, netuid), + Error::::TxChildkeyTakeRateLimitExceeded + ); + + // Set the new childkey take value for the given hotkey and network + ChildkeyTake::::insert((hotkey.clone(), netuid), take); + + // TODO: Consider adding a check to ensure the hotkey is registered on the specified network (netuid) + // before setting the childkey take. This could prevent setting takes for non-existent or + // unregistered hotkeys. + + // NOTE: The childkey take is now associated with both the hotkey and the network ID. + // This allows for different take values across different networks for the same hotkey. + + // Update the last transaction block + let current_block: u64 = >::block_number() + .try_into() + .unwrap_or_else(|_| 0); + Self::set_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + current_block, + ); + + // Emit the event + Self::deposit_event(Event::ChildKeyTakeSet(hotkey.clone(), take)); + log::debug!( + "Childkey take set for hotkey: {:?} and take: {:?}", + hotkey, + take + ); + Ok(()) + } + + /// Gets the childkey take for a given hotkey. + /// + /// This function retrieves the current childkey take value for a specified hotkey. + /// If no specific take value has been set, it returns the default childkey take. + /// + /// # Arguments: + /// * `hotkey` (&T::AccountId): + /// - The hotkey for which to retrieve the childkey take. + /// + /// # Returns: + /// * `u16` - The childkey take value. This is a percentage represented as a value between 0 and 10000, + /// where 10000 represents 100%. + pub fn get_childkey_take(hotkey: &T::AccountId, netuid: u16) -> u16 { + ChildkeyTake::::get((hotkey, netuid)) + } } diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 2cd49e198..edbe02a36 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -11,6 +11,7 @@ use substrate_fixed::types::I32F32; #[derive(Copy, Clone)] pub enum TransactionType { SetChildren, + SetChildkeyTake, Unknown, } @@ -19,7 +20,8 @@ impl From for u16 { fn from(tx_type: TransactionType) -> Self { match tx_type { TransactionType::SetChildren => 0, - TransactionType::Unknown => 1, + TransactionType::SetChildkeyTake => 1, + TransactionType::Unknown => 2, } } } @@ -29,6 +31,7 @@ impl From for TransactionType { fn from(value: u16) -> Self { match value { 0 => TransactionType::SetChildren, + 1 => TransactionType::SetChildkeyTake, _ => TransactionType::Unknown, } } @@ -309,6 +312,7 @@ impl Pallet { pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { match tx_type { TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. + TransactionType::SetChildkeyTake => (TxChildkeyTakeRateLimit::::get()).into(), TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) } } @@ -319,10 +323,22 @@ impl Pallet { hotkey: &T::AccountId, netuid: u16, ) -> bool { - let block: u64 = Self::get_current_block_as_u64(); + let current_block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit(tx_type); let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); - block.saturating_sub(last_block) < limit + + log::info!( + "Rate limit check: current_block: {}, last_block: {}, limit: {}", + current_block, + last_block, + limit + ); + + if last_block == 0 { + true + } else { + current_block.saturating_sub(last_block) > limit + } } /// Check if a transaction should be rate limited globally @@ -393,17 +409,6 @@ impl Pallet { pub fn coinbase(amount: u64) { TotalIssuance::::put(TotalIssuance::::get().saturating_add(amount)); } - pub fn get_default_take() -> u16 { - // Default to maximum - MaxTake::::get() - } - pub fn set_max_take(default_take: u16) { - MaxTake::::put(default_take); - Self::deposit_event(Event::DefaultTakeSet(default_take)); - } - pub fn get_min_take() -> u16 { - MinTake::::get() - } pub fn set_subnet_locked_balance(netuid: u16, amount: u64) { SubnetLocked::::insert(netuid, amount); @@ -425,6 +430,11 @@ impl Pallet { TxRateLimit::::put(tx_rate_limit); Self::deposit_event(Event::TxRateLimitSet(tx_rate_limit)); } + + pub fn get_default_delegate_take() -> u16 { + // Default to maximum + MaxDelegateTake::::get() + } pub fn get_tx_delegate_take_rate_limit() -> u64 { TxDelegateTakeRateLimit::::get() } @@ -433,18 +443,42 @@ impl Pallet { Self::deposit_event(Event::TxDelegateTakeRateLimitSet(tx_rate_limit)); } pub fn set_min_delegate_take(take: u16) { - MinTake::::put(take); + MinDelegateTake::::put(take); Self::deposit_event(Event::MinDelegateTakeSet(take)); } pub fn set_max_delegate_take(take: u16) { - MaxTake::::put(take); + MaxDelegateTake::::put(take); Self::deposit_event(Event::MaxDelegateTakeSet(take)); } pub fn get_min_delegate_take() -> u16 { - MinTake::::get() + MinDelegateTake::::get() } pub fn get_max_delegate_take() -> u16 { - MaxTake::::get() + MaxDelegateTake::::get() + } + pub fn set_tx_childkey_take_rate_limit(tx_rate_limit: u64) { + TxChildkeyTakeRateLimit::::put(tx_rate_limit); + Self::deposit_event(Event::TxChildKeyTakeRateLimitSet(tx_rate_limit)); + } + + pub fn set_min_childkey_take(take: u16) { + MinChildkeyTake::::put(take); + Self::deposit_event(Event::MinChildKeyTakeSet(take)); + } + pub fn set_max_childkey_take(take: u16) { + MaxChildkeyTake::::put(take); + Self::deposit_event(Event::MaxChildKeyTakeSet(take)); + } + pub fn get_min_childkey_take() -> u16 { + MinChildkeyTake::::get() + } + pub fn get_max_childkey_take() -> u16 { + MaxChildkeyTake::::get() + } + + pub fn get_default_childkey_take() -> u16 { + // Default to maximum + MaxChildkeyTake::::get() } pub fn get_serving_rate_limit(netuid: u16) -> u64 { diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index e834baa85..c38005a53 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1,7 +1,7 @@ use crate::mock::*; -use frame_support::{assert_err, assert_ok}; +use frame_support::{assert_err, assert_noop, assert_ok}; mod mock; -use pallet_subtensor::*; +use pallet_subtensor::{utils::TransactionType, *}; use sp_core::U256; // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_success --exact --nocapture @@ -791,7 +791,184 @@ fn test_do_set_children_multiple_overwrite_existing() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_empty_list --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_functionality --exact --nocapture +#[test] +fn test_childkey_take_functionality() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Test default and max childkey take + let default_take = SubtensorModule::get_default_childkey_take(); + let max_take = SubtensorModule::get_max_childkey_take(); + log::info!("Default take: {}, Max take: {}", default_take, max_take); + + // Check if default take and max take are the same + assert_eq!( + default_take, max_take, + "Default take should be equal to max take" + ); + + // Log the actual value of MaxChildkeyTake + log::info!( + "MaxChildkeyTake value: {:?}", + MaxChildkeyTake::::get() + ); + + // Test setting childkey take + let new_take: u16 = max_take / 2; // 50% of max_take + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + new_take + )); + + // Verify childkey take was set correctly + let stored_take = SubtensorModule::get_childkey_take(&hotkey, netuid); + log::info!("Stored take: {}", stored_take); + assert_eq!(stored_take, new_take); + + // Test setting childkey take outside of allowed range + let invalid_take: u16 = max_take + 1; + assert_noop!( + SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + invalid_take + ), + Error::::InvalidChildkeyTake + ); + + // Test setting childkey take with non-associated coldkey + let non_associated_coldkey = U256::from(999); + assert_noop!( + SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(non_associated_coldkey), + hotkey, + netuid, + new_take + ), + Error::::NonAssociatedColdKey + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_rate_limiting --exact --nocapture +#[test] +fn test_childkey_take_rate_limiting() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid: u16 = 1; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set a rate limit for childkey take changes + let rate_limit: u64 = 100; + SubtensorModule::set_tx_childkey_take_rate_limit(rate_limit.into()); + + log::info!( + "TxChildkeyTakeRateLimit: {:?}", + TxChildkeyTakeRateLimit::::get() + ); + + // First transaction (should succeed) + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + 500 + )); + + let current_block = SubtensorModule::get_current_block_as_u64(); + let last_block = SubtensorModule::get_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + ); + log::info!( + "After first transaction: current_block: {}, last_block: {}", + current_block, + last_block + ); + + // Second transaction (should fail due to rate limit) + let result = + SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 600); + log::info!("Second transaction result: {:?}", result); + let current_block = SubtensorModule::get_current_block_as_u64(); + let last_block = SubtensorModule::get_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + ); + log::info!( + "After second transaction attempt: current_block: {}, last_block: {}", + current_block, + last_block + ); + + assert_noop!(result, Error::::TxChildkeyTakeRateLimitExceeded); + + // Advance the block number to just before the rate limit + run_to_block(rate_limit); + + // Third transaction (should still fail) + let result = + SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 650); + log::info!("Third transaction result: {:?}", result); + let current_block = SubtensorModule::get_current_block_as_u64(); + let last_block = SubtensorModule::get_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + ); + log::info!( + "After third transaction attempt: current_block: {}, last_block: {}", + current_block, + last_block + ); + + assert_noop!(result, Error::::TxChildkeyTakeRateLimitExceeded); + + // Advance the block number to just after the rate limit + run_to_block(rate_limit * 2); + + // Fourth transaction (should succeed) + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + 700 + )); + + let current_block = SubtensorModule::get_current_block_as_u64(); + let last_block = SubtensorModule::get_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + ); + log::info!( + "After fourth transaction: current_block: {}, last_block: {}", + current_block, + last_block + ); + + // Verify the final take was set + let stored_take = SubtensorModule::get_childkey_take(&hotkey, netuid); + assert_eq!(stored_take, 700); + }); +} + #[test] fn test_do_set_children_multiple_empty_list() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index d8d677006..4de74f4ab 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -131,12 +131,16 @@ parameter_types! { pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; - pub const InitialDefaultTake: u16 = 11_796; // 18%, same as in production + pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production + pub const InitialMinDelegateTake: u16 = 5_898; // 9%; + pub const InitialDefaultChildKeyTake: u16 = 11_796; // 18%, same as in production + pub const InitialMinChildKeyTake: u16 = 5_898; // 9%; pub const InitialMinTake: u16 =5_898; // 9%; pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing pub const InitialTxDelegateTakeRateLimit: u64 = 1; // 1 block take rate limit for testing + pub const InitialTxChildKeyTakeRateLimit: u64 = 1; // 1 block take rate limit for testing pub const InitialBurn: u64 = 0; pub const InitialMinBurn: u64 = 0; pub const InitialMaxBurn: u64 = 1_000_000_000; @@ -360,8 +364,11 @@ impl pallet_subtensor::Config for Test { type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; - type InitialDefaultTake = InitialDefaultTake; - type InitialMinTake = InitialMinTake; + type InitialDefaultDelegateTake = InitialDefaultDelegateTake; + type InitialMinDelegateTake = InitialMinDelegateTake; + type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; + type InitialMinChildKeyTake = InitialMinChildKeyTake; + type InitialTxChildKeyTakeRateLimit = InitialTxChildKeyTakeRateLimit; type InitialWeightsVersionKey = InitialWeightsVersionKey; type InitialMaxDifficulty = InitialMaxDifficulty; type InitialMinDifficulty = InitialMinDifficulty; diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 5bf95841a..f053c7ca6 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1395,7 +1395,7 @@ fn test_clear_small_nominations() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(cold1), hot1, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot1), cold1); @@ -1404,7 +1404,7 @@ fn test_clear_small_nominations() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(cold2), hot2, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot2), cold2); @@ -1697,11 +1697,11 @@ fn test_delegate_take_can_be_decreased() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); // Coldkey / hotkey 0 decreases take to 5%. This should fail as the minimum take is 9% @@ -1743,11 +1743,11 @@ fn test_can_set_min_take_ok() { assert_ok!(SubtensorModule::do_decrease_take( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); }); } @@ -1772,11 +1772,11 @@ fn test_delegate_take_can_not_be_increased_with_decrease_take() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); // Coldkey / hotkey 0 tries to increase take to 12.5% @@ -1790,7 +1790,7 @@ fn test_delegate_take_can_not_be_increased_with_decrease_take() { ); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); }); } @@ -1815,11 +1815,11 @@ fn test_delegate_take_can_be_increased() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); @@ -1854,11 +1854,11 @@ fn test_delegate_take_can_not_be_decreased_with_increase_take() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); // Coldkey / hotkey 0 tries to decrease take to 5% @@ -1872,12 +1872,12 @@ fn test_delegate_take_can_not_be_decreased_with_increase_take() { ); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); }); } -// Verify delegate take can be increased up to InitialDefaultTake (18%) +// Verify delegate take can be increased up to InitialDefaultDelegateTake (18%) #[test] fn test_delegate_take_can_be_increased_to_limit() { new_test_ext(1).execute_with(|| { @@ -1897,29 +1897,29 @@ fn test_delegate_take_can_be_increased_to_limit() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); - // Coldkey / hotkey 0 tries to increase take to InitialDefaultTake+1 + // Coldkey / hotkey 0 tries to increase take to InitialDefaultDelegateTake+1 assert_ok!(SubtensorModule::do_increase_take( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - InitialDefaultTake::get() + InitialDefaultDelegateTake::get() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - InitialDefaultTake::get() + InitialDefaultDelegateTake::get() ); }); } -// Verify delegate take can not be set above InitialDefaultTake +// Verify delegate take can not be set above InitialDefaultDelegateTake #[test] fn test_delegate_take_can_not_be_set_beyond_limit() { new_test_ext(1).execute_with(|| { @@ -1937,13 +1937,13 @@ fn test_delegate_take_can_not_be_set_beyond_limit() { let before = SubtensorModule::get_hotkey_take(&hotkey0); // Coldkey / hotkey 0 attempt to become delegates with take above maximum - // (Disable this check if InitialDefaultTake is u16::MAX) - if InitialDefaultTake::get() != u16::MAX { + // (Disable this check if InitialDefaultDelegateTake is u16::MAX) + if InitialDefaultDelegateTake::get() != u16::MAX { assert_eq!( SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - InitialDefaultTake::get() + 1 + InitialDefaultDelegateTake::get() + 1 ), Err(Error::::DelegateTakeTooHigh.into()) ); @@ -1952,7 +1952,7 @@ fn test_delegate_take_can_not_be_set_beyond_limit() { }); } -// Verify delegate take can not be increased above InitialDefaultTake (18%) +// Verify delegate take can not be increased above InitialDefaultDelegateTake (18%) #[test] fn test_delegate_take_can_not_be_increased_beyond_limit() { new_test_ext(1).execute_with(|| { @@ -1972,28 +1972,28 @@ fn test_delegate_take_can_not_be_increased_beyond_limit() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); - // Coldkey / hotkey 0 tries to increase take to InitialDefaultTake+1 - // (Disable this check if InitialDefaultTake is u16::MAX) - if InitialDefaultTake::get() != u16::MAX { + // Coldkey / hotkey 0 tries to increase take to InitialDefaultDelegateTake+1 + // (Disable this check if InitialDefaultDelegateTake is u16::MAX) + if InitialDefaultDelegateTake::get() != u16::MAX { assert_eq!( SubtensorModule::do_increase_take( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - InitialDefaultTake::get() + 1 + InitialDefaultDelegateTake::get() + 1 ), Err(Error::::DelegateTakeTooHigh.into()) ); } assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); }); } @@ -2018,11 +2018,11 @@ fn test_rate_limits_enforced_on_increase_take() { assert_ok!(SubtensorModule::do_become_delegate( <::RuntimeOrigin>::signed(coldkey0), hotkey0, - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() )); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); // Coldkey / hotkey 0 increases take to 12.5% @@ -2036,7 +2036,7 @@ fn test_rate_limits_enforced_on_increase_take() { ); assert_eq!( SubtensorModule::get_hotkey_take(&hotkey0), - SubtensorModule::get_min_take() + SubtensorModule::get_min_delegate_take() ); step_block(1 + InitialTxDelegateTakeRateLimit::get() as u16); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 66951b7fc..1fecf7dbc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -858,7 +858,9 @@ parameter_types! { pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. - pub const SubtensorInitialMinTake: u16 = 5_898; // 9% + pub const SubtensorInitialMinDelegateTake: u16 = 5_898; // 9% + pub const SubtensorInitialDefaultChildKeyTake: u16 = 11_796; // 18% honest number. + pub const SubtensorInitialMinChildKeyTake: u16 = 5_898; // 9% pub const SubtensorInitialWeightsVersionKey: u64 = 0; pub const SubtensorInitialMinDifficulty: u64 = 10_000_000; pub const SubtensorInitialMaxDifficulty: u64 = u64::MAX / 4; @@ -868,6 +870,7 @@ parameter_types! { pub const SubtensorInitialMaxBurn: u64 = 100_000_000_000; // 100 tao pub const SubtensorInitialTxRateLimit: u64 = 1000; pub const SubtensorInitialTxDelegateTakeRateLimit: u64 = 216000; // 30 days at 12 seconds per block + pub const SubtensorInitialTxChildKeyTakeRateLimit: u64 = 216000; // 30 days at 12 seconds per block pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; @@ -914,8 +917,10 @@ impl pallet_subtensor::Config for Runtime { type InitialMaxRegistrationsPerBlock = SubtensorInitialMaxRegistrationsPerBlock; type InitialPruningScore = SubtensorInitialPruningScore; type InitialMaxAllowedValidators = SubtensorInitialMaxAllowedValidators; - type InitialDefaultTake = SubtensorInitialDefaultTake; - type InitialMinTake = SubtensorInitialMinTake; + type InitialDefaultDelegateTake = SubtensorInitialDefaultTake; + type InitialDefaultChildKeyTake = SubtensorInitialDefaultChildKeyTake; + type InitialMinDelegateTake = SubtensorInitialMinDelegateTake; + type InitialMinChildKeyTake = SubtensorInitialMinChildKeyTake; type InitialWeightsVersionKey = SubtensorInitialWeightsVersionKey; type InitialMaxDifficulty = SubtensorInitialMaxDifficulty; type InitialMinDifficulty = SubtensorInitialMinDifficulty; @@ -925,6 +930,7 @@ impl pallet_subtensor::Config for Runtime { type InitialMinBurn = SubtensorInitialMinBurn; type InitialTxRateLimit = SubtensorInitialTxRateLimit; type InitialTxDelegateTakeRateLimit = SubtensorInitialTxDelegateTakeRateLimit; + type InitialTxChildKeyTakeRateLimit = SubtensorInitialTxChildKeyTakeRateLimit; type InitialRAORecycledForRegistration = SubtensorInitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = SubtensorInitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = SubtensorInitialNetworkImmunity; diff --git a/scripts/test_specific.sh b/scripts/test_specific.sh index 018872d33..c5b940f0f 100755 --- a/scripts/test_specific.sh +++ b/scripts/test_specific.sh @@ -3,4 +3,4 @@ features="${4:-pow-faucet}" # RUST_LOG="pallet_subtensor=info" cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact -RUST_LOG=INFO cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file +RUST_LOG=DEBUG cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file From 9917c03357f5632ca1e9ff9510a6ccdcf28772cb Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 19:22:23 +0400 Subject: [PATCH 076/269] chore: lints --- pallets/subtensor/src/staking/set_children.rs | 2 +- pallets/subtensor/src/utils.rs | 2 +- pallets/subtensor/tests/children.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index bfe5d1c1e..c62d8f130 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -255,7 +255,7 @@ impl Pallet { // Update the last transaction block let current_block: u64 = >::block_number() .try_into() - .unwrap_or_else(|_| 0); + .unwrap_or(0); Self::set_last_transaction_block( &hotkey, netuid, diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index edbe02a36..7f70de0fe 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -312,7 +312,7 @@ impl Pallet { pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { match tx_type { TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. - TransactionType::SetChildkeyTake => (TxChildkeyTakeRateLimit::::get()).into(), + TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) } } diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index c38005a53..8dfeedc29 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -874,7 +874,7 @@ fn test_childkey_take_rate_limiting() { // Set a rate limit for childkey take changes let rate_limit: u64 = 100; - SubtensorModule::set_tx_childkey_take_rate_limit(rate_limit.into()); + SubtensorModule::set_tx_childkey_take_rate_limit(rate_limit); log::info!( "TxChildkeyTakeRateLimit: {:?}", From b656a583b563bb55f24d6536f4377f27669c0159 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 20:16:02 +0400 Subject: [PATCH 077/269] feat: benchmarks --- pallets/subtensor/src/benchmarks.rs | 30 +++++++++++++++++++++- pallets/subtensor/src/macros/dispatches.rs | 13 +++++----- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 03e087a92..25176dab7 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -159,7 +159,7 @@ benchmarks! { Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), wallet_bal); assert_ok!(Subtensor::::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone())); - assert_ok!(Subtensor::::do_become_delegate(RawOrigin::Signed(coldkey.clone()).into(), hotkey.clone(), Subtensor::::get_default_take())); + assert_ok!(Subtensor::::do_become_delegate(RawOrigin::Signed(coldkey.clone()).into(), hotkey.clone(), Subtensor::::get_default_delegate_take())); // Stake 10% of our current total staked TAO let u64_staked_amt = 100_000_000_000; @@ -429,4 +429,32 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) + benchmark_sudo_set_tx_childkey_take_rate_limit { + // We don't need to set up any initial state for this benchmark + // as it's a simple setter function that only requires root origin + let new_rate_limit: u64 = 100; +}: sudo_set_tx_childkey_take_rate_limit(RawOrigin::Root, new_rate_limit) + +benchmark_set_childkey_take { + // Setup + let netuid: u16 = 1; + let tempo: u16 = 1; + let seed: u32 = 1; + let coldkey: T::AccountId = account("Cold", 0, seed); + let hotkey: T::AccountId = account("Hot", 0, seed); + let take: u16 = 1000; // 10% in basis points + + // Initialize the network + Subtensor::::init_new_network(netuid, tempo); + + // Register the hotkey + Subtensor::::set_burn(netuid, 1); + let amount_to_be_staked = 1_000_000u32.into(); + Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked); + assert_ok!(Subtensor::::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone())); + + // Ensure the coldkey owns the hotkey + // Subtensor::::set_coldkey_ownership(coldkey.clone(), hotkey.clone(), true); + +}: set_childkey_take(RawOrigin::Signed(coldkey), hotkey, netuid, take) } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7954e77c1..26cba6abd 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -739,9 +739,9 @@ mod dispatches { /// #[pallet::call_index(68)] #[pallet::weight(( - Weight::from_parts(10_000_000, 0) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)), + Weight::from_parts(34_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::Yes ))] @@ -757,6 +757,8 @@ mod dispatches { Self::do_set_childkey_take(coldkey, hotkey, netuid, take) } + // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ + /// Sets the transaction rate limit for changing childkey take. /// /// This function can only be called by the root origin. @@ -771,8 +773,7 @@ mod dispatches { // TODO: Benchmark this call #[pallet::call_index(69)] #[pallet::weight(( - Weight::from_parts(10_000_000, 0) - .saturating_add(T::DbWeight::get().reads(1)) + Weight::from_parts(6_000, 0) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Operational, Pays::No @@ -786,8 +787,6 @@ mod dispatches { Ok(()) } - // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ - // ================================== // ==== Parameter Sudo calls ======== // ================================== From ee8aa6b52cafb3b0d51899e767f9612a37767029 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 20:16:19 +0400 Subject: [PATCH 078/269] chore: remove todos --- pallets/subtensor/src/macros/dispatches.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 26cba6abd..95dec13d3 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -770,7 +770,6 @@ mod dispatches { /// # Errors: /// * `BadOrigin` - If the origin is not root. /// - // TODO: Benchmark this call #[pallet::call_index(69)] #[pallet::weight(( Weight::from_parts(6_000, 0) From c1dbc59c3f0e6d8fea4555a4fc5590edc091a821 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 20:17:13 +0400 Subject: [PATCH 079/269] chore: remove more comments --- pallets/subtensor/src/benchmarks.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 25176dab7..0a251894e 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -452,9 +452,5 @@ benchmark_set_childkey_take { let amount_to_be_staked = 1_000_000u32.into(); Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked); assert_ok!(Subtensor::::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone())); - - // Ensure the coldkey owns the hotkey - // Subtensor::::set_coldkey_ownership(coldkey.clone(), hotkey.clone(), true); - }: set_childkey_take(RawOrigin::Signed(coldkey), hotkey, netuid, take) } From 01cabbdc033f9455aa30ae14ccba2fa507327cd7 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 20:25:27 +0400 Subject: [PATCH 080/269] chore: multiple network child key takes --- pallets/subtensor/tests/children.rs | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 8dfeedc29..68e13e56e 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -969,6 +969,58 @@ fn test_childkey_take_rate_limiting() { }); } +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_multiple_networks_childkey_take --exact --nocapture +#[test] +fn test_multiple_networks_childkey_take() { + new_test_ext(1).execute_with(|| { + const NUM_NETWORKS: u16 = 10; + let coldkey = U256::from(1); + let hotkey = U256::from(2); + + // Create 10 networks and set up neurons (skip network 0) + for netuid in 1..NUM_NETWORKS { + // Add network + add_network(netuid, 13, 0); + + // Register neuron + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set a unique childkey take value for each network + let take_value = (netuid as u16 + 1) * 1000; // Values will be 1000, 2000, ..., 10000 + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + take_value + )); + + // Verify the childkey take was set correctly + let stored_take = SubtensorModule::get_childkey_take(&hotkey, netuid); + assert_eq!( + stored_take, take_value, + "Childkey take not set correctly for network {}", + netuid + ); + + // Log the set value + log::info!("Network {}: Childkey take set to {}", netuid, take_value); + } + + // Verify all networks have different childkey take values + for i in 1..NUM_NETWORKS { + for j in (i + 1)..NUM_NETWORKS { + let take_i = SubtensorModule::get_childkey_take(&hotkey, i); + let take_j = SubtensorModule::get_childkey_take(&hotkey, j); + assert_ne!( + take_i, take_j, + "Childkey take values should be different for networks {} and {}", + i, j + ); + } + } + }); +} + #[test] fn test_do_set_children_multiple_empty_list() { new_test_ext(1).execute_with(|| { From f7f243cef46dc6245eaa6d60e99a8a59ccedeee1 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Tue, 30 Jul 2024 20:30:26 +0400 Subject: [PATCH 081/269] chore: clippy --- pallets/subtensor/tests/children.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 68e13e56e..147a8edae 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -986,7 +986,7 @@ fn test_multiple_networks_childkey_take() { register_ok_neuron(netuid, hotkey, coldkey, 0); // Set a unique childkey take value for each network - let take_value = (netuid as u16 + 1) * 1000; // Values will be 1000, 2000, ..., 10000 + let take_value = (netuid + 1) * 1000; // Values will be 1000, 2000, ..., 10000 assert_ok!(SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), hotkey, From e0ff0739f374f1bc35f8d4b27f12efc04e74f4f3 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:31:47 -0700 Subject: [PATCH 082/269] custom errors signed extension --- pallets/subtensor/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f7824e2a3..ff4297ae2 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2425,7 +2425,7 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Call.into()) + Err(InvalidTransaction::Custom(1).into()) } } Some(Call::reveal_weights { netuid, .. }) => { @@ -2437,7 +2437,7 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Call.into()) + Err(InvalidTransaction::Custom(2).into()) } } Some(Call::set_weights { netuid, .. }) => { @@ -2449,7 +2449,7 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Call.into()) + Err(InvalidTransaction::Custom(3).into()) } } Some(Call::set_root_weights { netuid, hotkey, .. }) => { @@ -2461,7 +2461,7 @@ where ..Default::default() }) } else { - Err(InvalidTransaction::Call.into()) + Err(InvalidTransaction::Custom(4).into()) } } Some(Call::add_stake { .. }) => Ok(ValidTransaction { @@ -2480,7 +2480,7 @@ where if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) { // If the registration limit for the interval is exceeded, reject the transaction - return InvalidTransaction::ExhaustsResources.into(); + return Err(InvalidTransaction::Custom(5).into()); } Ok(ValidTransaction { priority: Self::get_priority_vanilla(), @@ -2493,7 +2493,7 @@ where }), Some(Call::dissolve_network { .. }) => { if Pallet::::coldkey_in_arbitration(who) { - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + Err(InvalidTransaction::Custom(6).into()) } else { Ok(ValidTransaction { priority: Self::get_priority_vanilla(), From 18879258d792d3b8597f4fef3d5b9701386c69c3 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:47:46 -0700 Subject: [PATCH 083/269] update tests --- pallets/subtensor/tests/registration.rs | 4 ++-- pallets/subtensor/tests/weights.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index bd95ae3b1..74a493e04 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -276,7 +276,7 @@ fn test_registration_rate_limit_exceeded() { let result = extension.validate(&who, &call.into(), &info, 10); // Expectation: The transaction should be rejected - assert_err!(result, InvalidTransaction::ExhaustsResources); + assert_err!(result, InvalidTransaction::Custom(5)); let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); assert!(current_registrants <= max_registrants); @@ -362,7 +362,7 @@ fn test_burned_registration_rate_limit_exceeded() { // Expectation: The transaction should be rejected assert_err!( burned_register_result, - InvalidTransaction::ExhaustsResources + InvalidTransaction::Custom(5) ); let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 2344bd425..88b8aaf25 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -109,7 +109,7 @@ fn test_set_rootweights_validate() { assert_err!( // Should get an invalid transaction error result_no_stake, - TransactionValidityError::Invalid(InvalidTransaction::Call,) + TransactionValidityError::Invalid(InvalidTransaction::Custom(4)) ); // Increase the stake to be equal to the minimum @@ -209,7 +209,7 @@ fn test_commit_weights_validate() { assert_err!( // Should get an invalid transaction error result_no_stake, - TransactionValidityError::Invalid(InvalidTransaction::Call,) + TransactionValidityError::Invalid(InvalidTransaction::Custom(1)) ); // Increase the stake to be equal to the minimum @@ -308,7 +308,7 @@ fn test_reveal_weights_validate() { assert_err!( // Should get an invalid transaction error result_no_stake, - TransactionValidityError::Invalid(InvalidTransaction::Call,) + TransactionValidityError::Invalid(InvalidTransaction::Custom(2)) ); // Increase the stake to be equal to the minimum From 75e7299f96907f51a04a4e27dae6d717df19361e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:58:24 -0700 Subject: [PATCH 084/269] clippy --- pallets/collective/src/lib.rs | 6 +++--- pallets/subtensor/tests/difficulty.rs | 1 - pallets/subtensor/tests/epoch.rs | 1 - pallets/subtensor/tests/mock.rs | 4 ++++ pallets/subtensor/tests/neuron_info.rs | 1 - pallets/subtensor/tests/registration.rs | 1 - pallets/subtensor/tests/serving.rs | 2 -- pallets/subtensor/tests/staking.rs | 3 --- pallets/subtensor/tests/weights.rs | 3 --- 9 files changed, 7 insertions(+), 15 deletions(-) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 96040f99c..6aae3c85e 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,9 +951,9 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. - /// Two removals, one mutation. - /// Computation and i/o `O(P)` where: - /// - `P` is number of active proposals + /// - two removals, one mutation. + /// - computation and i/o `O(P)` where: + /// - `P` is number of active proposals fn do_approve_proposal( seats: MemberCount, yes_votes: MemberCount, diff --git a/pallets/subtensor/tests/difficulty.rs b/pallets/subtensor/tests/difficulty.rs index 05238bc43..c3023b829 100644 --- a/pallets/subtensor/tests/difficulty.rs +++ b/pallets/subtensor/tests/difficulty.rs @@ -5,7 +5,6 @@ mod mock; use sp_core::U256; #[test] -#[cfg(not(tarpaulin))] fn test_registration_difficulty_adjustment() { new_test_ext(1).execute_with(|| { // Create Net 1 diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 676b3cd35..e2b911525 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2107,7 +2107,6 @@ fn test_zero_weights() { // Test that epoch assigns validator permits to highest stake uids, varies uid interleaving and stake values. #[test] -#[cfg(not(tarpaulin))] fn test_validator_permits() { let netuid: u16 = 1; let tempo: u16 = u16::MAX - 1; // high tempo to skip automatic epochs in on_initialize, use manual epochs instead diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index fc784f46f..3b4ee8de8 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -262,6 +262,7 @@ impl CollectiveInterface for TriumvirateVotes { } // We call pallet_collective TriumvirateCollective +#[allow(dead_code)] type TriumvirateCollective = pallet_collective::Instance1; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -279,6 +280,7 @@ impl pallet_collective::Config for Test { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -295,6 +297,7 @@ impl pallet_membership::Config for Test { // This is a dummy collective instance for managing senate members // Probably not the best solution, but fastest implementation +#[allow(dead_code)] type SenateCollective = pallet_collective::Instance2; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -312,6 +315,7 @@ impl pallet_collective::Config for Test { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/pallets/subtensor/tests/neuron_info.rs b/pallets/subtensor/tests/neuron_info.rs index 10df1c07d..3494fdc5f 100644 --- a/pallets/subtensor/tests/neuron_info.rs +++ b/pallets/subtensor/tests/neuron_info.rs @@ -15,7 +15,6 @@ fn test_get_neuron_none() { } #[test] -#[cfg(not(tarpaulin))] fn test_get_neuron_some() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 74a493e04..8c1c6e6bd 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -539,7 +539,6 @@ fn test_burn_adjustment() { } #[test] -#[cfg(not(tarpaulin))] fn test_registration_too_many_registrations_per_block() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 41e9888cc..b87b7fd10 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -161,7 +161,6 @@ fn test_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_axon_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); @@ -379,7 +378,6 @@ fn test_prometheus_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] fn test_prometheus_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index cb2cac4ef..12d299d8f 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -22,7 +22,6 @@ use sp_runtime::traits::SignedExtension; ************************************************************/ #[test] -#[cfg(not(tarpaulin))] fn test_add_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); @@ -528,7 +527,6 @@ fn test_remove_stake_rate_limit_exceeded() { } #[test] -#[cfg(not(tarpaulin))] fn test_remove_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); @@ -1201,7 +1199,6 @@ fn test_delegate_stake_division_by_zero_check() { } #[test] -#[cfg(not(tarpaulin))] fn test_full_with_delegating() { new_test_ext(1).execute_with(|| { let netuid = 1; diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 88b8aaf25..c87f818b2 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -21,7 +21,6 @@ use substrate_fixed::types::I32F32; // Test the call passes through the subtensor module. #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -41,7 +40,6 @@ fn test_set_weights_dispatch_info_ok() { }); } #[test] -#[cfg(not(tarpaulin))] fn test_set_rootweights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -404,7 +402,6 @@ fn test_weights_err_no_validator_permit() { // To execute this test: cargo test --package pallet-subtensor --test weights test_set_weights_min_stake_failed -- --nocapture` #[test] -#[cfg(not(tarpaulin))] fn test_set_weights_min_stake_failed() { new_test_ext(0).execute_with(|| { let dests = vec![0]; From 38ad5169256aafe913b2389bb8ce5e925362edc6 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:01:30 -0700 Subject: [PATCH 085/269] fmt --- pallets/subtensor/tests/registration.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 8c1c6e6bd..4d235cb7e 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -360,10 +360,7 @@ fn test_burned_registration_rate_limit_exceeded() { extension.validate(&who, &call_burned_register.into(), &info, 10); // Expectation: The transaction should be rejected - assert_err!( - burned_register_result, - InvalidTransaction::Custom(5) - ); + assert_err!(burned_register_result, InvalidTransaction::Custom(5)); let current_registrants = SubtensorModule::get_registrations_this_interval(netuid); assert!(current_registrants <= max_registrants); From 826e55c1f26322adc3f84dd3b74207de5795f84b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:35:52 -0700 Subject: [PATCH 086/269] add custom error 7 & fix clippy --- pallets/subtensor/src/lib.rs | 18 ++++++++---------- pallets/subtensor/tests/staking.rs | 6 ++---- runtime/src/lib.rs | 2 ++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ff4297ae2..f5d70362f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2403,16 +2403,14 @@ where _len: usize, ) -> TransactionValidity { // Check if the call is one of the balance transfer types we want to reject - if let Some(balances_call) = call.is_sub_type() { - match balances_call { - BalancesCall::transfer_allow_death { .. } - | BalancesCall::transfer_keep_alive { .. } - | BalancesCall::transfer_all { .. } => { - if Pallet::::coldkey_in_arbitration(who) { - return Err(TransactionValidityError::Invalid(InvalidTransaction::Call)); - } - } - _ => {} // Other Balances calls are allowed + if let Some( + BalancesCall::transfer_allow_death { .. } + | BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. }, + ) = call.is_sub_type() + { + if Pallet::::coldkey_in_arbitration(who) { + return Err(InvalidTransaction::Custom(7).into()); } } match call.is_sub_type() { diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 12d299d8f..fff6749fc 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,9 +1,7 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::arithmetic_side_effects)] -use frame_support::pallet_prelude::{ - InvalidTransaction, TransactionValidity, TransactionValidityError, -}; +use frame_support::pallet_prelude::{InvalidTransaction, TransactionValidity}; use frame_support::traits::{OnFinalize, OnIdle, OnInitialize}; use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; @@ -3876,7 +3874,7 @@ fn test_transfer_coldkey_in_arbitration() { assert_eq!( validate_transaction(&coldkey_account_id, &call), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + Err(InvalidTransaction::Custom(7).into()) ); }); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..303e7040f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -516,6 +516,7 @@ impl pallet_collective::Config for Runtime { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -531,6 +532,7 @@ impl pallet_membership::Config for Runtime { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; From 0b5e24d0687d7a1aaef277208cae81b77a18b883 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 00:16:42 +0400 Subject: [PATCH 087/269] chore: scheduler tests --- Cargo.lock | 3 + pallets/subtensor/src/macros/dispatches.rs | 9 +- pallets/subtensor/src/macros/errors.rs | 6 + pallets/subtensor/src/macros/events.rs | 2 +- pallets/subtensor/tests/swap_coldkey.rs | 194 +++++++++++++++++++++ runtime/src/lib.rs | 2 +- runtime/tests/pallet_proxy.rs | 2 +- 7 files changed, 214 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a50f8a12..ea30f4e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4976,6 +4976,7 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-scheduler", "pallet-subtensor", "parity-scale-codec", "scale-info", @@ -4983,6 +4984,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-tracing 16.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3)", "sp-weights", "substrate-fixed", @@ -5264,6 +5266,7 @@ dependencies = [ "pallet-balances", "pallet-collective", "pallet-membership", + "pallet-scheduler", "pallet-transaction-payment", "pallet-utility", "parity-scale-codec", diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f5599adae..6a88d44ca 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -987,7 +987,14 @@ mod dispatches { 63, frame_system::RawOrigin::Root.into(), Bounded::Lookup { hash, len }, - )?; + ) + .map_err(|_| Error::::FailedToSchedule)?; + // Emit the SwapScheduled event + Self::deposit_event(Event::ColdkeySwapScheduled { + old_coldkey: who.clone(), + new_coldkey: new_coldkey.clone(), + execution_block: when, + }); Ok(().into()) } diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 156cbea56..d51469482 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -168,5 +168,11 @@ mod errors { TooManyChildren, /// Default transaction rate limit exceeded. TxRateLimitExceeded, + /// Swap coldkey only callable by root. + SwapColdkeyOnlyCallableByRoot, + /// Swap already scheduled. + SwapAlreadyScheduled, + /// failed to swap coldkey + FailedToSchedule, } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b93b8296b..b2c14ae76 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -164,7 +164,7 @@ mod events { /// The account ID of the new coldkey new_coldkey: T::AccountId, /// The arbitration block for the coldkey swap - arbitration_block: u64, + execution_block: BlockNumberFor, }, /// The arbitration period has been extended ArbitrationPeriodExtended { diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 9203e2b3f..6ea0bb542 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -4,10 +4,16 @@ use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; mod mock; +use frame_support::error::BadOrigin; +use frame_support::traits::schedule::v3::Named as ScheduleNamed; +use frame_support::traits::schedule::DispatchTime; +use frame_support::traits::OnInitialize; use mock::*; use pallet_subtensor::*; +use pallet_subtensor::{Call, Error}; use sp_core::H256; use sp_core::U256; +use sp_runtime::DispatchError; // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture #[test] @@ -1286,3 +1292,191 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, coldkey), 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); + + // Add balance to the old coldkey account + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000); + + // 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, + } + .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); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 2000); + + 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::::FailedToSchedule + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_schedule_swap_coldkey_execution --exact --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 = 1u16; + let stake_amount = 100; + + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, old_coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(old_coldkey), + hotkey, + stake_amount + )); + + // Check initial ownership + assert_eq!( + Owner::::get(hotkey), + old_coldkey, + "Initial ownership check failed" + ); + + // 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 blocks_in_5_days = 5 * 24 * 60 * 60 / 12; + let execution_block = current_block + blocks_in_5_days; + + println!("Current block: {}", current_block); + println!("Execution block: {}", execution_block); + + // Fast forward to the execution block + // System::set_block_number(execution_block); + run_to_block(execution_block); + + // Run on_initialize for the execution block + SubtensorModule::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); + // println!("New owner after swap: {:?}", new_owner); + // assert_eq!( + // new_owner, new_coldkey, + // "Ownership was not updated as expected" + // ); + + // assert_eq!( + // Stake::::get(hotkey, new_coldkey), + // stake_amount, + // "Stake was not transferred to new coldkey" + // ); + // assert_eq!( + // Stake::::get(hotkey, old_coldkey), + // 0, + // "Old coldkey still has stake" + // ); + + // Check for the SwapExecuted event + System::assert_last_event( + Event::ColdkeySwapScheduled { + old_coldkey, + new_coldkey, + execution_block, + } + .into(), + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_direct_swap_coldkey_call_fails --exact --nocapture +#[test] +fn test_direct_swap_coldkey_call_fails() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + assert_noop!( + SubtensorModule::swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + ), + 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_schedule_swap_coldkey_with_pending_swap() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey1 = U256::from(2); + let new_coldkey2 = U256::from(3); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 2000); + + 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::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey2 + ), + Error::::SwapAlreadyScheduled + ); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 289e9effe..d6699e759 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -144,7 +144,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: 191, + spec_version: 192, impl_version: 1, apis: RUNTIME_API_VERSIONS, diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs index 192893c1e..796dfc471 100644 --- a/runtime/tests/pallet_proxy.rs +++ b/runtime/tests/pallet_proxy.rs @@ -4,7 +4,7 @@ use codec::Encode; use frame_support::{assert_ok, traits::InstanceFilter, BoundedVec}; use node_subtensor_runtime::{ AccountId, BalancesCall, BuildStorage, Proxy, ProxyType, Runtime, RuntimeCall, RuntimeEvent, - RuntimeGenesisConfig, RuntimeOrigin, SchedulerCall, SubtensorModule, System, SystemCall, + RuntimeGenesisConfig, RuntimeOrigin, SubtensorModule, System, SystemCall, }; const ACCOUNT: [u8; 32] = [1_u8; 32]; From 06f1b9d44504a501492f6a4c30d8d148ab4edf94 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 00:17:30 +0400 Subject: [PATCH 088/269] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1fecf7dbc..4f10474c8 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 191, + spec_version: 192, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 850caf681d85e3bb4f7b32eee0c496392dcf160f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 01:29:29 +0400 Subject: [PATCH 089/269] chore: lints --- pallets/subtensor/tests/children.rs | 1 - pallets/subtensor/tests/coinbase.rs | 8 ++++---- pallets/subtensor/tests/difficulty.rs | 2 +- pallets/subtensor/tests/epoch.rs | 2 +- pallets/subtensor/tests/mock.rs | 4 ++++ pallets/subtensor/tests/neuron_info.rs | 2 +- pallets/subtensor/tests/registration.rs | 2 +- pallets/subtensor/tests/serving.rs | 4 ++-- pallets/subtensor/tests/staking.rs | 4 ++-- pallets/subtensor/tests/weights.rs | 6 +++--- runtime/src/lib.rs | 1 + 11 files changed, 20 insertions(+), 16 deletions(-) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 9ad07e1e7..e834baa85 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -297,7 +297,6 @@ fn test_do_set_child_singular_multiple_children() { // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_add_singular_child --exact --nocapture #[test] -#[cfg(not(tarpaulin))] fn test_add_singular_child() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/coinbase.rs b/pallets/subtensor/tests/coinbase.rs index 8fd963dff..a6c1acde1 100644 --- a/pallets/subtensor/tests/coinbase.rs +++ b/pallets/subtensor/tests/coinbase.rs @@ -6,7 +6,7 @@ use sp_core::U256; // Test the ability to hash all sorts of hotkeys. #[test] -#[cfg(not(tarpaulin))] + fn test_hotkey_hashing() { new_test_ext(1).execute_with(|| { for i in 0..10000 { @@ -18,7 +18,7 @@ fn test_hotkey_hashing() { // Test drain tempo on hotkeys. // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_hotkey_drain_time -- --nocapture #[test] -#[cfg(not(tarpaulin))] + fn test_hotkey_drain_time() { new_test_ext(1).execute_with(|| { // Block 0 @@ -46,7 +46,7 @@ fn test_hotkey_drain_time() { // To run this test specifically, use the following command: // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_coinbase_basic -- --nocapture #[test] -#[cfg(not(tarpaulin))] + fn test_coinbase_basic() { new_test_ext(1).execute_with(|| { // Define network ID @@ -138,7 +138,7 @@ fn test_coinbase_basic() { // Test getting and setting hotkey emission tempo // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_set_and_get_hotkey_emission_tempo -- --nocapture #[test] -#[cfg(not(tarpaulin))] + fn test_set_and_get_hotkey_emission_tempo() { new_test_ext(1).execute_with(|| { // Get the default hotkey emission tempo diff --git a/pallets/subtensor/tests/difficulty.rs b/pallets/subtensor/tests/difficulty.rs index 05238bc43..b9524de57 100644 --- a/pallets/subtensor/tests/difficulty.rs +++ b/pallets/subtensor/tests/difficulty.rs @@ -5,7 +5,7 @@ mod mock; use sp_core::U256; #[test] -#[cfg(not(tarpaulin))] + fn test_registration_difficulty_adjustment() { new_test_ext(1).execute_with(|| { // Create Net 1 diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 526a58b4e..53a825fee 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2107,7 +2107,7 @@ fn test_zero_weights() { // Test that epoch assigns validator permits to highest stake uids, varies uid interleaving and stake values. #[test] -#[cfg(not(tarpaulin))] + fn test_validator_permits() { let netuid: u16 = 1; let tempo: u16 = u16::MAX - 1; // high tempo to skip automatic epochs in on_initialize, use manual epochs instead diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 6f75fcb1a..5503b8190 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -266,6 +266,7 @@ impl CollectiveInterface for TriumvirateVotes { } // We call pallet_collective TriumvirateCollective +#[allow(dead_code)] type TriumvirateCollective = pallet_collective::Instance1; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -283,6 +284,7 @@ impl pallet_collective::Config for Test { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -299,6 +301,7 @@ impl pallet_membership::Config for Test { // This is a dummy collective instance for managing senate members // Probably not the best solution, but fastest implementation +#[allow(dead_code)] type SenateCollective = pallet_collective::Instance2; impl pallet_collective::Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -316,6 +319,7 @@ impl pallet_collective::Config for Test { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/pallets/subtensor/tests/neuron_info.rs b/pallets/subtensor/tests/neuron_info.rs index 10df1c07d..4375979bd 100644 --- a/pallets/subtensor/tests/neuron_info.rs +++ b/pallets/subtensor/tests/neuron_info.rs @@ -15,7 +15,7 @@ fn test_get_neuron_none() { } #[test] -#[cfg(not(tarpaulin))] + fn test_get_neuron_some() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index bd95ae3b1..f951971f5 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -539,7 +539,7 @@ fn test_burn_adjustment() { } #[test] -#[cfg(not(tarpaulin))] + fn test_registration_too_many_registrations_per_block() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 41e9888cc..8232a936d 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -161,7 +161,7 @@ fn test_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] + fn test_axon_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); @@ -379,7 +379,7 @@ fn test_prometheus_serving_set_metadata_update() { } #[test] -#[cfg(not(tarpaulin))] + fn test_prometheus_serving_rate_limit_exceeded() { new_test_ext(1).execute_with(|| { let hotkey_account_id = U256::from(1); diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 2952426a9..409348180 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -15,7 +15,7 @@ use sp_core::{H256, U256}; ************************************************************/ #[test] -#[cfg(not(tarpaulin))] + fn test_add_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); @@ -521,7 +521,7 @@ fn test_remove_stake_rate_limit_exceeded() { } #[test] -#[cfg(not(tarpaulin))] + fn test_remove_stake_dispatch_info_ok() { new_test_ext(1).execute_with(|| { let hotkey = U256::from(0); diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 2344bd425..f56b5e038 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -21,7 +21,7 @@ use substrate_fixed::types::I32F32; // Test the call passes through the subtensor module. #[test] -#[cfg(not(tarpaulin))] + fn test_set_weights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -41,7 +41,7 @@ fn test_set_weights_dispatch_info_ok() { }); } #[test] -#[cfg(not(tarpaulin))] + fn test_set_rootweights_dispatch_info_ok() { new_test_ext(0).execute_with(|| { let dests = vec![1, 1]; @@ -404,7 +404,7 @@ fn test_weights_err_no_validator_permit() { // To execute this test: cargo test --package pallet-subtensor --test weights test_set_weights_min_stake_failed -- --nocapture` #[test] -#[cfg(not(tarpaulin))] + fn test_set_weights_min_stake_failed() { new_test_ext(0).execute_with(|| { let dests = vec![0]; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d6699e759..000e3d7a1 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -522,6 +522,7 @@ impl pallet_collective::Config for Runtime { } // We call council members Triumvirate +#[allow(dead_code)] type TriumvirateMembership = pallet_membership::Instance1; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; From d8acb8bc10fa6148f188c15d76a88fdf7a7f32f2 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 01:40:10 +0400 Subject: [PATCH 090/269] feat: isabella question --- pallets/subtensor/tests/children.rs | 96 +++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 147a8edae..9db206c06 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1487,3 +1487,99 @@ fn test_children_stake_values() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_parents_chain --exact --nocapture +#[test] +fn test_get_parents_chain() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let coldkey = U256::from(1); + let num_keys: usize = 5; + let proportion = u64::MAX / 2; // 50% stake allocation + + log::info!("Test setup: netuid={}, coldkey={}, num_keys={}, proportion={}", netuid, coldkey, num_keys, proportion); + + // Create a vector of hotkeys + let hotkeys: Vec = (0..num_keys).map(|i| U256::from(i as u64 + 2)).collect(); + log::info!("Created hotkeys: {:?}", hotkeys); + + // Add network + add_network(netuid, 13, 0); + SubtensorModule::set_max_registrations_per_block(netuid, 1000); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000); + log::info!("Network added and parameters set: netuid={}", netuid); + + // Register all neurons + for hotkey in &hotkeys { + register_ok_neuron(netuid, *hotkey, coldkey, 0); + log::info!("Registered neuron: hotkey={}, coldkey={}, netuid={}", hotkey, coldkey, netuid); + } + + // Set up parent-child relationships + for i in 0..num_keys - 1 { + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkeys[i], + netuid, + vec![(proportion, hotkeys[i + 1])] + )); + log::info!("Set parent-child relationship: parent={}, child={}, proportion={}", hotkeys[i], hotkeys[i + 1], proportion); + } + + // Test get_parents for each hotkey + for i in 1..num_keys { + let parents = SubtensorModule::get_parents(&hotkeys[i], netuid); + log::info!("Testing get_parents for hotkey {}: {:?}", hotkeys[i], parents); + assert_eq!( + parents.len(), + 1, + "Hotkey {} should have exactly one parent", + i + ); + assert_eq!( + parents[0], + (proportion, hotkeys[i - 1]), + "Incorrect parent for hotkey {}", + i + ); + } + + // Test get_parents for the root (should be empty) + let root_parents = SubtensorModule::get_parents(&hotkeys[0], netuid); + log::info!("Testing get_parents for root hotkey {}: {:?}", hotkeys[0], root_parents); + assert!( + root_parents.is_empty(), + "Root hotkey should have no parents" + ); + + // Test multiple parents + let last_hotkey = hotkeys[num_keys - 1]; + let new_parent = U256::from(num_keys as u64 + 2); + register_ok_neuron(netuid, new_parent, coldkey, 0); + log::info!("Registered new parent neuron: new_parent={}, coldkey={}, netuid={}", new_parent, coldkey, netuid); + + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + new_parent, + netuid, + vec![(proportion / 2, last_hotkey)] + )); + log::info!("Set additional parent-child relationship: parent={}, child={}, proportion={}", new_parent, last_hotkey, proportion / 2); + + let last_hotkey_parents = SubtensorModule::get_parents(&last_hotkey, netuid); + log::info!("Testing get_parents for last hotkey {} with multiple parents: {:?}", last_hotkey, last_hotkey_parents); + assert_eq!( + last_hotkey_parents.len(), + 2, + "Last hotkey should have two parents" + ); + assert!( + last_hotkey_parents.contains(&(proportion, hotkeys[num_keys - 2])), + "Last hotkey should still have its original parent" + ); + assert!( + last_hotkey_parents.contains(&(proportion / 2, new_parent)), + "Last hotkey should have the new parent" + ); + }); +} From d790ab394446f41086417e0ab8489c747b529290 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 02:58:47 +0400 Subject: [PATCH 091/269] fix: hotkey swap delegates --- pallets/subtensor/src/swap/swap_hotkey.rs | 11 +++---- pallets/subtensor/tests/swap_hotkey.rs | 35 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index fb3c33e4d..f39948339 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -200,11 +200,12 @@ impl Pallet { // 8. Swap delegates. // Delegates( hotkey ) -> take value -- the hotkey delegate take value. - let old_delegate_take = Delegates::::get(old_hotkey); - Delegates::::remove(old_hotkey); // Remove the old delegate take. - Delegates::::insert(new_hotkey, old_delegate_take); // Insert the new delegate take. - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - + if Delegates::::contains_key(old_hotkey) { + let old_delegate_take = Delegates::::get(old_hotkey); + Delegates::::remove(old_hotkey); + Delegates::::insert(new_hotkey, old_delegate_take); + weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2)); + } // 9. Swap all subnet specific info. let all_netuids: Vec = Self::get_all_subnet_netuids(); for netuid in all_netuids { diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index c6a05f2b6..94abf73ea 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -959,3 +959,38 @@ fn test_swap_hotkey_error_cases() { assert_eq!(Balances::free_balance(coldkey), initial_balance - swap_cost); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_becomes_delegate --exact --nocapture +#[test] +fn test_swap_hotkey_becomes_delegate() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let swap_cost = 1_000_000_000u64 * 2; + let delegate_take = 10u16; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + + // Ensure old_hotkey is not a delegate + assert!(!Delegates::::contains_key(old_hotkey)); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey + )); + + // Check that old_hotkey is no longer a delegate + assert!(!Delegates::::contains_key(old_hotkey)); + + // Check that new_hotkey is now a delegate with the correct take value + assert!(!Delegates::::contains_key(new_hotkey)); + }); +} From 50179b5604b91e6be6f9f131c9a9b7bdc1600494 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:12:42 +0400 Subject: [PATCH 092/269] Update pallets/subtensor/tests/swap_hotkey.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap_hotkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 94abf73ea..417917af4 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -987,7 +987,7 @@ fn test_swap_hotkey_becomes_delegate() { &new_hotkey )); - // Check that old_hotkey is no longer a delegate + // Check that old_hotkey is still not a delegate assert!(!Delegates::::contains_key(old_hotkey)); // Check that new_hotkey is now a delegate with the correct take value From 6cbd4d07d9baff5cbe6ced6e3c95b00d2b11c833 Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:12:51 +0400 Subject: [PATCH 093/269] Update pallets/subtensor/tests/swap_hotkey.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap_hotkey.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 417917af4..83ee58c2b 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -960,9 +960,9 @@ fn test_swap_hotkey_error_cases() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_becomes_delegate --exact --nocapture +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_does_not_become_delegate --exact --nocapture #[test] -fn test_swap_hotkey_becomes_delegate() { +fn test_swap_hotkey_does_not_become_delegate() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; let tempo: u16 = 13; From bdb3e176b8f8ddeada52197edcdbdaca1273069d Mon Sep 17 00:00:00 2001 From: distributedstatemachine <112424909+distributedstatemachine@users.noreply.github.com> Date: Wed, 31 Jul 2024 03:12:59 +0400 Subject: [PATCH 094/269] Update pallets/subtensor/tests/swap_hotkey.rs Co-authored-by: Cameron Fairchild --- pallets/subtensor/tests/swap_hotkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 83ee58c2b..2cadc01fe 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -990,7 +990,7 @@ fn test_swap_hotkey_does_not_become_delegate() { // Check that old_hotkey is still not a delegate assert!(!Delegates::::contains_key(old_hotkey)); - // Check that new_hotkey is now a delegate with the correct take value + // Check that new_hotkey is NOT a delegate either assert!(!Delegates::::contains_key(new_hotkey)); }); } From 883799fb0dd23c3bc1a33ff0c120a7fbc8993cda Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 10:02:33 +0400 Subject: [PATCH 095/269] chore: additional tests --- pallets/subtensor/tests/swap_hotkey.rs | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index 2cadc01fe..ab8f08302 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -994,3 +994,130 @@ fn test_swap_hotkey_does_not_become_delegate() { assert!(!Delegates::::contains_key(new_hotkey)); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey -- test_swap_hotkey_with_delegate_and_stakes --exact --nocapture +#[test] +fn test_swap_hotkey_with_delegate_and_stakes() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let staker1 = U256::from(4); + let staker2 = U256::from(5); + let swap_cost = 1_000_000_000u64; + let delegate_take = 11_796; + let stake_amount1 = 100u64; + let stake_amount2 = 200u64; + + // Setup initial state + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, old_hotkey, coldkey, 0); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, swap_cost); + SubtensorModule::add_balance_to_coldkey_account(&staker1, stake_amount1); + SubtensorModule::add_balance_to_coldkey_account(&staker2, stake_amount2); + + // Make old_hotkey a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(coldkey), + old_hotkey, + )); + + // Add stakes to the delegate + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(staker1), + old_hotkey, + stake_amount1 + )); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(staker2), + old_hotkey, + stake_amount2 + )); + + // Assert initial stake amounts + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), + stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2) + ); + + // Print entire staking hotkeys map + log::info!( + "StakingHotkeys before swap: {:?}", + StakingHotkeys::::iter().collect::>() + ); + + // Perform the swap + assert_ok!(SubtensorModule::do_swap_hotkey( + <::RuntimeOrigin>::signed(coldkey), + &old_hotkey, + &new_hotkey + )); + + // Check that new_hotkey is now a delegate with the same take + assert_eq!(Delegates::::get(new_hotkey), delegate_take); + assert!(!Delegates::::contains_key(old_hotkey)); + + // Check that stakes have been transferred to the new hotkey + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2) + ); + + assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), 0); + + // Check that the total stake for the new hotkey is correct + assert_eq!( + TotalHotkeyStake::::get(new_hotkey), + stake_amount1 + stake_amount2 - (ExistentialDeposit::get() * 2) + ); + assert!(!TotalHotkeyStake::::contains_key(old_hotkey)); + + // Print entire staking hotkeys map + log::info!( + "StakingHotkeys after swap: {:?}", + StakingHotkeys::::iter().collect::>() + ); + + // Check that the staking hotkeys for the stakers have been updated + assert!(StakingHotkeys::::get(staker1).contains(&new_hotkey)); + assert!(StakingHotkeys::::get(staker2).contains(&new_hotkey)); + assert!(!StakingHotkeys::::get(staker1).contains(&old_hotkey)); + assert!(!StakingHotkeys::::get(staker2).contains(&old_hotkey)); + + // Check staking hotkeys for new hotkey + // Retrieve all stakers associated with the new hotkey + let new_hotkey_stakers = StakingHotkeys::::iter() + // Iterate through all entries in the StakingHotkeys storage map + .filter(|(_, hotkeys)| hotkeys.contains(&new_hotkey)) + // Keep only entries where the new_hotkey is in the list of hotkeys + .map(|(staker, _)| staker) + // Extract just the staker (coldkey) from each matching entry + .collect::>(); + // Collect the results into a vector + + log::info!("new_hotkey_stakers: {:?}", new_hotkey_stakers); + + assert_eq!(new_hotkey_stakers.len(), 2); + assert!(new_hotkey_stakers.contains(&staker1)); + assert!(new_hotkey_stakers.contains(&staker2)); + + // Verify that old_hotkey is not in any staker's StakingHotkeys + let old_hotkey_stakers = StakingHotkeys::::iter() + .filter(|(_, hotkeys)| hotkeys.contains(&old_hotkey)) + .count(); + + assert_eq!(old_hotkey_stakers, 0); + + // Check that the total balances of stakers haven't changed + assert_eq!( + SubtensorModule::get_coldkey_balance(&staker1), + ExistentialDeposit::get() + ); + assert_eq!( + SubtensorModule::get_coldkey_balance(&staker2), + ExistentialDeposit::get() + ); + }); +} From c61782ffd7828d4eb44a3bd59f2477fc7c57d265 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 10:04:56 +0400 Subject: [PATCH 096/269] chore: bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 66951b7fc..49c9bb786 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 191, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 3d4d57155c7a0a996cc32912b72a40b375ea5947 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 31 Jul 2024 15:18:19 +0800 Subject: [PATCH 097/269] start the unit test fix --- pallets/subtensor/src/lib.rs | 4 ++++ pallets/subtensor/src/macros/dispatches.rs | 6 ++++++ pallets/subtensor/src/swap/swap_coldkey.rs | 5 ++++- pallets/subtensor/tests/swap_coldkey.rs | 17 +++++++++-------- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f5febb0aa..321e58b4a 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -691,6 +691,10 @@ pub mod pallet { pub type OwnedHotkeys = StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + #[pallet::storage] // --- DMAP ( cold ) --> () | Maps coldkey to if a coldkey swap is scheduled. + pub type ColdkeySwapScheduled = + StorageMap<_, Blake2_128Concat, T::AccountId, (), ValueQuery>; + /// ============================ /// ==== Global Parameters ===== /// ============================ diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 6a88d44ca..348f57f73 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -953,6 +953,10 @@ mod dispatches { new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + ensure!( + !ColdkeySwapScheduled::::contains_key(&who), + Error::::SwapAlreadyScheduled + ); // Calculate the number of blocks in 5 days let blocks_in_5_days: u32 = 5 * 24 * 60 * 60 / 12; @@ -989,6 +993,8 @@ mod dispatches { Bounded::Lookup { hash, len }, ) .map_err(|_| Error::::FailedToSchedule)?; + + ColdkeySwapScheduled::::insert(&who, ()); // Emit the SwapScheduled event Self::deposit_event(Event::ColdkeySwapScheduled { old_coldkey: who.clone(), diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index bd8a11fa1..b47b0ac95 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -74,7 +74,10 @@ impl Pallet { Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); weight.saturating_accrue(T::DbWeight::get().writes(1)); - // 10. Emit the ColdkeySwapped event + // 10. Remove the coldkey swap scheduled record + ColdkeySwapScheduled::::remove(&old_coldkey); + + // 11. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 6ea0bb542..8c2bb2330 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1395,6 +1395,15 @@ fn test_schedule_swap_coldkey_execution() { println!("Current block: {}", current_block); println!("Execution block: {}", execution_block); + System::assert_last_event( + Event::ColdkeySwapScheduled { + old_coldkey, + new_coldkey, + execution_block, + } + .into(), + ); + // Fast forward to the execution block // System::set_block_number(execution_block); run_to_block(execution_block); @@ -1427,14 +1436,6 @@ fn test_schedule_swap_coldkey_execution() { // ); // Check for the SwapExecuted event - System::assert_last_event( - Event::ColdkeySwapScheduled { - old_coldkey, - new_coldkey, - execution_block, - } - .into(), - ); }); } From 5d48fae1b40c919db94ee7e5ce38080c300cf959 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 12:03:16 +0400 Subject: [PATCH 098/269] chore: PR review comments --- pallets/subtensor/src/lib.rs | 11 +- pallets/subtensor/src/staking/set_children.rs | 9 +- pallets/subtensor/tests/children.rs | 106 ++++++++++++++++-- runtime/src/lib.rs | 4 +- scripts/test_specific.sh | 4 +- 5 files changed, 117 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c58c6a0c6..89aa76bb7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -628,8 +628,15 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, T::AccountId, u16, ValueQuery, DefaultDelegateTake>; #[pallet::storage] /// DMAP ( hot, netuid ) --> take | Returns the hotkey childkey take for a specific subnet - pub type ChildkeyTake = - StorageMap<_, Blake2_128Concat, (T::AccountId, u16), u16, ValueQuery>; + pub type ChildkeyTake = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, // First key: hotkey + Identity, + u16, // Second key: netuid + u16, // Value: take + ValueQuery, + >; #[pallet::storage] /// DMAP ( hot, cold ) --> stake | Returns the stake under a coldkey prefixed by hotkey. diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index c62d8f130..f60298d57 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -130,6 +130,11 @@ impl Pallet { // --- 7.1. Insert my new children + proportion list into the map. ChildKeys::::insert(hotkey.clone(), netuid, children.clone()); + if children.is_empty() { + // If there are no children, remove the ChildkeyTake value + ChildkeyTake::::remove(hotkey.clone(), netuid); + } + // --- 7.2. Update the parents list for my new children. for (proportion, new_child_i) in children.clone().iter() { // --- 8.2.1. Get the child's parents on this network. @@ -243,7 +248,7 @@ impl Pallet { ); // Set the new childkey take value for the given hotkey and network - ChildkeyTake::::insert((hotkey.clone(), netuid), take); + ChildkeyTake::::insert(hotkey.clone(), netuid, take); // TODO: Consider adding a check to ensure the hotkey is registered on the specified network (netuid) // before setting the childkey take. This could prevent setting takes for non-existent or @@ -286,6 +291,6 @@ impl Pallet { /// * `u16` - The childkey take value. This is a percentage represented as a value between 0 and 10000, /// where 10000 represents 100%. pub fn get_childkey_take(hotkey: &T::AccountId, netuid: u16) -> u16 { - ChildkeyTake::::get((hotkey, netuid)) + ChildkeyTake::::get(hotkey, netuid) } } diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 9db206c06..0025dd24a 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1497,7 +1497,13 @@ fn test_get_parents_chain() { let num_keys: usize = 5; let proportion = u64::MAX / 2; // 50% stake allocation - log::info!("Test setup: netuid={}, coldkey={}, num_keys={}, proportion={}", netuid, coldkey, num_keys, proportion); + log::info!( + "Test setup: netuid={}, coldkey={}, num_keys={}, proportion={}", + netuid, + coldkey, + num_keys, + proportion + ); // Create a vector of hotkeys let hotkeys: Vec = (0..num_keys).map(|i| U256::from(i as u64 + 2)).collect(); @@ -1512,7 +1518,12 @@ fn test_get_parents_chain() { // Register all neurons for hotkey in &hotkeys { register_ok_neuron(netuid, *hotkey, coldkey, 0); - log::info!("Registered neuron: hotkey={}, coldkey={}, netuid={}", hotkey, coldkey, netuid); + log::info!( + "Registered neuron: hotkey={}, coldkey={}, netuid={}", + hotkey, + coldkey, + netuid + ); } // Set up parent-child relationships @@ -1523,13 +1534,22 @@ fn test_get_parents_chain() { netuid, vec![(proportion, hotkeys[i + 1])] )); - log::info!("Set parent-child relationship: parent={}, child={}, proportion={}", hotkeys[i], hotkeys[i + 1], proportion); + log::info!( + "Set parent-child relationship: parent={}, child={}, proportion={}", + hotkeys[i], + hotkeys[i + 1], + proportion + ); } // Test get_parents for each hotkey for i in 1..num_keys { let parents = SubtensorModule::get_parents(&hotkeys[i], netuid); - log::info!("Testing get_parents for hotkey {}: {:?}", hotkeys[i], parents); + log::info!( + "Testing get_parents for hotkey {}: {:?}", + hotkeys[i], + parents + ); assert_eq!( parents.len(), 1, @@ -1546,7 +1566,11 @@ fn test_get_parents_chain() { // Test get_parents for the root (should be empty) let root_parents = SubtensorModule::get_parents(&hotkeys[0], netuid); - log::info!("Testing get_parents for root hotkey {}: {:?}", hotkeys[0], root_parents); + log::info!( + "Testing get_parents for root hotkey {}: {:?}", + hotkeys[0], + root_parents + ); assert!( root_parents.is_empty(), "Root hotkey should have no parents" @@ -1556,7 +1580,12 @@ fn test_get_parents_chain() { let last_hotkey = hotkeys[num_keys - 1]; let new_parent = U256::from(num_keys as u64 + 2); register_ok_neuron(netuid, new_parent, coldkey, 0); - log::info!("Registered new parent neuron: new_parent={}, coldkey={}, netuid={}", new_parent, coldkey, netuid); + log::info!( + "Registered new parent neuron: new_parent={}, coldkey={}, netuid={}", + new_parent, + coldkey, + netuid + ); assert_ok!(SubtensorModule::do_set_children( RuntimeOrigin::signed(coldkey), @@ -1564,10 +1593,19 @@ fn test_get_parents_chain() { netuid, vec![(proportion / 2, last_hotkey)] )); - log::info!("Set additional parent-child relationship: parent={}, child={}, proportion={}", new_parent, last_hotkey, proportion / 2); + log::info!( + "Set additional parent-child relationship: parent={}, child={}, proportion={}", + new_parent, + last_hotkey, + proportion / 2 + ); let last_hotkey_parents = SubtensorModule::get_parents(&last_hotkey, netuid); - log::info!("Testing get_parents for last hotkey {} with multiple parents: {:?}", last_hotkey, last_hotkey_parents); + log::info!( + "Testing get_parents for last hotkey {} with multiple parents: {:?}", + last_hotkey, + last_hotkey_parents + ); assert_eq!( last_hotkey_parents.len(), 2, @@ -1583,3 +1621,55 @@ fn test_get_parents_chain() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_removal_on_empty_children --exact --nocapture +#[test] +fn test_childkey_take_removal_on_empty_children() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child = U256::from(3); + let netuid: u16 = 1; + let proportion: u64 = 1000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set a child and childkey take + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![(proportion, child)] + )); + + let take: u16 = u16::MAX / 10; // 10% take + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + take + )); + + // Verify childkey take is set + assert_eq!(SubtensorModule::get_childkey_take(&hotkey, netuid), take); + + // Remove all children + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + vec![] + )); + + // Verify children are removed + let children = SubtensorModule::get_children(&hotkey, netuid); + assert!(children.is_empty()); + + // Verify childkey take is removed + assert_eq!(SubtensorModule::get_childkey_take(&hotkey, netuid), 0); + // Verify childkey take storage is empty + assert_eq!(ChildkeyTake::::get(hotkey, netuid), 0); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4f10474c8..20d337d64 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -858,8 +858,8 @@ parameter_types! { pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. - pub const SubtensorInitialMinDelegateTake: u16 = 5_898; // 9% - pub const SubtensorInitialDefaultChildKeyTake: u16 = 11_796; // 18% honest number. + pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take + pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take pub const SubtensorInitialMinChildKeyTake: u16 = 5_898; // 9% pub const SubtensorInitialWeightsVersionKey: u64 = 0; pub const SubtensorInitialMinDifficulty: u64 = 10_000_000; diff --git a/scripts/test_specific.sh b/scripts/test_specific.sh index c5b940f0f..85f3ebe30 100755 --- a/scripts/test_specific.sh +++ b/scripts/test_specific.sh @@ -1,6 +1,4 @@ pallet="${3:-pallet-subtensor}" features="${4:-pow-faucet}" -# RUST_LOG="pallet_subtensor=info" cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact - -RUST_LOG=DEBUG cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file +SKIP_WASM_BUILD=1 RUST_LOG=DEBUG cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file From b1037ab0ff7566ccaea79679d482b19a1def8a18 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 12:09:40 +0400 Subject: [PATCH 099/269] chore: clippy --- pallets/subtensor/tests/children.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 0025dd24a..2a15d05d6 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1,3 +1,4 @@ +#![allow(clippy::indexing_slicing)] use crate::mock::*; use frame_support::{assert_err, assert_noop, assert_ok}; mod mock; From 531417a2788947fbdfc7773e7ae92fda8d211362 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 12:11:44 +0400 Subject: [PATCH 100/269] chore: bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 20d337d64..c7187e0f4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 192, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From beadf9f51b1a54c472c3afa6a29a51145878c4bd Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 31 Jul 2024 08:49:30 -0700 Subject: [PATCH 101/269] add missing validate tests --- pallets/subtensor/tests/root.rs | 114 +++++++++++++++++++++++++++-- pallets/subtensor/tests/weights.rs | 60 +++++++++++++++ 2 files changed, 169 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 7c6622670..9e5cdbb3b 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -1,12 +1,16 @@ #![allow(clippy::indexing_slicing, clippy::unwrap_used)] use crate::mock::*; -use frame_support::{assert_err, assert_ok}; -use frame_system::Config; -use frame_system::{EventRecord, Phase}; -use pallet_subtensor::migration; -use pallet_subtensor::Error; +use frame_support::{assert_err, assert_ok, dispatch::DispatchInfo}; +use frame_system::{Config, EventRecord, Phase}; +use pallet_subtensor::{ + migration, BaseDifficulty, ColdkeySwapDestinations, Error, MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, +}; use sp_core::{Get, H256, U256}; +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, +}; mod mock; @@ -974,3 +978,103 @@ fn test_dissolve_network_does_not_exist_err() { ); }); } + +#[test] +fn test_dissolve_network_validate() { + fn generate_valid_pow(coldkey: &U256, block_number: u64, difficulty: U256) -> (H256, u64) { + let mut nonce: u64 = 0; + loop { + let work = SubtensorModule::create_seal_hash(block_number, nonce, coldkey); + if SubtensorModule::hash_meets_difficulty(&work, difficulty) { + return (work, nonce); + } + nonce += 1; + } + } + // Testing the signed extension validate function + // correctly filters the `dissolve_network` transaction. + + new_test_ext(0).execute_with(|| { + let netuid: u16 = 1; + let delegate_coldkey = U256::from(1); + let delegate_hotkey = U256::from(2); + let new_coldkey = U256::from(3); + let current_block = 0u64; + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); + + // Make delegate a delegate + assert_ok!(SubtensorModule::become_delegate( + RuntimeOrigin::signed(delegate_coldkey), + delegate_hotkey + )); + + // Add more than 500 TAO of stake to the delegate's hotkey + let stake_amount = 501_000_000_000; // 501 TAO in RAO + let delegator = U256::from(4); + SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(delegator), + delegate_hotkey, + stake_amount + )); + + // Ensure the delegate's coldkey has less than minimum balance + assert!( + SubtensorModule::get_coldkey_balance(&delegate_coldkey) + < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, + "Delegate coldkey balance should be less than minimum required" + ); + + // Ensure the delegate's hotkey has more than 500 TAO delegated + assert!( + SubtensorModule::get_total_delegated_stake(&delegate_coldkey) >= 500_000_000_000, + "Delegate hotkey should have at least 500 TAO delegated" + ); + + // Generate valid PoW + let (work, nonce) = generate_valid_pow( + &delegate_coldkey, + current_block, + U256::from(4) * U256::from(BaseDifficulty::::get()), + ); + + // Schedule coldkey swap + assert_ok!(SubtensorModule::do_schedule_coldkey_swap( + &delegate_coldkey, + &new_coldkey, + work.to_fixed_bytes().to_vec(), + current_block, + nonce, + )); + + // Verify that the swap was scheduled + assert_eq!( + ColdkeySwapDestinations::::get(delegate_coldkey), + vec![new_coldkey] + ); + + // Check if the coldkey is in arbitration + assert!( + SubtensorModule::coldkey_in_arbitration(&delegate_coldkey), + "Delegate coldkey should be in arbitration after swap" + ); + + let who = delegate_coldkey; // The coldkey signs this transaction + let call = RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { netuid }); + + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + + let extension = pallet_subtensor::SubtensorSignedExtension::::new(); + + // Submit to the signed extension validate function + let result_in_arbitration = extension.validate(&who, &call.clone(), &info, 10); + // Should fail with InvalidTransaction::Custom(6) because coldkey is in arbitration + assert_err!( + result_in_arbitration, + TransactionValidityError::Invalid(InvalidTransaction::Custom(6)) + ); + }); +} diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index c87f818b2..214e3add0 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -259,6 +259,66 @@ fn test_reveal_weights_dispatch_info_ok() { }); } +#[test] +fn test_set_weights_validate() { + // Testing the signed extension validate function + // correctly filters the `set_weights` transaction. + + new_test_ext(0).execute_with(|| { + let netuid: u16 = 1; + let coldkey = U256::from(0); + let hotkey: U256 = U256::from(1); + assert_ne!(hotkey, coldkey); + + let who = hotkey; // The hotkey signs this transaction + + let call = RuntimeCall::SubtensorModule(SubtensorCall::set_weights { + netuid, + dests: vec![1, 1], + weights: vec![1, 1], + version_key: 0, + }); + + // Create netuid + add_network(netuid, 0, 0); + // Register the hotkey + SubtensorModule::append_neuron(netuid, &hotkey, 0); + Owner::::insert(hotkey, coldkey); + + let min_stake = 500_000_000_000; + // Set the minimum stake + SubtensorModule::set_weights_min_stake(min_stake); + + // Verify stake is less than minimum + assert!(SubtensorModule::get_total_stake_for_hotkey(&hotkey) < min_stake); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + + let extension = pallet_subtensor::SubtensorSignedExtension::::new(); + // Submit to the signed extension validate function + let result_no_stake = extension.validate(&who, &call.clone(), &info, 10); + // Should fail due to insufficient stake + assert_err!( + result_no_stake, + TransactionValidityError::Invalid(InvalidTransaction::Custom(3)) + ); + + // Increase the stake to be equal to the minimum + SubtensorModule::increase_stake_on_hotkey_account(&hotkey, min_stake); + + // Verify stake is equal to minimum + assert_eq!( + SubtensorModule::get_total_stake_for_hotkey(&hotkey), + min_stake + ); + + // Submit to the signed extension validate function + let result_min_stake = extension.validate(&who, &call.clone(), &info, 10); + // Now the call should pass + assert_ok!(result_min_stake); + }); +} + #[test] fn test_reveal_weights_validate() { // Testing the signed extension validate function From ce31915e301825bc6769396bba4aa7bcf0ec7c2b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:55:10 -0700 Subject: [PATCH 102/269] add delegate id --- docs/delegate-info.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/delegate-info.json b/docs/delegate-info.json index bda41b48e..46355da3d 100644 --- a/docs/delegate-info.json +++ b/docs/delegate-info.json @@ -390,5 +390,12 @@ "url": "https://cortex.foundation/", "description": "Cortex Foundation is committed to advancing the integration of decentralized AI. Our validator is designed for transparency, reliability, and community engagement.", "signature": "7a6274ff6b0f7ddca97e37ef4a9b90781012ff3cf7baa3159f6feaafc43c557975aad324ea608d6b8abeb21f8f3ca2595e54b81a7564574d0242b803d969618a" + }, + { + "address":"5F27Eqz2PhyMtGMEce898x31DokNqRVxkm5AhDDe6rDGNvoY", + "name": "Love", + "url": "https://love.cosimo.fund", + "description": "Love validator exists to accelerate open source AI and be good stewards of the Bittensorr network", + "signature": "c221a3de3be031c149a7be912b3b75e0355605f041dc975153302b23b4d93e45e9cc7453532491e92076ccd333a4c1f95f4a2229aae8f4fcfb88e5dec3f14c87" } ] \ No newline at end of file From cab0fc34f19d184aed8ff548b4f8d01dc18e3ade Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 22:18:58 +0400 Subject: [PATCH 103/269] chore: fix tests --- pallets/subtensor/tests/swap_hotkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap_hotkey.rs b/pallets/subtensor/tests/swap_hotkey.rs index ab8f08302..b416e6b4d 100644 --- a/pallets/subtensor/tests/swap_hotkey.rs +++ b/pallets/subtensor/tests/swap_hotkey.rs @@ -1099,7 +1099,7 @@ fn test_swap_hotkey_with_delegate_and_stakes() { log::info!("new_hotkey_stakers: {:?}", new_hotkey_stakers); - assert_eq!(new_hotkey_stakers.len(), 2); + assert_eq!(new_hotkey_stakers.len(), 3); assert!(new_hotkey_stakers.contains(&staker1)); assert!(new_hotkey_stakers.contains(&staker2)); From ad827b4620a3e8736b7437dba31a08df690bddcb Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 31 Jul 2024 19:04:45 -0700 Subject: [PATCH 104/269] add swap & tests --- pallets/subtensor/src/macros/errors.rs | 4 + pallets/subtensor/src/swap/swap_coldkey.rs | 15 +- pallets/subtensor/src/utils/identity.rs | 28 ++++ pallets/subtensor/tests/serving.rs | 180 ++++++++++++++++++++- 4 files changed, 221 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 07710dc5f..d3e228ec5 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -170,5 +170,9 @@ mod errors { TxRateLimitExceeded, /// Invalid identity. InvalidIdentity, + /// The old coldkey does not have an identity. + OldColdkeyNotFound, + /// The new coldkey already has an identity. + NewColdkeyInUse, } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index bd8a11fa1..082114d94 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -64,23 +64,28 @@ impl Pallet { Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); - // 7. Update the weight for the balance operations + // 7. Swap identity if the old coldkey has one. + if Identities::::contains_key(&old_coldkey) { + Self::swap_delegate_identity_coldkey(&old_coldkey, new_coldkey)?; + } + + // 8. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 8. Perform the actual coldkey swap + // 9. Perform the actual coldkey swap let _ = Self::perform_swap_coldkey(&old_coldkey, new_coldkey, &mut weight); - // 9. Update the last transaction block for the new coldkey + // 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)); - // 10. Emit the ColdkeySwapped event + // 11. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), }); - // 11. Return the result with the updated weight + // 12. Return the result with the updated weight Ok(Some(weight).into()) } diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 1c9c3c25d..11d3677d1 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -106,4 +106,32 @@ impl Pallet { && identity.description.len() <= 1024 && identity.additional.len() <= 1024 } + + /// Swaps the hotkey of a delegate identity from an old account ID to a new account ID. + /// + /// # Parameters + /// - `old_hotkey`: A reference to the current account ID (old hotkey) of the delegate identity. + /// - `new_hotkey`: A reference to the new account ID (new hotkey) to be assigned to the delegate identity. + /// + /// # Returns + /// - `Result<(), SwapError>`: Returns `Ok(())` if the swap is successful. Returns `Err(SwapError)` otherwise. + pub fn swap_delegate_identity_coldkey( + old_coldkey: &T::AccountId, + new_coldkey: &T::AccountId, + ) -> DispatchResult { + // Attempt to remove the identity associated with the old hotkey. + let identity: ChainIdentity = + Identities::::take(old_coldkey).ok_or(Error::::OldColdkeyNotFound)?; + + // Ensure the new hotkey is not already in use. + if Identities::::contains_key(new_coldkey) { + // Reinsert the identity back with the old hotkey to maintain consistency. + Identities::::insert(old_coldkey, identity); + return Err(Error::::NewColdkeyInUse.into()); + } + + // Insert the identity with the new hotkey. + Identities::::insert(new_coldkey, identity); + Ok(()) + } } diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index b0eada8e6..564db2238 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -1,7 +1,7 @@ use crate::mock::*; mod mock; -use frame_support::assert_noop; use frame_support::pallet_prelude::Weight; +use frame_support::{assert_err, assert_noop}; use frame_support::{ assert_ok, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, @@ -827,3 +827,181 @@ fn test_migrate_set_hotkey_identities() { ); }); } + +#[test] +fn test_swap_delegate_identity_coldkey_successful() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let name = b"Second Coolest Identity".to_vec(); + let old_identity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + // Set identity for the old coldkey + Identities::::insert(old_coldkey, old_identity.clone()); + + // Swap the coldkey + assert_ok!(SubtensorModule::swap_delegate_identity_coldkey( + &old_coldkey, + &new_coldkey + )); + assert!(Identities::::get(new_coldkey).is_some()); + assert!(Identities::::get(old_coldkey).is_none()); + + // Verify the identity information is correctly swapped + let identity: ChainIdentity = + Identities::::get(new_coldkey).expect("Expected an Identity"); + assert_eq!(identity.name, name); + }); +} + +#[test] +fn test_swap_delegate_identity_coldkey_new_coldkey_already_exists() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let old_identity = ChainIdentity { + name: b"Old Identity".to_vec(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + let new_identity = ChainIdentity { + name: b"New Identity".to_vec(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + // Add identity for old coldkey and new coldkey + Identities::::insert(old_coldkey, old_identity.clone()); + Identities::::insert(new_coldkey, new_identity.clone()); + + // Attempt to swap coldkey to one that is already in use + assert_err!( + SubtensorModule::swap_delegate_identity_coldkey(&old_coldkey, &new_coldkey), + Error::::NewColdkeyInUse + ); + + // Verify both identities remain unchanged + let stored_old_identity: ChainIdentity = + Identities::::get(old_coldkey).expect("Expected an Identity"); + assert_eq!(stored_old_identity.name, old_identity.name); + + let stored_new_identity: ChainIdentity = + Identities::::get(new_coldkey).expect("Expected an Identity"); + assert_eq!(stored_new_identity.name, new_identity.name); + }); +} + +#[test] +fn test_swap_delegate_identity_coldkey_old_coldkey_does_not_exist() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + // Ensure old coldkey does not exist + assert!(Identities::::get(old_coldkey).is_none()); + + assert_err!( + SubtensorModule::swap_delegate_identity_coldkey(&old_coldkey, &new_coldkey), + Error::::OldColdkeyNotFound + ); + assert!(Identities::::get(new_coldkey).is_none()); + }); +} + +#[test] +fn test_coldkey_swap_delegate_identity_updated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Third Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(old_coldkey, identity.clone()); + + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &new_coldkey + )); + + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert_eq!( + Identities::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + }); +} + +#[test] +fn test_coldkey_swap_no_identity_no_changes() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + // Ensure the old coldkey does not have an identity before the swap + assert!(Identities::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &new_coldkey, + )); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_none()); + }); +} From 9d13693b5537eb11fefd23b1a961e1e49ed4c9bc Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 31 Jul 2024 20:09:54 -0700 Subject: [PATCH 105/269] expand check --- pallets/subtensor/src/swap/swap_coldkey.rs | 14 ++++--- pallets/subtensor/tests/serving.rs | 47 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 082114d94..ca57cf994 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -59,16 +59,18 @@ impl Pallet { Error::::NotEnoughBalanceToPaySwapColdKey ); - // 6. Remove and burn the swap cost from the old coldkey's account + // 6. Swap identity if the old coldkey has one. + if Identities::::contains_key(&old_coldkey) + && !Identities::::contains_key(new_coldkey) + { + Self::swap_delegate_identity_coldkey(&old_coldkey, new_coldkey)?; + } + + // 7. Remove and burn the swap cost from the old coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); - // 7. Swap identity if the old coldkey has one. - if Identities::::contains_key(&old_coldkey) { - Self::swap_delegate_identity_coldkey(&old_coldkey, new_coldkey)?; - } - // 8. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 564db2238..914a891de 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -1005,3 +1005,50 @@ fn test_coldkey_swap_no_identity_no_changes() { assert!(Identities::::get(new_coldkey).is_none()); }); } + +#[test] +fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { + new_test_ext(1).execute_with(|| { + let old_coldkey_2 = U256::from(3); + let new_coldkey_2 = U256::from(4); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey_2, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey_2), + netuid, + old_coldkey_2 + )); + + let name: Vec = b"The Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(new_coldkey_2, identity.clone()); + // Ensure the new coldkey does have an identity before the swap + assert!(Identities::::get(new_coldkey_2).is_some()); + assert!(Identities::::get(old_coldkey_2).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey_2), + &new_coldkey_2, + )); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey_2).is_none()); + assert!(Identities::::get(new_coldkey_2).is_some()); + }); +} From 3ce54bd44045c1307762f8140ca61f47d40c69e4 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 31 Jul 2024 20:15:20 -0700 Subject: [PATCH 106/269] fix doc comment --- pallets/subtensor/src/utils/identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 11d3677d1..8cf7b4ef0 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -114,7 +114,7 @@ impl Pallet { /// - `new_hotkey`: A reference to the new account ID (new hotkey) to be assigned to the delegate identity. /// /// # Returns - /// - `Result<(), SwapError>`: Returns `Ok(())` if the swap is successful. Returns `Err(SwapError)` otherwise. + /// - `DispatchResult`: Returns `Ok(())` if the swap is successful. Returns an error variant of `DispatchResult` otherwise. pub fn swap_delegate_identity_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, From a125856bb4aec20ab968c8dd3b6460a8c589ff50 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 11:19:24 +0800 Subject: [PATCH 107/269] fix unit test --- pallets/subtensor/Cargo.toml | 4 +- pallets/subtensor/src/macros/config.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 29 ++++++-- pallets/subtensor/src/swap/swap_coldkey.rs | 1 + pallets/subtensor/tests/mock.rs | 81 ++++++++++++++++++++-- pallets/subtensor/tests/swap_coldkey.rs | 57 ++++++++------- 6 files changed, 138 insertions(+), 36 deletions(-) diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index b002c2371..37c579db0 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -24,7 +24,7 @@ sp-core = { workspace = true } pallet-balances = { workspace = true } scale-info = { workspace = true, features = ["derive"] } frame-benchmarking = { workspace = true, optional = true } -frame-support = { workspace = true } +frame-support = { workspace = true , features = ["experimental", "tuples-96"]} frame-system = { workspace = true } sp-io = { workspace = true } serde = { workspace = true, features = ["derive"] } @@ -56,6 +56,8 @@ parity-util-mem = { workspace = true, features = ["primitive-types"] } rand = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } +pallet-preimage = { workspace = true } +pallet-parameters = { workspace = true } [features] default = ["std"] diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index d2504eb4e..82e3f5afc 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -31,7 +31,7 @@ mod config { type TriumvirateInterface: crate::CollectiveInterface; /// The scheduler type used for scheduling delayed calls. - type Scheduler: ScheduleNamed, CallOf, PalletsOriginOf>; + type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf>; /// ================================= /// ==== Initial Value Constants ==== diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 348f57f73..3b1aa4418 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -4,6 +4,7 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod dispatches { + use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::traits::schedule::DispatchTime; use frame_support::traits::Bounded; @@ -959,7 +960,7 @@ mod dispatches { ); // Calculate the number of blocks in 5 days - let blocks_in_5_days: u32 = 5 * 24 * 60 * 60 / 12; + let blocks_in_5_days: u32 = 2; let current_block = >::block_number(); let when = current_block.saturating_add(BlockNumberFor::::from(blocks_in_5_days)); @@ -976,7 +977,7 @@ mod dispatches { ) .using_encoded(sp_io::hashing::blake2_256); - let hash = , CallOf, PalletsOriginOf, @@ -984,16 +985,34 @@ mod dispatches { let len = call.using_encoded(|e| e.len() as u32); - T::Scheduler::schedule_named( - unique_id, + // fn schedule( + // when: DispatchTime, + // maybe_periodic: Option>, + // priority: Priority, + // origin: Origin, + // call: Bounded, + // ) -> Result; + + T::Scheduler::schedule( DispatchTime::At(when), None, - 63, + 0, + // T::RuntimeOrigin::root(), frame_system::RawOrigin::Root.into(), Bounded::Lookup { hash, len }, ) .map_err(|_| Error::::FailedToSchedule)?; + // T::Scheduler::schedule_named( + // unique_id, + // DispatchTime::At(when), + // None, + // 63, + // frame_system::RawOrigin::Root.into(), + // Bounded::Lookup { hash, len }, + // ) + // .map_err(|_| Error::::FailedToSchedule)?; + ColdkeySwapScheduled::::insert(&who, ()); // Emit the SwapScheduled event Self::deposit_event(Event::ColdkeySwapScheduled { diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index b47b0ac95..5c866e663 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -33,6 +33,7 @@ impl Pallet { origin: T::RuntimeOrigin, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { + log::info!("+++ do_swap_coldkey +++"); // 1. Ensure the origin is signed and get the old coldkey let old_coldkey = ensure_signed(origin)?; diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 5503b8190..62deba782 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -5,12 +5,17 @@ use frame_support::weights::constants::RocksDbWeight; // use frame_support::weights::constants::WEIGHT_PER_SECOND; use frame_support::weights::Weight; use frame_support::{ - assert_ok, parameter_types, - traits::{Everything, Hooks, PrivilegeCmp}, + assert_ok, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + parameter_types, + traits::fungible::HoldConsideration, + traits::{Everything, Hooks, LinearStoragePrice, PrivilegeCmp}, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; use pallet_collective::MemberCount; +use pallet_parameters; +use pallet_preimage; use sp_core::{Get, H256, U256}; use sp_runtime::Perbill; use sp_runtime::{ @@ -34,6 +39,8 @@ frame_support::construct_runtime!( SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Storage, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, + Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, + Parameters: pallet_parameters::{Pallet, Call, Storage, Event}, } ); @@ -69,6 +76,36 @@ pub type Balance = u64; #[allow(dead_code)] pub type BlockNumber = u64; +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod storage { + /// Configures the base deposit of storing some data. + #[codec(index = 0)] + pub static BaseDeposit: Balance = 1; + + /// Configures the per-byte deposit of storing some data. + #[codec(index = 1)] + pub static ByteDeposit: Balance = 1; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod contracts { + #[codec(index = 0)] + pub static DepositPerItem: Balance = 1; + + #[codec(index = 1)] + pub static DepositPerByte: Balance = 1; + + #[codec(index = 2)] + pub static DefaultDepositLimit: Balance = 1; + } +} + #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { type Balance = Balance; @@ -395,7 +432,7 @@ pub struct OriginPrivilegeCmp; impl PrivilegeCmp for OriginPrivilegeCmp { fn cmp_privilege(_left: &OriginCaller, _right: &OriginCaller) -> Option { - None + Some(Ordering::Less) } } @@ -416,7 +453,7 @@ impl pallet_scheduler::Config for Test { type MaxScheduledPerBlock = MaxScheduledPerBlock; type WeightInfo = pallet_scheduler::weights::SubstrateWeight; type OriginPrivilegeCmp = OriginPrivilegeCmp; - type Preimages = (); + type Preimages = Preimage; } impl pallet_utility::Config for Test { @@ -426,6 +463,34 @@ impl pallet_utility::Config for Test { type WeightInfo = pallet_utility::weights::SubstrateWeight; } +parameter_types! { + pub const PreimageMaxSize: u32 = 4096 * 1024; + pub const PreimageBaseDeposit: Balance = 1; + pub const PreimageByteDeposit: Balance = 1; + // pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); +} + +impl pallet_preimage::Config for Test { + type WeightInfo = pallet_preimage::weights::SubstrateWeight; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type ManagerOrigin = EnsureRoot; + type Consideration = (); + // HoldConsideration< + // AccountId, + // Balances, + // PreimageHoldReason, + // LinearStoragePrice, + // >; +} + +impl pallet_parameters::Config for Test { + type RuntimeParameters = RuntimeParameters; + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = EnsureRoot; + type WeightInfo = (); +} + #[allow(dead_code)] // Build genesis storage according to the mock runtime. pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { @@ -460,22 +525,30 @@ pub fn test_ext_with_balances(balances: Vec<(U256, u128)>) -> sp_io::TestExterna #[allow(dead_code)] pub(crate) fn step_block(n: u16) { for _ in 0..n { + Scheduler::on_finalize(System::block_number()); SubtensorModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); SubtensorModule::on_initialize(System::block_number()); + Scheduler::on_initialize(System::block_number()); } } #[allow(dead_code)] pub(crate) fn run_to_block(n: u64) { while System::block_number() < n { + Scheduler::on_finalize(System::block_number()); SubtensorModule::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); + System::events().iter().for_each(|event| { + log::info!("Event: {:?}", event.event); + }); + System::reset_events(); SubtensorModule::on_initialize(System::block_number()); + Scheduler::on_initialize(System::block_number()); } } diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 8c2bb2330..3ba9a0468 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1389,7 +1389,7 @@ fn test_schedule_swap_coldkey_execution() { // Get the scheduled execution block let current_block = System::block_number(); - let blocks_in_5_days = 5 * 24 * 60 * 60 / 12; + let blocks_in_5_days = 2; let execution_block = current_block + blocks_in_5_days; println!("Current block: {}", current_block); @@ -1406,34 +1406,41 @@ fn test_schedule_swap_coldkey_execution() { // Fast forward to the execution block // System::set_block_number(execution_block); - run_to_block(execution_block); + for block_number in current_block..(execution_block + 3) { + log::info!("+++++ Block number: {}", block_number); + // System::events().iter().for_each(|event| { + // log::info!("Event: {:?}", event.event); + // }); + // Preimage::len(); + run_to_block(block_number + 1); + } // Run on_initialize for the execution block SubtensorModule::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); - // println!("New owner after swap: {:?}", new_owner); - // assert_eq!( - // new_owner, new_coldkey, - // "Ownership was not updated as expected" - // ); - - // assert_eq!( - // Stake::::get(hotkey, new_coldkey), - // stake_amount, - // "Stake was not transferred to new coldkey" - // ); - // assert_eq!( - // Stake::::get(hotkey, old_coldkey), - // 0, - // "Old coldkey still has stake" - // ); + // Also run Scheduler's on_initialize + as OnInitialize>::on_initialize( + execution_block, + ); + + // Check if the swap has occurred + let new_owner = Owner::::get(hotkey); + println!("New owner after swap: {:?}", new_owner); + assert_eq!( + new_owner, new_coldkey, + "Ownership was not updated as expected" + ); + + assert_eq!( + Stake::::get(hotkey, new_coldkey), + stake_amount, + "Stake was not transferred to new coldkey" + ); + assert_eq!( + Stake::::get(hotkey, old_coldkey), + 0, + "Old coldkey still has stake" + ); // Check for the SwapExecuted event }); From 0c1816cfd7b705dd17ffe0902d48b79384f4a361 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 15:41:50 +0800 Subject: [PATCH 108/269] fixed the config level issue --- pallets/subtensor/src/lib.rs | 20 ++++--- pallets/subtensor/src/macros/config.rs | 18 ++++++- pallets/subtensor/src/macros/dispatches.rs | 61 +++++++++++++++------- pallets/subtensor/src/swap/swap_coldkey.rs | 5 +- pallets/subtensor/tests/mock.rs | 2 + pallets/subtensor/tests/swap_coldkey.rs | 6 ++- runtime/src/lib.rs | 2 + 7 files changed, 83 insertions(+), 31 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 321e58b4a..34b4ab5b2 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -65,11 +65,13 @@ pub mod pallet { use frame_support::{ dispatch::GetDispatchInfo, pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, - traits::{tokens::fungible, OriginTrait, UnfilteredDispatchable}, + traits::{ + tokens::fungible, OriginTrait, QueryPreimage, StorePreimage, UnfilteredDispatchable, + }, }; use frame_system::pallet_prelude::*; use sp_core::H256; - use sp_runtime::traits::TrailingZeroInput; + use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::vec; use sp_std::vec::Vec; @@ -103,6 +105,9 @@ pub mod pallet { /// Struct for Axon. pub type AxonInfoOf = AxonInfo; + /// local one + pub type LocalCallOf = ::RuntimeCall; + /// Data structure for Axon information. #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct AxonInfo { @@ -1189,7 +1194,8 @@ pub struct SubtensorSignedExtension(pub Phan impl Default for SubtensorSignedExtension where - T::RuntimeCall: Dispatchable, + ::RuntimeCall: + Dispatchable, ::RuntimeCall: IsSubType>, { fn default() -> Self { @@ -1199,7 +1205,8 @@ where impl SubtensorSignedExtension where - T::RuntimeCall: Dispatchable, + ::RuntimeCall: + Dispatchable, ::RuntimeCall: IsSubType>, { pub fn new() -> Self { @@ -1230,14 +1237,15 @@ impl sp_std::fmt::Debug for SubtensorSignedE impl SignedExtension for SubtensorSignedExtension where - T::RuntimeCall: Dispatchable, + ::RuntimeCall: + Dispatchable, ::RuntimeCall: IsSubType>, ::RuntimeCall: IsSubType>, { const IDENTIFIER: &'static str = "SubtensorSignedExtension"; type AccountId = T::AccountId; - type Call = T::RuntimeCall; + type Call = ::RuntimeCall; type AdditionalSigned = (); type Pre = (CallType, u64, Self::AccountId); diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 82e3f5afc..c333dfed6 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -9,6 +9,13 @@ mod config { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { + /// call type + type RuntimeCall: Parameter + + Dispatchable + + From> + + IsType<::RuntimeCall> + + From>; + /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -31,7 +38,16 @@ mod config { type TriumvirateInterface: crate::CollectiveInterface; /// The scheduler type used for scheduling delayed calls. - type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf>; + type Scheduler: ScheduleNamed, LocalCallOf, PalletsOriginOf> + + ScheduleAnon< + BlockNumberFor, + LocalCallOf, + PalletsOriginOf, + Hasher = Self::Hashing, + >; + + /// the preimage to store the call data. + type Preimages: QueryPreimage + StorePreimage; /// ================================= /// ==== Initial Value Constants ==== diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3b1aa4418..3e3590bf9 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -683,10 +683,10 @@ mod dispatches { new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) - ensure_root(origin.clone())?; + // ensure_root(origin.clone())?; let who = ensure_signed(origin)?; - Self::do_swap_coldkey(frame_system::RawOrigin::Signed(who).into(), &new_coldkey) + Self::do_swap_coldkey(&who, &new_coldkey) } /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. @@ -969,21 +969,24 @@ mod dispatches { new_coldkey: new_coldkey.clone(), }; - let unique_id = ( - b"schedule_swap_coldkey", - who.clone(), - new_coldkey.clone(), - when, - ) - .using_encoded(sp_io::hashing::blake2_256); + let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) + .map_err(|_| Error::::FailedToSchedule)?; + + // let unique_id = ( + // b"schedule_swap_coldkey", + // who.clone(), + // new_coldkey.clone(), + // when, + // ) + // .using_encoded(sp_io::hashing::blake2_256); - let hash = , - CallOf, - PalletsOriginOf, - >>::Hasher::hash_of(&call); + // let hash = , + // CallOf, + // PalletsOriginOf, + // >>::Hasher::hash_of(&call); - let len = call.using_encoded(|e| e.len() as u32); + // let len = call.using_encoded(|e| e.len() as u32); // fn schedule( // when: DispatchTime, @@ -993,13 +996,33 @@ mod dispatches { // call: Bounded, // ) -> Result; + // T::Scheduler::schedule_named( + // unique_id, + // DispatchTime::At(when), + // None, + // 63, + // frame_system::RawOrigin::Root.into(), + // // bound_call, + // Bounded::Lookup { hash, len }, + // ) + // .map_err(|_| Error::::FailedToSchedule)?; + + // let result = T::Scheduler::schedule( + // DispatchTime::At(when), + // None, + // 128u8, + // frame_system::RawOrigin::Root.into(), + // call, + // ); + T::Scheduler::schedule( DispatchTime::At(when), None, - 0, - // T::RuntimeOrigin::root(), - frame_system::RawOrigin::Root.into(), - Bounded::Lookup { hash, len }, + 63, + // frame_system::RawOrigin::Root.into(), + frame_system::RawOrigin::Signed(who.clone()).into(), + // frame_system::RawOrigin::Signed(&who).into(), + bound_call, ) .map_err(|_| Error::::FailedToSchedule)?; diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 5c866e663..1b87b386b 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -30,12 +30,11 @@ impl Pallet { /// /// Weight is tracked and updated throughout the function execution. pub fn do_swap_coldkey( - origin: T::RuntimeOrigin, + old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - log::info!("+++ do_swap_coldkey +++"); // 1. Ensure the origin is signed and get the old coldkey - let old_coldkey = ensure_signed(origin)?; + // let old_coldkey = ensure_signed(origin)?; // 2. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 62deba782..c357e0948 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -373,6 +373,7 @@ impl pallet_membership::Config for Test { impl pallet_subtensor::Config for Test { type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type InitialIssuance = InitialIssuance; type SudoRuntimeCall = TestRuntimeCall; @@ -426,6 +427,7 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; type InitialNetworkMaxStake = InitialNetworkMaxStake; + type Preimages = Preimage; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 3ba9a0468..b27549a8f 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -548,7 +548,8 @@ fn test_do_swap_coldkey_success() { // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), + // <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, &new_coldkey )); @@ -889,7 +890,8 @@ fn test_do_swap_coldkey_with_subnet_ownership() { // Perform the swap assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), + // <::RuntimeOrigin>::signed(old_coldkey), + &old_coldkey, &new_coldkey )); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 000e3d7a1..12fb55586 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -894,6 +894,7 @@ parameter_types! { impl pallet_subtensor::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type SudoRuntimeCall = RuntimeCall; type Currency = Balances; type CouncilOrigin = EnsureMajoritySenate; @@ -947,6 +948,7 @@ impl pallet_subtensor::Config for Runtime { type LiquidAlphaOn = InitialLiquidAlphaOn; type InitialHotkeyEmissionTempo = SubtensorInitialHotkeyEmissionTempo; type InitialNetworkMaxStake = SubtensorInitialNetworkMaxStake; + type Preimages = Preimage; } use sp_runtime::BoundedVec; From aad89d88aeb8c701f609562ba03b7b22c225cdc0 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 18:35:26 +0800 Subject: [PATCH 109/269] fix compilation --- pallets/subtensor/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 37c579db0..c05b12b2e 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -57,7 +57,6 @@ rand = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } pallet-preimage = { workspace = true } -pallet-parameters = { workspace = true } [features] default = ["std"] From 53ccd943faa6038dfab66dc0bd6cb81370dfa65c Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 19:15:53 +0800 Subject: [PATCH 110/269] all unit test passed --- pallets/subtensor/src/macros/dispatches.rs | 69 +++------------------- pallets/subtensor/src/swap/swap_coldkey.rs | 7 +++ pallets/subtensor/tests/mock.rs | 64 ++++++++++---------- pallets/subtensor/tests/swap_coldkey.rs | 29 ++++----- 4 files changed, 58 insertions(+), 111 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 3e3590bf9..9105dc0ea 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -7,9 +7,7 @@ mod dispatches { use frame_support::traits::schedule::v3::Anon as ScheduleAnon; use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::traits::schedule::DispatchTime; - use frame_support::traits::Bounded; use frame_system::pallet_prelude::BlockNumberFor; - use sp_runtime::traits::Hash; use sp_runtime::traits::Saturating; /// Dispatchable functions allow users to interact with the pallet and invoke state changes. /// These functions materialize as "extrinsics", which are often compared to transactions. @@ -680,13 +678,14 @@ mod dispatches { .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] pub fn swap_coldkey( origin: OriginFor, + old_coldkey: T::AccountId, new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) - // ensure_root(origin.clone())?; + ensure_root(origin.clone())?; + log::info!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); - let who = ensure_signed(origin)?; - Self::do_swap_coldkey(&who, &new_coldkey) + Self::do_swap_coldkey(&old_coldkey, &new_coldkey) } /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. @@ -960,82 +959,28 @@ mod dispatches { ); // Calculate the number of blocks in 5 days - let blocks_in_5_days: u32 = 2; + let blocks_in_5_days: u32 = 5 * 24 * 60 * 60 / 12; let current_block = >::block_number(); let when = current_block.saturating_add(BlockNumberFor::::from(blocks_in_5_days)); let call = Call::::swap_coldkey { + old_coldkey: who.clone(), new_coldkey: new_coldkey.clone(), }; let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) .map_err(|_| Error::::FailedToSchedule)?; - // let unique_id = ( - // b"schedule_swap_coldkey", - // who.clone(), - // new_coldkey.clone(), - // when, - // ) - // .using_encoded(sp_io::hashing::blake2_256); - - // let hash = , - // CallOf, - // PalletsOriginOf, - // >>::Hasher::hash_of(&call); - - // let len = call.using_encoded(|e| e.len() as u32); - - // fn schedule( - // when: DispatchTime, - // maybe_periodic: Option>, - // priority: Priority, - // origin: Origin, - // call: Bounded, - // ) -> Result; - - // T::Scheduler::schedule_named( - // unique_id, - // DispatchTime::At(when), - // None, - // 63, - // frame_system::RawOrigin::Root.into(), - // // bound_call, - // Bounded::Lookup { hash, len }, - // ) - // .map_err(|_| Error::::FailedToSchedule)?; - - // let result = T::Scheduler::schedule( - // DispatchTime::At(when), - // None, - // 128u8, - // frame_system::RawOrigin::Root.into(), - // call, - // ); - T::Scheduler::schedule( DispatchTime::At(when), None, 63, - // frame_system::RawOrigin::Root.into(), - frame_system::RawOrigin::Signed(who.clone()).into(), - // frame_system::RawOrigin::Signed(&who).into(), + frame_system::RawOrigin::Root.into(), bound_call, ) .map_err(|_| Error::::FailedToSchedule)?; - // T::Scheduler::schedule_named( - // unique_id, - // DispatchTime::At(when), - // None, - // 63, - // frame_system::RawOrigin::Root.into(), - // Bounded::Lookup { hash, len }, - // ) - // .map_err(|_| Error::::FailedToSchedule)?; - ColdkeySwapScheduled::::insert(&who, ()); // Emit the SwapScheduled event Self::deposit_event(Event::ColdkeySwapScheduled { diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 1b87b386b..c76da4ef2 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -39,17 +39,22 @@ impl Pallet { // 2. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); + log::info!("do_swap_coldkey old_coldkey: {:?}", old_coldkey); + // 3. Ensure the new coldkey is not associated with any hotkeys ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); + log::info!("do_swap_coldkey ColdKeyAlreadyAssociated"); + // 4. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::ColdKeyAlreadyAssociated ); + log::info!("do_swap_coldkey ColdKeyAlreadyAssociated 2"); // 5. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); @@ -58,6 +63,7 @@ impl Pallet { Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey ); + log::info!("do_swap_coldkey NotEnoughBalanceToPaySwapColdKey"); // 6. Remove and burn the swap cost from the old coldkey's account let actual_burn_amount = @@ -69,6 +75,7 @@ impl Pallet { // 8. Perform the actual coldkey swap let _ = Self::perform_swap_coldkey(&old_coldkey, new_coldkey, &mut weight); + log::info!("do_swap_coldkey perform_swap_coldkey"); // 9. Update the last transaction block for the new coldkey Self::set_last_tx_block(new_coldkey, Self::get_current_block_as_u64()); diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index c357e0948..e1c5f07d7 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -14,7 +14,7 @@ use frame_support::{ use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; use pallet_collective::MemberCount; -use pallet_parameters; +// use pallet_parameters; use pallet_preimage; use sp_core::{Get, H256, U256}; use sp_runtime::Perbill; @@ -40,7 +40,7 @@ frame_support::construct_runtime!( Utility: pallet_utility::{Pallet, Call, Storage, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, - Parameters: pallet_parameters::{Pallet, Call, Storage, Event}, + // Parameters: pallet_parameters::{Pallet, Call, Storage, Event}, } ); @@ -76,35 +76,35 @@ pub type Balance = u64; #[allow(dead_code)] pub type BlockNumber = u64; -#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] -pub mod dynamic_params { - use super::*; +// #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +// pub mod dynamic_params { +// use super::*; - #[dynamic_pallet_params] - #[codec(index = 0)] - pub mod storage { - /// Configures the base deposit of storing some data. - #[codec(index = 0)] - pub static BaseDeposit: Balance = 1; +// #[dynamic_pallet_params] +// #[codec(index = 0)] +// pub mod storage { +// /// Configures the base deposit of storing some data. +// #[codec(index = 0)] +// pub static BaseDeposit: Balance = 1; - /// Configures the per-byte deposit of storing some data. - #[codec(index = 1)] - pub static ByteDeposit: Balance = 1; - } +// /// Configures the per-byte deposit of storing some data. +// #[codec(index = 1)] +// pub static ByteDeposit: Balance = 1; +// } - #[dynamic_pallet_params] - #[codec(index = 1)] - pub mod contracts { - #[codec(index = 0)] - pub static DepositPerItem: Balance = 1; +// #[dynamic_pallet_params] +// #[codec(index = 1)] +// pub mod contracts { +// #[codec(index = 0)] +// pub static DepositPerItem: Balance = 1; - #[codec(index = 1)] - pub static DepositPerByte: Balance = 1; +// #[codec(index = 1)] +// pub static DepositPerByte: Balance = 1; - #[codec(index = 2)] - pub static DefaultDepositLimit: Balance = 1; - } -} +// #[codec(index = 2)] +// pub static DefaultDepositLimit: Balance = 1; +// } +// } #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { @@ -486,12 +486,12 @@ impl pallet_preimage::Config for Test { // >; } -impl pallet_parameters::Config for Test { - type RuntimeParameters = RuntimeParameters; - type RuntimeEvent = RuntimeEvent; - type AdminOrigin = EnsureRoot; - type WeightInfo = (); -} +// impl pallet_parameters::Config for Test { +// type RuntimeParameters = RuntimeParameters; +// type RuntimeEvent = RuntimeEvent; +// type AdminOrigin = EnsureRoot; +// type WeightInfo = (); +// } #[allow(dead_code)] // Build genesis storage according to the mock runtime. diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index b27549a8f..cfc2d0098 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1352,7 +1352,7 @@ fn test_schedule_swap_coldkey_duplicate() { <::RuntimeOrigin>::signed(old_coldkey), new_coldkey ), - Error::::FailedToSchedule + Error::::SwapAlreadyScheduled ); }); } @@ -1369,7 +1369,7 @@ fn test_schedule_swap_coldkey_execution() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000000000000000); assert_ok!(SubtensorModule::add_stake( <::RuntimeOrigin>::signed(old_coldkey), hotkey, @@ -1391,12 +1391,9 @@ fn test_schedule_swap_coldkey_execution() { // Get the scheduled execution block let current_block = System::block_number(); - let blocks_in_5_days = 2; + let blocks_in_5_days = 5 * 24 * 60 * 60 / 12; let execution_block = current_block + blocks_in_5_days; - println!("Current block: {}", current_block); - println!("Execution block: {}", execution_block); - System::assert_last_event( Event::ColdkeySwapScheduled { old_coldkey, @@ -1406,16 +1403,7 @@ fn test_schedule_swap_coldkey_execution() { .into(), ); - // Fast forward to the execution block - // System::set_block_number(execution_block); - for block_number in current_block..(execution_block + 3) { - log::info!("+++++ Block number: {}", block_number); - // System::events().iter().for_each(|event| { - // log::info!("Event: {:?}", event.event); - // }); - // Preimage::len(); - run_to_block(block_number + 1); - } + run_to_block(execution_block); // Run on_initialize for the execution block SubtensorModule::on_initialize(execution_block); @@ -1427,7 +1415,6 @@ fn test_schedule_swap_coldkey_execution() { // Check if the swap has occurred let new_owner = Owner::::get(hotkey); - println!("New owner after swap: {:?}", new_owner); assert_eq!( new_owner, new_coldkey, "Ownership was not updated as expected" @@ -1445,6 +1432,13 @@ fn test_schedule_swap_coldkey_execution() { ); // Check for the SwapExecuted event + System::assert_last_event( + Event::ColdkeySwapped { + old_coldkey, + new_coldkey, + } + .into(), + ); }); } @@ -1458,6 +1452,7 @@ fn test_direct_swap_coldkey_call_fails() { assert_noop!( SubtensorModule::swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), + old_coldkey, new_coldkey ), BadOrigin From 2f83b84e726b22e5171d6bfcacc211db4bcf2dbf Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 19:19:19 +0800 Subject: [PATCH 111/269] clean up code --- pallets/subtensor/src/swap/swap_coldkey.rs | 20 ++-------- pallets/subtensor/tests/mock.rs | 45 ---------------------- 2 files changed, 4 insertions(+), 61 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index c76da4ef2..2b54d4313 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -33,56 +33,44 @@ impl Pallet { old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, ) -> DispatchResultWithPostInfo { - // 1. Ensure the origin is signed and get the old coldkey - // let old_coldkey = ensure_signed(origin)?; - // 2. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); - log::info!("do_swap_coldkey old_coldkey: {:?}", old_coldkey); - // 3. Ensure the new coldkey is not associated with any hotkeys ensure!( StakingHotkeys::::get(new_coldkey).is_empty(), Error::::ColdKeyAlreadyAssociated ); - log::info!("do_swap_coldkey ColdKeyAlreadyAssociated"); - // 4. Ensure the new coldkey is not a hotkey ensure!( !Self::hotkey_account_exists(new_coldkey), Error::::ColdKeyAlreadyAssociated ); - log::info!("do_swap_coldkey ColdKeyAlreadyAssociated 2"); // 5. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); - log::debug!("Coldkey swap cost: {:?}", swap_cost); ensure!( - Self::can_remove_balance_from_coldkey_account(&old_coldkey, swap_cost), + Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey ); - log::info!("do_swap_coldkey NotEnoughBalanceToPaySwapColdKey"); // 6. Remove and burn the swap cost from the old coldkey's account - let actual_burn_amount = - Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; + let actual_burn_amount = Self::remove_balance_from_coldkey_account(old_coldkey, swap_cost)?; Self::burn_tokens(actual_burn_amount); // 7. Update the weight for the balance operations weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); // 8. Perform the actual coldkey swap - let _ = Self::perform_swap_coldkey(&old_coldkey, new_coldkey, &mut weight); - log::info!("do_swap_coldkey perform_swap_coldkey"); + let _ = Self::perform_swap_coldkey(old_coldkey, new_coldkey, &mut weight); // 9. 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)); // 10. Remove the coldkey swap scheduled record - ColdkeySwapScheduled::::remove(&old_coldkey); + ColdkeySwapScheduled::::remove(old_coldkey); // 11. Emit the ColdkeySwapped event Self::deposit_event(Event::ColdkeySwapped { diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index e1c5f07d7..d6b45c63f 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -40,7 +40,6 @@ frame_support::construct_runtime!( Utility: pallet_utility::{Pallet, Call, Storage, Event}, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, - // Parameters: pallet_parameters::{Pallet, Call, Storage, Event}, } ); @@ -76,36 +75,6 @@ pub type Balance = u64; #[allow(dead_code)] pub type BlockNumber = u64; -// #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] -// pub mod dynamic_params { -// use super::*; - -// #[dynamic_pallet_params] -// #[codec(index = 0)] -// pub mod storage { -// /// Configures the base deposit of storing some data. -// #[codec(index = 0)] -// pub static BaseDeposit: Balance = 1; - -// /// Configures the per-byte deposit of storing some data. -// #[codec(index = 1)] -// pub static ByteDeposit: Balance = 1; -// } - -// #[dynamic_pallet_params] -// #[codec(index = 1)] -// pub mod contracts { -// #[codec(index = 0)] -// pub static DepositPerItem: Balance = 1; - -// #[codec(index = 1)] -// pub static DepositPerByte: Balance = 1; - -// #[codec(index = 2)] -// pub static DefaultDepositLimit: Balance = 1; -// } -// } - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { type Balance = Balance; @@ -117,7 +86,6 @@ impl pallet_balances::Config for Test { type WeightInfo = (); type MaxReserves = (); type ReserveIdentifier = (); - type RuntimeHoldReason = (); type FreezeIdentifier = (); type MaxFreezes = (); @@ -478,21 +446,8 @@ impl pallet_preimage::Config for Test { type Currency = Balances; type ManagerOrigin = EnsureRoot; type Consideration = (); - // HoldConsideration< - // AccountId, - // Balances, - // PreimageHoldReason, - // LinearStoragePrice, - // >; } -// impl pallet_parameters::Config for Test { -// type RuntimeParameters = RuntimeParameters; -// type RuntimeEvent = RuntimeEvent; -// type AdminOrigin = EnsureRoot; -// type WeightInfo = (); -// } - #[allow(dead_code)] // Build genesis storage according to the mock runtime. pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { From d80a92889adc893716440250c7ee48e3197b7c96 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 19:31:18 +0800 Subject: [PATCH 112/269] fix clippy --- pallets/subtensor/tests/mock.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index d6b45c63f..328b6e5c7 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -2,20 +2,14 @@ use frame_support::derive_impl; use frame_support::dispatch::DispatchResultWithPostInfo; use frame_support::weights::constants::RocksDbWeight; -// use frame_support::weights::constants::WEIGHT_PER_SECOND; use frame_support::weights::Weight; use frame_support::{ - assert_ok, - dynamic_params::{dynamic_pallet_params, dynamic_params}, - parameter_types, - traits::fungible::HoldConsideration, - traits::{Everything, Hooks, LinearStoragePrice, PrivilegeCmp}, + assert_ok, parameter_types, + traits::{Everything, Hooks, PrivilegeCmp}, }; use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; use pallet_collective::MemberCount; -// use pallet_parameters; -use pallet_preimage; use sp_core::{Get, H256, U256}; use sp_runtime::Perbill; use sp_runtime::{ From 1633b6c98b7d037c1ead9cff69ad90a041a6cd54 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 19:40:17 +0800 Subject: [PATCH 113/269] fix admin test --- pallets/admin-utils/tests/mock.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 4b08c578b..3f3ef843d 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -122,6 +122,7 @@ parameter_types! { impl pallet_subtensor::Config for Test { type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; type Currency = Balances; type InitialIssuance = InitialIssuance; type SudoRuntimeCall = TestRuntimeCall; @@ -175,6 +176,7 @@ impl pallet_subtensor::Config for Test { type LiquidAlphaOn = InitialLiquidAlphaOn; type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; type InitialNetworkMaxStake = InitialNetworkMaxStake; + type Preimages = (); } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] From 49bd0c553702f6b3e7e9b9654bd5103806c78efd Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 20:08:33 +0800 Subject: [PATCH 114/269] fix test --- pallets/subtensor/tests/swap_coldkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index cfc2d0098..5a576fd2d 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1432,7 +1432,7 @@ fn test_schedule_swap_coldkey_execution() { ); // Check for the SwapExecuted event - System::assert_last_event( + System::assert_has_event( Event::ColdkeySwapped { old_coldkey, new_coldkey, From 0f2fa057419a7a726df656b0da29699010034f52 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 20:46:29 +0800 Subject: [PATCH 115/269] remove unneeded feature --- pallets/subtensor/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index c05b12b2e..d7a9bc06c 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -24,7 +24,7 @@ sp-core = { workspace = true } pallet-balances = { workspace = true } scale-info = { workspace = true, features = ["derive"] } frame-benchmarking = { workspace = true, optional = true } -frame-support = { workspace = true , features = ["experimental", "tuples-96"]} +frame-support = { workspace = true } frame-system = { workspace = true } sp-io = { workspace = true } serde = { workspace = true, features = ["derive"] } From 59dc95cf5a189dc7dafef622ce4986dc7656ffe9 Mon Sep 17 00:00:00 2001 From: open-junius Date: Thu, 1 Aug 2024 20:52:46 +0800 Subject: [PATCH 116/269] clean up code --- pallets/subtensor/src/macros/config.rs | 13 ++++++------- pallets/subtensor/src/macros/dispatches.rs | 1 - pallets/subtensor/tests/mock.rs | 1 - pallets/subtensor/tests/swap_coldkey.rs | 6 +----- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index c333dfed6..2f924905d 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -38,13 +38,12 @@ mod config { type TriumvirateInterface: crate::CollectiveInterface; /// The scheduler type used for scheduling delayed calls. - type Scheduler: ScheduleNamed, LocalCallOf, PalletsOriginOf> - + ScheduleAnon< - BlockNumberFor, - LocalCallOf, - PalletsOriginOf, - Hasher = Self::Hashing, - >; + type Scheduler: ScheduleAnon< + BlockNumberFor, + LocalCallOf, + PalletsOriginOf, + Hasher = Self::Hashing, + >; /// the preimage to store the call data. type Preimages: QueryPreimage + StorePreimage; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 9105dc0ea..011fdfe83 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -5,7 +5,6 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod dispatches { use frame_support::traits::schedule::v3::Anon as ScheduleAnon; - use frame_support::traits::schedule::v3::Named as ScheduleNamed; use frame_support::traits::schedule::DispatchTime; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::traits::Saturating; diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 328b6e5c7..3497b6d67 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -431,7 +431,6 @@ parameter_types! { pub const PreimageMaxSize: u32 = 4096 * 1024; pub const PreimageBaseDeposit: Balance = 1; pub const PreimageByteDeposit: Balance = 1; - // pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } impl pallet_preimage::Config for Test { diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 5a576fd2d..48168addc 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -889,11 +889,7 @@ fn test_do_swap_coldkey_with_subnet_ownership() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey( - // <::RuntimeOrigin>::signed(old_coldkey), - &old_coldkey, - &new_coldkey - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); // Verify subnet ownership transfer assert_eq!(SubnetOwner::::get(netuid), new_coldkey); From 28a00fefefd285f3c8bb5d6d993f71bf4954da2e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:11:34 -0700 Subject: [PATCH 117/269] remove swap_id_coldkey fn --- pallets/subtensor/src/macros/errors.rs | 4 - pallets/subtensor/src/swap/swap_coldkey.rs | 14 ++- pallets/subtensor/src/utils/identity.rs | 28 ------ pallets/subtensor/tests/serving.rs | 100 +-------------------- 4 files changed, 10 insertions(+), 136 deletions(-) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index d3e228ec5..07710dc5f 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -170,9 +170,5 @@ mod errors { TxRateLimitExceeded, /// Invalid identity. InvalidIdentity, - /// The old coldkey does not have an identity. - OldColdkeyNotFound, - /// The new coldkey already has an identity. - NewColdkeyInUse, } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index ca57cf994..b3fadfa70 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -51,7 +51,12 @@ impl Pallet { Error::::ColdKeyAlreadyAssociated ); - // 5. Calculate the swap cost and ensure sufficient balance + // 5. Swap the identity if the old coldkey has one + if let Some(identity) = Identities::::take(&old_coldkey) { + Identities::::insert(new_coldkey, identity); + } + + // 6. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); log::debug!("Coldkey swap cost: {:?}", swap_cost); ensure!( @@ -59,13 +64,6 @@ impl Pallet { Error::::NotEnoughBalanceToPaySwapColdKey ); - // 6. Swap identity if the old coldkey has one. - if Identities::::contains_key(&old_coldkey) - && !Identities::::contains_key(new_coldkey) - { - Self::swap_delegate_identity_coldkey(&old_coldkey, new_coldkey)?; - } - // 7. Remove and burn the swap cost from the old coldkey's account let actual_burn_amount = Self::remove_balance_from_coldkey_account(&old_coldkey, swap_cost)?; diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 8cf7b4ef0..1c9c3c25d 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -106,32 +106,4 @@ impl Pallet { && identity.description.len() <= 1024 && identity.additional.len() <= 1024 } - - /// Swaps the hotkey of a delegate identity from an old account ID to a new account ID. - /// - /// # Parameters - /// - `old_hotkey`: A reference to the current account ID (old hotkey) of the delegate identity. - /// - `new_hotkey`: A reference to the new account ID (new hotkey) to be assigned to the delegate identity. - /// - /// # Returns - /// - `DispatchResult`: Returns `Ok(())` if the swap is successful. Returns an error variant of `DispatchResult` otherwise. - pub fn swap_delegate_identity_coldkey( - old_coldkey: &T::AccountId, - new_coldkey: &T::AccountId, - ) -> DispatchResult { - // Attempt to remove the identity associated with the old hotkey. - let identity: ChainIdentity = - Identities::::take(old_coldkey).ok_or(Error::::OldColdkeyNotFound)?; - - // Ensure the new hotkey is not already in use. - if Identities::::contains_key(new_coldkey) { - // Reinsert the identity back with the old hotkey to maintain consistency. - Identities::::insert(old_coldkey, identity); - return Err(Error::::NewColdkeyInUse.into()); - } - - // Insert the identity with the new hotkey. - Identities::::insert(new_coldkey, identity); - Ok(()) - } } diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 914a891de..8dd371acb 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -1,7 +1,7 @@ use crate::mock::*; mod mock; +use frame_support::assert_noop; use frame_support::pallet_prelude::Weight; -use frame_support::{assert_err, assert_noop}; use frame_support::{ assert_ok, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, @@ -828,101 +828,6 @@ fn test_migrate_set_hotkey_identities() { }); } -#[test] -fn test_swap_delegate_identity_coldkey_successful() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let name = b"Second Coolest Identity".to_vec(); - let old_identity = ChainIdentity { - name: name.clone(), - url: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - // Set identity for the old coldkey - Identities::::insert(old_coldkey, old_identity.clone()); - - // Swap the coldkey - assert_ok!(SubtensorModule::swap_delegate_identity_coldkey( - &old_coldkey, - &new_coldkey - )); - assert!(Identities::::get(new_coldkey).is_some()); - assert!(Identities::::get(old_coldkey).is_none()); - - // Verify the identity information is correctly swapped - let identity: ChainIdentity = - Identities::::get(new_coldkey).expect("Expected an Identity"); - assert_eq!(identity.name, name); - }); -} - -#[test] -fn test_swap_delegate_identity_coldkey_new_coldkey_already_exists() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let old_identity = ChainIdentity { - name: b"Old Identity".to_vec(), - url: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - let new_identity = ChainIdentity { - name: b"New Identity".to_vec(), - url: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - // Add identity for old coldkey and new coldkey - Identities::::insert(old_coldkey, old_identity.clone()); - Identities::::insert(new_coldkey, new_identity.clone()); - - // Attempt to swap coldkey to one that is already in use - assert_err!( - SubtensorModule::swap_delegate_identity_coldkey(&old_coldkey, &new_coldkey), - Error::::NewColdkeyInUse - ); - - // Verify both identities remain unchanged - let stored_old_identity: ChainIdentity = - Identities::::get(old_coldkey).expect("Expected an Identity"); - assert_eq!(stored_old_identity.name, old_identity.name); - - let stored_new_identity: ChainIdentity = - Identities::::get(new_coldkey).expect("Expected an Identity"); - assert_eq!(stored_new_identity.name, new_identity.name); - }); -} - -#[test] -fn test_swap_delegate_identity_coldkey_old_coldkey_does_not_exist() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - // Ensure old coldkey does not exist - assert!(Identities::::get(old_coldkey).is_none()); - - assert_err!( - SubtensorModule::swap_delegate_identity_coldkey(&old_coldkey, &new_coldkey), - Error::::OldColdkeyNotFound - ); - assert!(Identities::::get(new_coldkey).is_none()); - }); -} - #[test] fn test_coldkey_swap_delegate_identity_updated() { new_test_ext(1).execute_with(|| { @@ -956,6 +861,9 @@ fn test_coldkey_swap_delegate_identity_updated() { Identities::::insert(old_coldkey, identity.clone()); + assert!(Identities::::get(old_coldkey).is_some()); + assert!(Identities::::get(new_coldkey).is_none()); + assert_ok!(SubtensorModule::do_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), &new_coldkey From 9250464572a049778042d391b9c695f8766414f7 Mon Sep 17 00:00:00 2001 From: open-junius Date: Fri, 2 Aug 2024 13:44:36 +0800 Subject: [PATCH 118/269] update feature dependency --- Cargo.lock | 36 ++++++++++++++++++---------------- pallets/admin-utils/Cargo.toml | 4 +++- pallets/subtensor/Cargo.toml | 6 +++++- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea30f4e25..d8959b99b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1198,7 +1198,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" +source = "git+https://github.com/w3f/ring-proof#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ "ark-ec", "ark-ff", @@ -4150,9 +4150,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" @@ -5266,6 +5266,7 @@ dependencies = [ "pallet-balances", "pallet-collective", "pallet-membership", + "pallet-preimage", "pallet-scheduler", "pallet-transaction-payment", "pallet-utility", @@ -6433,13 +6434,14 @@ dependencies = [ [[package]] name = "ring" version = "0.1.0" -source = "git+https://github.com/w3f/ring-proof#b273d33f9981e2bb3375ab45faeb537f7ee35224" +source = "git+https://github.com/w3f/ring-proof#665f5f51af5734c7b6d90b985dd6861d4c5b4752" dependencies = [ "ark-ec", "ark-ff", "ark-poly", "ark-serialize", "ark-std", + "arrayvec", "blake2 0.10.6", "common", "fflonk", @@ -7908,9 +7910,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -7935,9 +7937,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -8408,7 +8410,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -8470,7 +8472,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "proc-macro2", "quote", @@ -8490,7 +8492,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "environmental", "parity-scale-codec", @@ -8673,7 +8675,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8705,7 +8707,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "Inflector", "expander", @@ -8794,7 +8796,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2 [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" [[package]] name = "sp-storage" @@ -8811,7 +8813,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8846,7 +8848,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "parity-scale-codec", "tracing", @@ -8943,7 +8945,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c4b3c1c6c6e492c4196e06fbba824a58e8119a3b" +source = "git+https://github.com/paritytech/polkadot-sdk#6a5b6e03bfc8d0c6f5f05f3180313902c15aee84" dependencies = [ "impl-trait-for-tuples", "log", diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index 97371a79f..ff54989cd 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -65,12 +65,14 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-subtensor/runtime-benchmarks" + "pallet-subtensor/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-scheduler/try-runtime", "sp-runtime/try-runtime", "pallet-subtensor/try-runtime" ] diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index d7a9bc06c..2baee6127 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -95,13 +95,17 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-collective/runtime-benchmarks" + "pallet-collective/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", "pallet-membership/try-runtime", + "pallet-preimage/try-runtime", + "pallet-scheduler/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", "sp-runtime/try-runtime", From 93644925bebcf5091704cf23f1bd0008bb1b37a7 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 15:43:45 +0900 Subject: [PATCH 119/269] Replace SubtensorInterface with tight pallet coupling --- pallets/admin-utils/src/lib.rs | 302 +++++++++++++-------------------- runtime/src/lib.rs | 279 ------------------------------ 2 files changed, 114 insertions(+), 467 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 9a8744dc6..60209de7d 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -4,7 +4,6 @@ pub use pallet::*; pub mod weights; pub use weights::WeightInfo; -use sp_runtime::DispatchError; use sp_runtime::{traits::Member, RuntimeAppPublic}; mod benchmarking; @@ -26,7 +25,7 @@ pub mod pallet { /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config + pallet_subtensor::pallet::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -48,13 +47,6 @@ pub mod pallet { /// Unit of assets type Balance: Balance; - - /// Implementation of the subtensor interface - type Subtensor: crate::SubtensorInterface< - Self::AccountId, - Self::Balance, - Self::RuntimeOrigin, - >; } #[pallet::event] @@ -100,7 +92,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::sudo_set_default_take())] pub fn sudo_set_default_take(origin: OriginFor, default_take: u16) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_max_delegate_take(default_take); + pallet_subtensor::Pallet::::set_max_delegate_take(default_take); log::info!("DefaultTakeSet( default_take: {:?} ) ", default_take); Ok(()) } @@ -112,7 +104,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_tx_rate_limit(origin: OriginFor, tx_rate_limit: u64) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_tx_rate_limit(tx_rate_limit); + pallet_subtensor::Pallet::::set_tx_rate_limit(tx_rate_limit); log::info!("TxRateLimitSet( tx_rate_limit: {:?} ) ", tx_rate_limit); Ok(()) } @@ -127,9 +119,9 @@ pub mod pallet { netuid: u16, serving_rate_limit: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; - T::Subtensor::set_serving_rate_limit(netuid, serving_rate_limit); + pallet_subtensor::Pallet::::set_serving_rate_limit(netuid, serving_rate_limit); log::info!( "ServingRateLimitSet( serving_rate_limit: {:?} ) ", serving_rate_limit @@ -147,13 +139,13 @@ pub mod pallet { netuid: u16, min_difficulty: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_min_difficulty(netuid, min_difficulty); + pallet_subtensor::Pallet::::set_min_difficulty(netuid, min_difficulty); log::info!( "MinDifficultySet( netuid: {:?} min_difficulty: {:?} ) ", netuid, @@ -172,13 +164,13 @@ pub mod pallet { netuid: u16, max_difficulty: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_max_difficulty(netuid, max_difficulty); + pallet_subtensor::Pallet::::set_max_difficulty(netuid, max_difficulty); log::info!( "MaxDifficultySet( netuid: {:?} max_difficulty: {:?} ) ", netuid, @@ -197,13 +189,13 @@ pub mod pallet { netuid: u16, weights_version_key: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_weights_version_key(netuid, weights_version_key); + pallet_subtensor::Pallet::::set_weights_version_key(netuid, weights_version_key); log::info!( "WeightsVersionKeySet( netuid: {:?} weights_version_key: {:?} ) ", netuid, @@ -222,13 +214,16 @@ pub mod pallet { netuid: u16, weights_set_rate_limit: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_weights_set_rate_limit(netuid, weights_set_rate_limit); + pallet_subtensor::Pallet::::set_weights_set_rate_limit( + netuid, + weights_set_rate_limit, + ); log::info!( "WeightsSetRateLimitSet( netuid: {:?} weights_set_rate_limit: {:?} ) ", netuid, @@ -250,10 +245,10 @@ pub mod pallet { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_adjustment_interval(netuid, adjustment_interval); + pallet_subtensor::Pallet::::set_adjustment_interval(netuid, adjustment_interval); log::info!( "AdjustmentIntervalSet( netuid: {:?} adjustment_interval: {:?} ) ", netuid, @@ -278,13 +273,13 @@ pub mod pallet { netuid: u16, adjustment_alpha: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_adjustment_alpha(netuid, adjustment_alpha); + pallet_subtensor::Pallet::::set_adjustment_alpha(netuid, adjustment_alpha); log::info!( "AdjustmentAlphaSet( adjustment_alpha: {:?} ) ", adjustment_alpha @@ -302,13 +297,13 @@ pub mod pallet { netuid: u16, max_weight_limit: u16, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_max_weight_limit(netuid, max_weight_limit); + pallet_subtensor::Pallet::::set_max_weight_limit(netuid, max_weight_limit); log::info!( "MaxWeightLimitSet( netuid: {:?} max_weight_limit: {:?} ) ", netuid, @@ -327,13 +322,13 @@ pub mod pallet { netuid: u16, immunity_period: u16, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_immunity_period(netuid, immunity_period); + pallet_subtensor::Pallet::::set_immunity_period(netuid, immunity_period); log::info!( "ImmunityPeriodSet( netuid: {:?} immunity_period: {:?} ) ", netuid, @@ -352,13 +347,13 @@ pub mod pallet { netuid: u16, min_allowed_weights: u16, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_min_allowed_weights(netuid, min_allowed_weights); + pallet_subtensor::Pallet::::set_min_allowed_weights(netuid, min_allowed_weights); log::info!( "MinAllowedWeightSet( netuid: {:?} min_allowed_weights: {:?} ) ", netuid, @@ -379,14 +374,14 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); ensure!( - T::Subtensor::get_subnetwork_n(netuid) < max_allowed_uids, + pallet_subtensor::Pallet::::get_subnetwork_n(netuid) < max_allowed_uids, Error::::MaxAllowedUIdsLessThanCurrentUIds ); - T::Subtensor::set_max_allowed_uids(netuid, max_allowed_uids); + pallet_subtensor::Pallet::::set_max_allowed_uids(netuid, max_allowed_uids); log::info!( "MaxAllowedUidsSet( netuid: {:?} max_allowed_uids: {:?} ) ", netuid, @@ -401,13 +396,13 @@ pub mod pallet { #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::sudo_set_kappa())] pub fn sudo_set_kappa(origin: OriginFor, netuid: u16, kappa: u16) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_kappa(netuid, kappa); + pallet_subtensor::Pallet::::set_kappa(netuid, kappa); log::info!("KappaSet( netuid: {:?} kappa: {:?} ) ", netuid, kappa); Ok(()) } @@ -418,13 +413,13 @@ pub mod pallet { #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::sudo_set_rho())] pub fn sudo_set_rho(origin: OriginFor, netuid: u16, rho: u16) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_rho(netuid, rho); + pallet_subtensor::Pallet::::set_rho(netuid, rho); log::info!("RhoSet( netuid: {:?} rho: {:?} ) ", netuid, rho); Ok(()) } @@ -439,13 +434,13 @@ pub mod pallet { netuid: u16, activity_cutoff: u16, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_activity_cutoff(netuid, activity_cutoff); + pallet_subtensor::Pallet::::set_activity_cutoff(netuid, activity_cutoff); log::info!( "ActivityCutoffSet( netuid: {:?} activity_cutoff: {:?} ) ", netuid, @@ -470,9 +465,12 @@ pub mod pallet { netuid: u16, registration_allowed: bool, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; - T::Subtensor::set_network_registration_allowed(netuid, registration_allowed); + pallet_subtensor::Pallet::::set_network_registration_allowed( + netuid, + registration_allowed, + ); log::info!( "NetworkRegistrationAllowed( registration_allowed: {:?} ) ", registration_allowed @@ -495,9 +493,12 @@ pub mod pallet { netuid: u16, registration_allowed: bool, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; - T::Subtensor::set_network_pow_registration_allowed(netuid, registration_allowed); + pallet_subtensor::Pallet::::set_network_pow_registration_allowed( + netuid, + registration_allowed, + ); log::info!( "NetworkPowRegistrationAllowed( registration_allowed: {:?} ) ", registration_allowed @@ -518,10 +519,10 @@ pub mod pallet { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_target_registrations_per_interval( + pallet_subtensor::Pallet::::set_target_registrations_per_interval( netuid, target_registrations_per_interval, ); @@ -543,13 +544,13 @@ pub mod pallet { netuid: u16, min_burn: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_min_burn(netuid, min_burn); + pallet_subtensor::Pallet::::set_min_burn(netuid, min_burn); log::info!( "MinBurnSet( netuid: {:?} min_burn: {:?} ) ", netuid, @@ -568,13 +569,13 @@ pub mod pallet { netuid: u16, max_burn: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_max_burn(netuid, max_burn); + pallet_subtensor::Pallet::::set_max_burn(netuid, max_burn); log::info!( "MaxBurnSet( netuid: {:?} max_burn: {:?} ) ", netuid, @@ -593,12 +594,12 @@ pub mod pallet { netuid: u16, difficulty: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_difficulty(netuid, difficulty); + pallet_subtensor::Pallet::::set_difficulty(netuid, difficulty); log::info!( "DifficultySet( netuid: {:?} difficulty: {:?} ) ", netuid, @@ -619,15 +620,19 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); ensure!( - max_allowed_validators <= T::Subtensor::get_max_allowed_uids(netuid), + max_allowed_validators + <= pallet_subtensor::Pallet::::get_max_allowed_uids(netuid), Error::::MaxValidatorsLargerThanMaxUIds ); - T::Subtensor::set_max_allowed_validators(netuid, max_allowed_validators); + pallet_subtensor::Pallet::::set_max_allowed_validators( + netuid, + max_allowed_validators, + ); log::info!( "MaxAllowedValidatorsSet( netuid: {:?} max_allowed_validators: {:?} ) ", netuid, @@ -646,13 +651,13 @@ pub mod pallet { netuid: u16, bonds_moving_average: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_bonds_moving_average(netuid, bonds_moving_average); + pallet_subtensor::Pallet::::set_bonds_moving_average(netuid, bonds_moving_average); log::info!( "BondsMovingAverageSet( netuid: {:?} bonds_moving_average: {:?} ) ", netuid, @@ -674,10 +679,13 @@ pub mod pallet { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_max_registrations_per_block(netuid, max_registrations_per_block); + pallet_subtensor::Pallet::::set_max_registrations_per_block( + netuid, + max_registrations_per_block, + ); log::info!( "MaxRegistrationsPerBlock( netuid: {:?} max_registrations_per_block: {:?} ) ", netuid, @@ -701,7 +709,7 @@ pub mod pallet { subnet_owner_cut: u16, ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_subnet_owner_cut(subnet_owner_cut); + pallet_subtensor::Pallet::::set_subnet_owner_cut(subnet_owner_cut); log::info!( "SubnetOwnerCut( subnet_owner_cut: {:?} ) ", subnet_owner_cut @@ -724,7 +732,7 @@ pub mod pallet { rate_limit: u64, ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_network_rate_limit(rate_limit); + pallet_subtensor::Pallet::::set_network_rate_limit(rate_limit); log::info!("NetworkRateLimit( rate_limit: {:?} ) ", rate_limit); Ok(()) } @@ -737,10 +745,10 @@ pub mod pallet { pub fn sudo_set_tempo(origin: OriginFor, netuid: u16, tempo: u16) -> DispatchResult { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_tempo(netuid, tempo); + pallet_subtensor::Pallet::::set_tempo(netuid, tempo); log::info!("TempoSet( netuid: {:?} tempo: {:?} ) ", netuid, tempo); Ok(()) } @@ -756,7 +764,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_total_issuance(total_issuance); + pallet_subtensor::Pallet::::set_total_issuance(total_issuance); Ok(()) } @@ -777,7 +785,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_network_immunity_period(immunity_period); + pallet_subtensor::Pallet::::set_network_immunity_period(immunity_period); log::info!("NetworkImmunityPeriod( period: {:?} ) ", immunity_period); @@ -800,7 +808,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_network_min_lock(lock_cost); + pallet_subtensor::Pallet::::set_network_min_lock(lock_cost); log::info!("NetworkMinLockCost( lock_cost: {:?} ) ", lock_cost); @@ -819,7 +827,7 @@ pub mod pallet { ))] pub fn sudo_set_subnet_limit(origin: OriginFor, max_subnets: u16) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_subnet_limit(max_subnets); + pallet_subtensor::Pallet::::set_max_subnets(max_subnets); log::info!("SubnetLimit( max_subnets: {:?} ) ", max_subnets); @@ -842,7 +850,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_lock_reduction_interval(interval); + pallet_subtensor::Pallet::::set_lock_reduction_interval(interval); log::info!("NetworkLockReductionInterval( interval: {:?} ) ", interval); @@ -861,10 +869,10 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_rao_recycled(netuid, rao_recycled); + pallet_subtensor::Pallet::::set_rao_recycled(netuid, rao_recycled); Ok(()) } @@ -875,7 +883,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_weights_min_stake(origin: OriginFor, min_stake: u64) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_weights_min_stake(min_stake); + pallet_subtensor::Pallet::::set_weights_min_stake(min_stake); Ok(()) } @@ -890,12 +898,12 @@ pub mod pallet { min_stake: u64, ) -> DispatchResult { ensure_root(origin)?; - let prev_min_stake = T::Subtensor::get_nominator_min_required_stake(); + let prev_min_stake = pallet_subtensor::Pallet::::get_nominator_min_required_stake(); log::trace!("Setting minimum stake to: {}", min_stake); - T::Subtensor::set_nominator_min_required_stake(min_stake); + pallet_subtensor::Pallet::::set_nominator_min_required_stake(min_stake); if min_stake > prev_min_stake { log::trace!("Clearing small nominations"); - T::Subtensor::clear_small_nominations(); + pallet_subtensor::Pallet::::clear_small_nominations(); log::trace!("Small nominations cleared"); } Ok(()) @@ -911,7 +919,7 @@ pub mod pallet { tx_rate_limit: u64, ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_tx_delegate_take_rate_limit(tx_rate_limit); + pallet_subtensor::Pallet::::set_tx_delegate_take_rate_limit(tx_rate_limit); log::info!( "TxRateLimitDelegateTakeSet( tx_delegate_take_rate_limit: {:?} ) ", tx_rate_limit @@ -926,7 +934,7 @@ pub mod pallet { #[pallet::weight((0, DispatchClass::Operational, Pays::No))] pub fn sudo_set_min_delegate_take(origin: OriginFor, take: u16) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_min_delegate_take(take); + pallet_subtensor::Pallet::::set_min_delegate_take(take); log::info!("TxMinDelegateTakeSet( tx_min_delegate_take: {:?} ) ", take); Ok(()) } @@ -941,7 +949,9 @@ pub mod pallet { target_stakes_per_interval: u64, ) -> DispatchResult { ensure_root(origin)?; - T::Subtensor::set_target_stakes_per_interval(target_stakes_per_interval); + pallet_subtensor::Pallet::::set_target_stakes_per_interval( + target_stakes_per_interval, + ); log::info!( "TxTargetStakesPerIntervalSet( set_target_stakes_per_interval: {:?} ) ", target_stakes_per_interval @@ -959,14 +969,14 @@ pub mod pallet { netuid: u16, interval: u64, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_commit_reveal_weights_interval(netuid, interval); + pallet_subtensor::Pallet::::set_commit_reveal_weights_interval(netuid, interval); log::info!( "SetWeightCommitInterval( netuid: {:?}, interval: {:?} ) ", netuid, @@ -985,14 +995,14 @@ pub mod pallet { netuid: u16, enabled: bool, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( - T::Subtensor::if_subnet_exist(netuid), + pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - T::Subtensor::set_commit_reveal_weights_enabled(netuid, enabled); + pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, enabled); log::info!("ToggleSetWeightsCommitReveal( netuid: {:?} ) ", netuid); Ok(()) } @@ -1013,8 +1023,8 @@ pub mod pallet { netuid: u16, enabled: bool, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; - T::Subtensor::set_liquid_alpha_enabled(netuid, enabled); + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + pallet_subtensor::Pallet::::set_liquid_alpha_enabled(netuid, enabled); log::info!( "LiquidAlphaEnableToggled( netuid: {:?}, Enabled: {:?} ) ", netuid, @@ -1032,8 +1042,10 @@ pub mod pallet { alpha_low: u16, alpha_high: u16, ) -> DispatchResult { - T::Subtensor::ensure_subnet_owner_or_root(origin.clone(), netuid)?; - T::Subtensor::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + pallet_subtensor::Pallet::::do_set_alpha_values( + origin, netuid, alpha_low, alpha_high, + ) } } } @@ -1052,89 +1064,3 @@ pub trait AuraInterface { impl AuraInterface for () { fn change_authorities(_: BoundedVec) {} } - -/////////////////////////////////////////// - -pub trait SubtensorInterface { - fn set_min_delegate_take(take: u16); - fn set_max_delegate_take(take: u16); - fn set_tx_rate_limit(rate_limit: u64); - fn set_tx_delegate_take_rate_limit(rate_limit: u64); - - fn set_serving_rate_limit(netuid: u16, rate_limit: u64); - - fn set_max_burn(netuid: u16, max_burn: u64); - fn set_min_burn(netuid: u16, min_burn: u64); - fn set_burn(netuid: u16, burn: u64); - - fn set_max_difficulty(netuid: u16, max_diff: u64); - fn set_min_difficulty(netuid: u16, min_diff: u64); - fn set_difficulty(netuid: u16, diff: u64); - - fn set_weights_rate_limit(netuid: u16, rate_limit: u64); - - fn set_weights_version_key(netuid: u16, version: u64); - - fn set_bonds_moving_average(netuid: u16, moving_average: u64); - - fn set_max_allowed_validators(netuid: u16, max_validators: u16); - - fn get_root_netuid() -> u16; - fn if_subnet_exist(netuid: u16) -> bool; - fn create_account_if_non_existent(coldkey: &AccountId, hotkey: &AccountId); - fn coldkey_owns_hotkey(coldkey: &AccountId, hotkey: &AccountId) -> bool; - fn increase_stake_on_coldkey_hotkey_account( - coldkey: &AccountId, - hotkey: &AccountId, - increment: u64, - ); - fn add_balance_to_coldkey_account(coldkey: &AccountId, amount: Balance); - fn get_current_block_as_u64() -> u64; - fn get_subnetwork_n(netuid: u16) -> u16; - fn get_max_allowed_uids(netuid: u16) -> u16; - fn append_neuron(netuid: u16, new_hotkey: &AccountId, block_number: u64); - fn get_neuron_to_prune(netuid: u16) -> u16; - fn replace_neuron(netuid: u16, uid_to_replace: u16, new_hotkey: &AccountId, block_number: u64); - fn set_total_issuance(total_issuance: u64); - fn set_network_immunity_period(net_immunity_period: u64); - fn set_network_min_lock(net_min_lock: u64); - fn set_rao_recycled(netuid: u16, rao_recycled: u64); - fn set_subnet_limit(limit: u16); - fn is_hotkey_registered_on_network(netuid: u16, hotkey: &AccountId) -> bool; - fn set_lock_reduction_interval(interval: u64); - fn set_tempo(netuid: u16, tempo: u16); - fn set_subnet_owner_cut(subnet_owner_cut: u16); - fn set_network_rate_limit(limit: u64); - fn set_max_registrations_per_block(netuid: u16, max_registrations_per_block: u16); - fn set_adjustment_alpha(netuid: u16, adjustment_alpha: u64); - fn set_target_registrations_per_interval(netuid: u16, target_registrations_per_interval: u16); - fn set_network_pow_registration_allowed(netuid: u16, registration_allowed: bool); - fn set_network_registration_allowed(netuid: u16, registration_allowed: bool); - fn set_activity_cutoff(netuid: u16, activity_cutoff: u16); - fn ensure_subnet_owner_or_root(o: RuntimeOrigin, netuid: u16) -> Result<(), DispatchError>; - fn set_rho(netuid: u16, rho: u16); - fn set_kappa(netuid: u16, kappa: u16); - fn set_max_allowed_uids(netuid: u16, max_allowed: u16); - fn set_min_allowed_weights(netuid: u16, min_allowed_weights: u16); - fn set_immunity_period(netuid: u16, immunity_period: u16); - fn set_max_weight_limit(netuid: u16, max_weight_limit: u16); - fn set_scaling_law_power(netuid: u16, scaling_law_power: u16); - fn set_validator_prune_len(netuid: u16, validator_prune_len: u64); - fn set_adjustment_interval(netuid: u16, adjustment_interval: u16); - fn set_weights_set_rate_limit(netuid: u16, weights_set_rate_limit: u64); - fn init_new_network(netuid: u16, tempo: u16); - fn set_weights_min_stake(min_stake: u64); - fn get_nominator_min_required_stake() -> u64; - fn set_nominator_min_required_stake(min_stake: u64); - fn clear_small_nominations(); - fn set_target_stakes_per_interval(target_stakes_per_interval: u64); - fn set_commit_reveal_weights_interval(netuid: u16, interval: u64); - fn set_commit_reveal_weights_enabled(netuid: u16, enabled: bool); - fn set_liquid_alpha_enabled(netuid: u16, enabled: bool); - fn do_set_alpha_values( - origin: RuntimeOrigin, - netuid: u16, - alpha_low: u16, - alpha_high: u16, - ) -> Result<(), DispatchError>; -} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4abd124f..d302652d4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -948,291 +948,12 @@ impl pallet_admin_utils::AuraInterface> for AuraPalletIntrf } } -pub struct SubtensorInterface; - -impl - pallet_admin_utils::SubtensorInterface< - AccountId, - as frame_support::traits::Currency>::Balance, - RuntimeOrigin, - > for SubtensorInterface -{ - fn set_max_delegate_take(max_take: u16) { - SubtensorModule::set_max_delegate_take(max_take); - } - - fn set_min_delegate_take(max_take: u16) { - SubtensorModule::set_min_delegate_take(max_take); - } - - fn set_tx_rate_limit(rate_limit: u64) { - SubtensorModule::set_tx_rate_limit(rate_limit); - } - - fn set_tx_delegate_take_rate_limit(rate_limit: u64) { - SubtensorModule::set_tx_delegate_take_rate_limit(rate_limit); - } - - fn set_serving_rate_limit(netuid: u16, rate_limit: u64) { - SubtensorModule::set_serving_rate_limit(netuid, rate_limit); - } - - fn set_max_burn(netuid: u16, max_burn: u64) { - SubtensorModule::set_max_burn(netuid, max_burn); - } - - fn set_min_burn(netuid: u16, min_burn: u64) { - SubtensorModule::set_min_burn(netuid, min_burn); - } - - fn set_burn(netuid: u16, burn: u64) { - SubtensorModule::set_burn(netuid, burn); - } - - fn set_max_difficulty(netuid: u16, max_diff: u64) { - SubtensorModule::set_max_difficulty(netuid, max_diff); - } - - fn set_min_difficulty(netuid: u16, min_diff: u64) { - SubtensorModule::set_min_difficulty(netuid, min_diff); - } - - fn set_difficulty(netuid: u16, diff: u64) { - SubtensorModule::set_difficulty(netuid, diff); - } - - fn set_weights_rate_limit(netuid: u16, rate_limit: u64) { - SubtensorModule::set_weights_set_rate_limit(netuid, rate_limit); - } - - fn set_weights_version_key(netuid: u16, version: u64) { - SubtensorModule::set_weights_version_key(netuid, version); - } - - fn set_bonds_moving_average(netuid: u16, moving_average: u64) { - SubtensorModule::set_bonds_moving_average(netuid, moving_average); - } - - fn set_max_allowed_validators(netuid: u16, max_validators: u16) { - SubtensorModule::set_max_allowed_validators(netuid, max_validators); - } - - fn get_root_netuid() -> u16 { - SubtensorModule::get_root_netuid() - } - - fn if_subnet_exist(netuid: u16) -> bool { - SubtensorModule::if_subnet_exist(netuid) - } - - fn create_account_if_non_existent(coldkey: &AccountId, hotkey: &AccountId) { - SubtensorModule::create_account_if_non_existent(coldkey, hotkey) - } - - fn coldkey_owns_hotkey(coldkey: &AccountId, hotkey: &AccountId) -> bool { - SubtensorModule::coldkey_owns_hotkey(coldkey, hotkey) - } - - fn increase_stake_on_coldkey_hotkey_account( - coldkey: &AccountId, - hotkey: &AccountId, - increment: u64, - ) { - SubtensorModule::increase_stake_on_coldkey_hotkey_account(coldkey, hotkey, increment); - } - - fn add_balance_to_coldkey_account(coldkey: &AccountId, amount: Balance) { - SubtensorModule::add_balance_to_coldkey_account(coldkey, amount); - } - - fn get_current_block_as_u64() -> u64 { - SubtensorModule::get_current_block_as_u64() - } - - fn get_subnetwork_n(netuid: u16) -> u16 { - SubtensorModule::get_subnetwork_n(netuid) - } - - fn get_max_allowed_uids(netuid: u16) -> u16 { - SubtensorModule::get_max_allowed_uids(netuid) - } - - fn append_neuron(netuid: u16, new_hotkey: &AccountId, block_number: u64) { - SubtensorModule::append_neuron(netuid, new_hotkey, block_number) - } - - fn get_neuron_to_prune(netuid: u16) -> u16 { - SubtensorModule::get_neuron_to_prune(netuid) - } - - fn replace_neuron(netuid: u16, uid_to_replace: u16, new_hotkey: &AccountId, block_number: u64) { - SubtensorModule::replace_neuron(netuid, uid_to_replace, new_hotkey, block_number); - } - - fn set_total_issuance(total_issuance: u64) { - SubtensorModule::set_total_issuance(total_issuance); - } - - fn set_network_immunity_period(net_immunity_period: u64) { - SubtensorModule::set_network_immunity_period(net_immunity_period); - } - - fn set_network_min_lock(net_min_lock: u64) { - SubtensorModule::set_network_min_lock(net_min_lock); - } - - fn set_subnet_limit(limit: u16) { - SubtensorModule::set_max_subnets(limit); - } - - fn set_lock_reduction_interval(interval: u64) { - SubtensorModule::set_lock_reduction_interval(interval); - } - - fn set_tempo(netuid: u16, tempo: u16) { - SubtensorModule::set_tempo(netuid, tempo); - } - - fn set_subnet_owner_cut(subnet_owner_cut: u16) { - SubtensorModule::set_subnet_owner_cut(subnet_owner_cut); - } - - fn set_network_rate_limit(limit: u64) { - SubtensorModule::set_network_rate_limit(limit); - } - - fn set_max_registrations_per_block(netuid: u16, max_registrations_per_block: u16) { - SubtensorModule::set_max_registrations_per_block(netuid, max_registrations_per_block); - } - - fn set_adjustment_alpha(netuid: u16, adjustment_alpha: u64) { - SubtensorModule::set_adjustment_alpha(netuid, adjustment_alpha); - } - - fn set_target_registrations_per_interval(netuid: u16, target_registrations_per_interval: u16) { - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - } - - fn set_network_pow_registration_allowed(netuid: u16, registration_allowed: bool) { - SubtensorModule::set_network_pow_registration_allowed(netuid, registration_allowed); - } - - fn set_network_registration_allowed(netuid: u16, registration_allowed: bool) { - SubtensorModule::set_network_registration_allowed(netuid, registration_allowed); - } - - fn set_activity_cutoff(netuid: u16, activity_cutoff: u16) { - SubtensorModule::set_activity_cutoff(netuid, activity_cutoff); - } - - fn ensure_subnet_owner_or_root(o: RuntimeOrigin, netuid: u16) -> Result<(), DispatchError> { - SubtensorModule::ensure_subnet_owner_or_root(o, netuid) - } - - fn set_rho(netuid: u16, rho: u16) { - SubtensorModule::set_rho(netuid, rho); - } - - fn set_kappa(netuid: u16, kappa: u16) { - SubtensorModule::set_kappa(netuid, kappa); - } - - fn set_max_allowed_uids(netuid: u16, max_allowed: u16) { - SubtensorModule::set_max_allowed_uids(netuid, max_allowed); - } - - fn set_min_allowed_weights(netuid: u16, min_allowed_weights: u16) { - SubtensorModule::set_min_allowed_weights(netuid, min_allowed_weights); - } - - fn set_immunity_period(netuid: u16, immunity_period: u16) { - SubtensorModule::set_immunity_period(netuid, immunity_period); - } - - fn set_max_weight_limit(netuid: u16, max_weight_limit: u16) { - SubtensorModule::set_max_weight_limit(netuid, max_weight_limit); - } - - fn set_scaling_law_power(netuid: u16, scaling_law_power: u16) { - SubtensorModule::set_scaling_law_power(netuid, scaling_law_power); - } - - fn set_validator_prune_len(netuid: u16, validator_prune_len: u64) { - SubtensorModule::set_validator_prune_len(netuid, validator_prune_len); - } - - fn set_adjustment_interval(netuid: u16, adjustment_interval: u16) { - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - } - - fn set_weights_set_rate_limit(netuid: u16, weights_set_rate_limit: u64) { - SubtensorModule::set_weights_set_rate_limit(netuid, weights_set_rate_limit); - } - - fn set_rao_recycled(netuid: u16, rao_recycled: u64) { - SubtensorModule::set_rao_recycled(netuid, rao_recycled); - } - - fn is_hotkey_registered_on_network(netuid: u16, hotkey: &AccountId) -> bool { - SubtensorModule::is_hotkey_registered_on_network(netuid, hotkey) - } - - fn init_new_network(netuid: u16, tempo: u16) { - SubtensorModule::init_new_network(netuid, tempo); - } - - fn set_weights_min_stake(min_stake: u64) { - SubtensorModule::set_weights_min_stake(min_stake); - } - - fn clear_small_nominations() { - SubtensorModule::clear_small_nominations(); - } - - fn set_nominator_min_required_stake(min_stake: u64) { - SubtensorModule::set_nominator_min_required_stake(min_stake); - } - - fn get_nominator_min_required_stake() -> u64 { - SubtensorModule::get_nominator_min_required_stake() - } - - fn set_target_stakes_per_interval(target_stakes_per_interval: u64) { - SubtensorModule::set_target_stakes_per_interval(target_stakes_per_interval) - } - - fn set_commit_reveal_weights_interval(netuid: u16, interval: u64) { - SubtensorModule::set_commit_reveal_weights_interval(netuid, interval); - } - - fn set_commit_reveal_weights_enabled(netuid: u16, enabled: bool) { - SubtensorModule::set_commit_reveal_weights_enabled(netuid, enabled); - } - - fn set_liquid_alpha_enabled(netuid: u16, enabled: bool) { - SubtensorModule::set_liquid_alpha_enabled(netuid, enabled); - } - - fn do_set_alpha_values( - origin: RuntimeOrigin, - netuid: u16, - alpha_low: u16, - alpha_high: u16, - ) -> Result<(), DispatchError> { - SubtensorModule::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) - } -} - impl pallet_admin_utils::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AuthorityId = AuraId; type MaxAuthorities = ConstU32<32>; type Aura = AuraPalletIntrf; type Balance = Balance; - type Subtensor = SubtensorInterface; type WeightInfo = pallet_admin_utils::weights::SubstrateWeight; } From b0202f4a2fd1ae8bac71214a374eff9f5adc0bb2 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 15:53:58 +0900 Subject: [PATCH 120/269] Remove SubtensorInterface in tests --- pallets/admin-utils/tests/mock.rs | 274 +----------------------------- 1 file changed, 1 insertion(+), 273 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index dbf88bdfa..3b6ec0ccd 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -12,7 +12,7 @@ use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, DispatchError, + BuildStorage, }; type Block = frame_system::mocking::MockBlock; @@ -216,284 +216,12 @@ impl pallet_balances::Config for Test { type RuntimeHoldReason = (); } -pub struct SubtensorIntrf; - -impl pallet_admin_utils::SubtensorInterface for SubtensorIntrf { - fn set_max_delegate_take(default_take: u16) { - SubtensorModule::set_max_delegate_take(default_take); - } - - fn set_min_delegate_take(default_take: u16) { - SubtensorModule::set_min_delegate_take(default_take); - } - - fn set_tx_rate_limit(rate_limit: u64) { - SubtensorModule::set_tx_rate_limit(rate_limit); - } - - fn set_tx_delegate_take_rate_limit(rate_limit: u64) { - SubtensorModule::set_tx_delegate_take_rate_limit(rate_limit); - } - - fn set_serving_rate_limit(netuid: u16, rate_limit: u64) { - SubtensorModule::set_serving_rate_limit(netuid, rate_limit); - } - - fn set_max_burn(netuid: u16, max_burn: u64) { - SubtensorModule::set_max_burn(netuid, max_burn); - } - - fn set_min_burn(netuid: u16, min_burn: u64) { - SubtensorModule::set_min_burn(netuid, min_burn); - } - - fn set_burn(netuid: u16, burn: u64) { - SubtensorModule::set_burn(netuid, burn); - } - - fn set_max_difficulty(netuid: u16, max_diff: u64) { - SubtensorModule::set_max_difficulty(netuid, max_diff); - } - - fn set_min_difficulty(netuid: u16, min_diff: u64) { - SubtensorModule::set_min_difficulty(netuid, min_diff); - } - - fn set_difficulty(netuid: u16, diff: u64) { - SubtensorModule::set_difficulty(netuid, diff); - } - - fn set_weights_rate_limit(netuid: u16, rate_limit: u64) { - SubtensorModule::set_weights_set_rate_limit(netuid, rate_limit); - } - - fn set_weights_version_key(netuid: u16, version: u64) { - SubtensorModule::set_weights_version_key(netuid, version); - } - - fn set_bonds_moving_average(netuid: u16, moving_average: u64) { - SubtensorModule::set_bonds_moving_average(netuid, moving_average); - } - - fn set_max_allowed_validators(netuid: u16, max_validators: u16) { - SubtensorModule::set_max_allowed_validators(netuid, max_validators); - } - - fn get_root_netuid() -> u16 { - SubtensorModule::get_root_netuid() - } - - fn if_subnet_exist(netuid: u16) -> bool { - SubtensorModule::if_subnet_exist(netuid) - } - - fn create_account_if_non_existent(coldkey: &AccountId, hotkey: &AccountId) { - SubtensorModule::create_account_if_non_existent(coldkey, hotkey) - } - - fn coldkey_owns_hotkey(coldkey: &AccountId, hotkey: &AccountId) -> bool { - SubtensorModule::coldkey_owns_hotkey(coldkey, hotkey) - } - - fn increase_stake_on_coldkey_hotkey_account( - coldkey: &AccountId, - hotkey: &AccountId, - increment: u64, - ) { - SubtensorModule::increase_stake_on_coldkey_hotkey_account(coldkey, hotkey, increment); - } - - fn add_balance_to_coldkey_account(coldkey: &AccountId, amount: Balance) { - SubtensorModule::add_balance_to_coldkey_account(coldkey, amount); - } - - fn get_current_block_as_u64() -> u64 { - SubtensorModule::get_current_block_as_u64() - } - - fn get_subnetwork_n(netuid: u16) -> u16 { - SubtensorModule::get_subnetwork_n(netuid) - } - - fn get_max_allowed_uids(netuid: u16) -> u16 { - SubtensorModule::get_max_allowed_uids(netuid) - } - - fn append_neuron(netuid: u16, new_hotkey: &AccountId, block_number: u64) { - SubtensorModule::append_neuron(netuid, new_hotkey, block_number) - } - - fn get_neuron_to_prune(netuid: u16) -> u16 { - SubtensorModule::get_neuron_to_prune(netuid) - } - - fn replace_neuron(netuid: u16, uid_to_replace: u16, new_hotkey: &AccountId, block_number: u64) { - SubtensorModule::replace_neuron(netuid, uid_to_replace, new_hotkey, block_number); - } - - fn set_total_issuance(total_issuance: u64) { - SubtensorModule::set_total_issuance(total_issuance); - } - - fn set_network_immunity_period(net_immunity_period: u64) { - SubtensorModule::set_network_immunity_period(net_immunity_period); - } - - fn set_network_min_lock(net_min_lock: u64) { - SubtensorModule::set_network_min_lock(net_min_lock); - } - - fn set_subnet_limit(limit: u16) { - SubtensorModule::set_max_subnets(limit); - } - - fn set_lock_reduction_interval(interval: u64) { - SubtensorModule::set_lock_reduction_interval(interval); - } - - fn set_tempo(netuid: u16, tempo: u16) { - SubtensorModule::set_tempo(netuid, tempo); - } - - fn set_subnet_owner_cut(subnet_owner_cut: u16) { - SubtensorModule::set_subnet_owner_cut(subnet_owner_cut); - } - - fn set_network_rate_limit(limit: u64) { - SubtensorModule::set_network_rate_limit(limit); - } - - fn set_max_registrations_per_block(netuid: u16, max_registrations_per_block: u16) { - SubtensorModule::set_max_registrations_per_block(netuid, max_registrations_per_block); - } - - fn set_adjustment_alpha(netuid: u16, adjustment_alpha: u64) { - SubtensorModule::set_adjustment_alpha(netuid, adjustment_alpha); - } - - fn set_target_registrations_per_interval(netuid: u16, target_registrations_per_interval: u16) { - SubtensorModule::set_target_registrations_per_interval( - netuid, - target_registrations_per_interval, - ); - } - - fn set_network_pow_registration_allowed(netuid: u16, registration_allowed: bool) { - SubtensorModule::set_network_pow_registration_allowed(netuid, registration_allowed); - } - - fn set_network_registration_allowed(netuid: u16, registration_allowed: bool) { - SubtensorModule::set_network_pow_registration_allowed(netuid, registration_allowed); - } - - fn set_activity_cutoff(netuid: u16, activity_cutoff: u16) { - SubtensorModule::set_activity_cutoff(netuid, activity_cutoff); - } - - fn ensure_subnet_owner_or_root(o: RuntimeOrigin, netuid: u16) -> Result<(), DispatchError> { - SubtensorModule::ensure_subnet_owner_or_root(o, netuid) - } - - fn set_rho(netuid: u16, rho: u16) { - SubtensorModule::set_rho(netuid, rho); - } - - fn set_kappa(netuid: u16, kappa: u16) { - SubtensorModule::set_kappa(netuid, kappa); - } - - fn set_max_allowed_uids(netuid: u16, max_allowed: u16) { - SubtensorModule::set_max_allowed_uids(netuid, max_allowed); - } - - fn set_min_allowed_weights(netuid: u16, min_allowed_weights: u16) { - SubtensorModule::set_min_allowed_weights(netuid, min_allowed_weights); - } - - fn set_immunity_period(netuid: u16, immunity_period: u16) { - SubtensorModule::set_immunity_period(netuid, immunity_period); - } - - fn set_max_weight_limit(netuid: u16, max_weight_limit: u16) { - SubtensorModule::set_max_weight_limit(netuid, max_weight_limit); - } - - fn set_scaling_law_power(netuid: u16, scaling_law_power: u16) { - SubtensorModule::set_scaling_law_power(netuid, scaling_law_power); - } - - fn set_validator_prune_len(netuid: u16, validator_prune_len: u64) { - SubtensorModule::set_validator_prune_len(netuid, validator_prune_len); - } - - fn set_adjustment_interval(netuid: u16, adjustment_interval: u16) { - SubtensorModule::set_adjustment_interval(netuid, adjustment_interval); - } - - fn set_weights_set_rate_limit(netuid: u16, weights_set_rate_limit: u64) { - SubtensorModule::set_weights_set_rate_limit(netuid, weights_set_rate_limit); - } - - fn set_rao_recycled(netuid: u16, rao_recycled: u64) { - SubtensorModule::set_rao_recycled(netuid, rao_recycled); - } - - fn is_hotkey_registered_on_network(netuid: u16, hotkey: &AccountId) -> bool { - SubtensorModule::is_hotkey_registered_on_network(netuid, hotkey) - } - - fn init_new_network(netuid: u16, tempo: u16) { - SubtensorModule::init_new_network(netuid, tempo); - } - - fn set_weights_min_stake(min_stake: u64) { - SubtensorModule::set_weights_min_stake(min_stake); - } - - fn set_nominator_min_required_stake(min_stake: u64) { - SubtensorModule::set_nominator_min_required_stake(min_stake); - } - - fn get_nominator_min_required_stake() -> u64 { - SubtensorModule::get_nominator_min_required_stake() - } - - fn clear_small_nominations() { - SubtensorModule::clear_small_nominations(); - } - - fn set_target_stakes_per_interval(target_stakes_per_interval: u64) { - SubtensorModule::set_target_stakes_per_interval(target_stakes_per_interval); - } - - fn set_commit_reveal_weights_interval(netuid: u16, interval: u64) { - SubtensorModule::set_commit_reveal_weights_interval(netuid, interval); - } - - fn set_commit_reveal_weights_enabled(netuid: u16, enabled: bool) { - SubtensorModule::set_commit_reveal_weights_enabled(netuid, enabled); - } - - fn set_liquid_alpha_enabled(netuid: u16, enabled: bool) { - SubtensorModule::set_liquid_alpha_enabled(netuid, enabled); - } - fn do_set_alpha_values( - origin: RuntimeOrigin, - netuid: u16, - alpha_low: u16, - alpha_high: u16, - ) -> Result<(), DispatchError> { - SubtensorModule::do_set_alpha_values(origin, netuid, alpha_low, alpha_high) - } -} - impl pallet_admin_utils::Config for Test { type RuntimeEvent = RuntimeEvent; type AuthorityId = AuraId; type MaxAuthorities = ConstU32<32>; type Aura = (); type Balance = Balance; - type Subtensor = SubtensorIntrf; type WeightInfo = (); } From 83d680d1e3785c8e9a2f3ad34e46950ea46cffb7 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 15:55:22 +0900 Subject: [PATCH 121/269] Remove unused import --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d302652d4..66cea80d4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -15,7 +15,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, - pallet_prelude::{DispatchError, Get}, + pallet_prelude::Get, traits::{fungible::HoldConsideration, Contains, LinearStoragePrice}, }; use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, RawOrigin}; From 063cf574cca294b8f67c7c9ee84b915879d20599 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 16:05:00 +0900 Subject: [PATCH 122/269] Fix clippy --- pallets/collective/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 96040f99c..66c55036d 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -951,6 +951,7 @@ impl, I: 'static> Pallet { /// /// If not `approved`: /// - one event deposited. + /// /// Two removals, one mutation. /// Computation and i/o `O(P)` where: /// - `P` is number of active proposals From af4bf3527fcea4fd8586f0c37039d12e8758d614 Mon Sep 17 00:00:00 2001 From: open-junius Date: Fri, 2 Aug 2024 15:20:51 +0800 Subject: [PATCH 123/269] fix zepter --- pallets/admin-utils/Cargo.toml | 2 ++ pallets/subtensor/Cargo.toml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index ff54989cd..c67c00914 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -51,12 +51,14 @@ std = [ "pallet-subtensor/std", "sp-consensus-aura/std", "pallet-balances/std", + "pallet-scheduler/std", "sp-runtime/std", "sp-tracing/std", "sp-weights/std", "log/std", "sp-core/std", "sp-io/std", + "sp-std/std", "substrate-fixed/std", ] runtime-benchmarks = [ diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 2baee6127..3c0bd71c1 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -70,6 +70,8 @@ std = [ "pallet-membership/std", "substrate-fixed/std", "pallet-balances/std", + "pallet-preimage/std", + "pallet-scheduler/std", "pallet-transaction-payment/std", "pallet-utility/std", "sp-core/std", From 0bce83eaeb2a0d4de86dfab4cf68bf431c7625c1 Mon Sep 17 00:00:00 2001 From: open-junius Date: Fri, 2 Aug 2024 15:25:41 +0800 Subject: [PATCH 124/269] fix clippy --- runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 12fb55586..b606d2eb1 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -538,6 +538,7 @@ impl pallet_membership::Config for Runtime { } // We call our top K delegates membership Senate +#[allow(dead_code)] type SenateMembership = pallet_membership::Instance2; impl pallet_membership::Config for Runtime { type RuntimeEvent = RuntimeEvent; From 5181bef8424c1ce31a2c0a30f0e14b64a4c69eec Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 17:41:27 +0900 Subject: [PATCH 125/269] Remove references to T::Subtensor in benchmarks --- pallets/admin-utils/src/benchmarking.rs | 48 ++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 0158311f7..59f16f6c4 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -47,7 +47,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_difficulty() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 10000u64/*max_difficulty*/)/*sudo_set_max_difficulty*/; @@ -55,7 +55,7 @@ mod benchmarks { #[benchmark] fn sudo_set_min_difficulty() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 1000u64/*min_difficulty*/)/*sudo_set_min_difficulty*/; @@ -63,7 +63,7 @@ mod benchmarks { #[benchmark] fn sudo_set_weights_set_rate_limit() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 3u64/*rate_limit*/)/*sudo_set_weights_set_rate_limit*/; @@ -71,7 +71,7 @@ mod benchmarks { #[benchmark] fn sudo_set_weights_version_key() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 1u64/*version_key*/)/*sudo_set_weights_version_key*/; @@ -79,7 +79,7 @@ mod benchmarks { #[benchmark] fn sudo_set_bonds_moving_average() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 100u64/*bonds_moving_average*/)/*sudo_set_bonds_moving_average*/; @@ -87,7 +87,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_allowed_validators() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 10u16/*max_allowed_validators*/)/*sudo_set_max_allowed_validators*/; @@ -95,7 +95,7 @@ mod benchmarks { #[benchmark] fn sudo_set_difficulty() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 1200000u64/*difficulty*/)/*sudo_set_difficulty*/; @@ -103,7 +103,7 @@ mod benchmarks { #[benchmark] fn sudo_set_adjustment_interval() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 12u16/*adjustment_interval*/)/*sudo_set_adjustment_interval*/; @@ -111,7 +111,7 @@ mod benchmarks { #[benchmark] fn sudo_set_target_registrations_per_interval() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 300u16/*target_registrations*/)/*sudo_set_target_registrations_per_interval*/; @@ -119,7 +119,7 @@ mod benchmarks { #[benchmark] fn sudo_set_activity_cutoff() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 300u16/*activity_cutoff*/)/*sudo_set_activity_cutoff*/; @@ -127,7 +127,7 @@ mod benchmarks { #[benchmark] fn sudo_set_rho() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 300u16/*rho*/)/*sudo_set_rho*/; @@ -135,7 +135,7 @@ mod benchmarks { #[benchmark] fn sudo_set_kappa() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 3u16/*kappa*/)/*set_kappa*/; @@ -143,7 +143,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_allowed_uids() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 4097u16/*max_allowed_uids*/)/*sudo_set_max_allowed_uids*/; @@ -151,7 +151,7 @@ mod benchmarks { #[benchmark] fn sudo_set_min_allowed_weights() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 10u16/*max_allowed_uids*/)/*sudo_set_min_allowed_weights*/; @@ -159,7 +159,7 @@ mod benchmarks { #[benchmark] fn sudo_set_immunity_period() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 100u16/*immunity_period*/)/*sudo_set_immunity_period*/; @@ -167,7 +167,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_weight_limit() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 100u16/*max_weight_limit*/)/*sudo_set_max_weight_limit*/; @@ -175,7 +175,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_registrations_per_block() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 100u16/*max_registrations*/)/*sudo_set_max_registrations_per_block*/; @@ -183,7 +183,7 @@ mod benchmarks { #[benchmark] fn sudo_set_max_burn() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 10u64/*max_burn*/)/*sudo_set_max_burn*/; @@ -191,7 +191,7 @@ mod benchmarks { #[benchmark] fn sudo_set_min_burn() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 10u64/*min_burn*/)/*sudo_set_min_burn*/; @@ -199,7 +199,7 @@ mod benchmarks { #[benchmark] fn sudo_set_network_registration_allowed() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, true/*registration_allowed*/)/*sudo_set_network_registration_allowed*/; @@ -212,13 +212,13 @@ mod benchmarks { let tempo: u16 = 15; let modality: u16 = 0; - T::Subtensor::init_new_network(netuid, tempo); + pallet_subtensor::Pallet::::init_new_network(netuid, tempo); }: sudo_set_tempo(RawOrigin::>::Root, netuid, tempo) */ #[benchmark] fn sudo_set_tempo() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 1u16/*tempo*/)/*sudo_set_tempo*/; @@ -226,7 +226,7 @@ mod benchmarks { #[benchmark] fn sudo_set_commit_reveal_weights_interval() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 3u64/*interval*/)/*set_commit_reveal_weights_interval()*/; @@ -234,7 +234,7 @@ mod benchmarks { #[benchmark] fn sudo_set_commit_reveal_weights_enabled() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, true/*enabled*/)/*set_commit_reveal_weights_enabled*/; From 06013f87ea9eb3884f4300c8bc0d151623266a38 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 2 Aug 2024 14:38:52 +0400 Subject: [PATCH 126/269] chore: remove childkey take removal --- pallets/admin-utils/tests/mock.rs | 5 +- pallets/subtensor/src/staking/set_children.rs | 5 -- pallets/subtensor/tests/children.rs | 52 ------------------- 3 files changed, 2 insertions(+), 60 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 0142a435a..acb9c8a4a 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -79,9 +79,8 @@ parameter_types! { pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. pub const InitialMinDelegateTake: u16 = 5_898; // 9%; - pub const InitialDefaultChildKeyTake: u16 = 11_796; // 18% honest number. - pub const InitialMinChildKeyTake: u16 = 5_898; // 9%; - pub const InitialMinTake: u16 = 5_898; // 9%; + pub const InitialDefaultChildKeyTake: u16 = 0; // Allow 0 % + pub const InitialMinChildKeyTake: u16 = 0; // Allow 0 % pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index f60298d57..fda0ae27b 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -130,11 +130,6 @@ impl Pallet { // --- 7.1. Insert my new children + proportion list into the map. ChildKeys::::insert(hotkey.clone(), netuid, children.clone()); - if children.is_empty() { - // If there are no children, remove the ChildkeyTake value - ChildkeyTake::::remove(hotkey.clone(), netuid); - } - // --- 7.2. Update the parents list for my new children. for (proportion, new_child_i) in children.clone().iter() { // --- 8.2.1. Get the child's parents on this network. diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 2a15d05d6..6a37b5bea 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1622,55 +1622,3 @@ fn test_get_parents_chain() { ); }); } - -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_removal_on_empty_children --exact --nocapture -#[test] -fn test_childkey_take_removal_on_empty_children() { - new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - let hotkey = U256::from(2); - let child = U256::from(3); - let netuid: u16 = 1; - let proportion: u64 = 1000; - - // Add network and register hotkey - add_network(netuid, 13, 0); - register_ok_neuron(netuid, hotkey, coldkey, 0); - - // Set a child and childkey take - assert_ok!(SubtensorModule::do_set_children( - RuntimeOrigin::signed(coldkey), - hotkey, - netuid, - vec![(proportion, child)] - )); - - let take: u16 = u16::MAX / 10; // 10% take - assert_ok!(SubtensorModule::set_childkey_take( - RuntimeOrigin::signed(coldkey), - hotkey, - netuid, - take - )); - - // Verify childkey take is set - assert_eq!(SubtensorModule::get_childkey_take(&hotkey, netuid), take); - - // Remove all children - assert_ok!(SubtensorModule::do_set_children( - RuntimeOrigin::signed(coldkey), - hotkey, - netuid, - vec![] - )); - - // Verify children are removed - let children = SubtensorModule::get_children(&hotkey, netuid); - assert!(children.is_empty()); - - // Verify childkey take is removed - assert_eq!(SubtensorModule::get_childkey_take(&hotkey, netuid), 0); - // Verify childkey take storage is empty - assert_eq!(ChildkeyTake::::get(hotkey, netuid), 0); - }); -} From 7a935f9853a87b095cc84b009c45677f2d682a19 Mon Sep 17 00:00:00 2001 From: Keith Date: Fri, 2 Aug 2024 22:28:44 +0900 Subject: [PATCH 127/269] cargo fmt --- pallets/admin-utils/src/benchmarking.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 59f16f6c4..f12836af9 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -135,7 +135,10 @@ mod benchmarks { #[benchmark] fn sudo_set_kappa() { - pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network( + 1u16, /*netuid*/ + 1u16, /*sudo_tempo*/ + ); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 3u16/*kappa*/)/*set_kappa*/; @@ -226,7 +229,10 @@ mod benchmarks { #[benchmark] fn sudo_set_commit_reveal_weights_interval() { - pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network( + 1u16, /*netuid*/ + 1u16, /*sudo_tempo*/ + ); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 3u64/*interval*/)/*set_commit_reveal_weights_interval()*/; @@ -234,7 +240,10 @@ mod benchmarks { #[benchmark] fn sudo_set_commit_reveal_weights_enabled() { - pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network( + 1u16, /*netuid*/ + 1u16, /*sudo_tempo*/ + ); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, true/*enabled*/)/*set_commit_reveal_weights_enabled*/; From 7058b51beb737d92b73ee8289f9f6bd71a85d371 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 2 Aug 2024 07:59:35 -0700 Subject: [PATCH 128/269] update & move tests --- pallets/subtensor/tests/serving.rs | 133 -------------------- pallets/subtensor/tests/swap_coldkey.rs | 157 ++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 133 deletions(-) diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 8dd371acb..b0eada8e6 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -827,136 +827,3 @@ fn test_migrate_set_hotkey_identities() { ); }); } - -#[test] -fn test_coldkey_swap_delegate_identity_updated() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let netuid = 1; - let burn_cost = 10; - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey), - netuid, - old_coldkey - )); - - let name: Vec = b"The Third Coolest Identity".to_vec(); - let identity: ChainIdentity = ChainIdentity { - name: name.clone(), - url: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - Identities::::insert(old_coldkey, identity.clone()); - - assert!(Identities::::get(old_coldkey).is_some()); - assert!(Identities::::get(new_coldkey).is_none()); - - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &new_coldkey - )); - - assert!(Identities::::get(old_coldkey).is_none()); - assert!(Identities::::get(new_coldkey).is_some()); - assert_eq!( - Identities::::get(new_coldkey).expect("Expected an Identity"), - identity - ); - }); -} - -#[test] -fn test_coldkey_swap_no_identity_no_changes() { - new_test_ext(1).execute_with(|| { - let old_coldkey = U256::from(1); - let new_coldkey = U256::from(2); - - let netuid = 1; - let burn_cost = 10; - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey), - netuid, - old_coldkey - )); - - // Ensure the old coldkey does not have an identity before the swap - assert!(Identities::::get(old_coldkey).is_none()); - - // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &new_coldkey, - )); - - // Ensure no identities have been changed - assert!(Identities::::get(old_coldkey).is_none()); - assert!(Identities::::get(new_coldkey).is_none()); - }); -} - -#[test] -fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { - new_test_ext(1).execute_with(|| { - let old_coldkey_2 = U256::from(3); - let new_coldkey_2 = U256::from(4); - - let netuid = 1; - let burn_cost = 10; - let tempo = 1; - - SubtensorModule::set_burn(netuid, burn_cost); - add_network(netuid, tempo, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey_2, 100_000_000_000); - - assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey_2), - netuid, - old_coldkey_2 - )); - - let name: Vec = b"The Coolest Identity".to_vec(); - let identity: ChainIdentity = ChainIdentity { - name: name.clone(), - url: vec![], - image: vec![], - discord: vec![], - description: vec![], - additional: vec![], - }; - - Identities::::insert(new_coldkey_2, identity.clone()); - // Ensure the new coldkey does have an identity before the swap - assert!(Identities::::get(new_coldkey_2).is_some()); - assert!(Identities::::get(old_coldkey_2).is_none()); - - // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey_2), - &new_coldkey_2, - )); - - // Ensure no identities have been changed - assert!(Identities::::get(old_coldkey_2).is_none()); - assert!(Identities::::get(new_coldkey_2).is_some()); - }); -} diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 9203e2b3f..a4fddfe90 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -523,6 +523,22 @@ fn test_do_swap_coldkey_success() { stake_amount2 )); + // Insert an Identity + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(old_coldkey, identity.clone()); + + assert!(Identities::::get(old_coldkey).is_some()); + assert!(Identities::::get(new_coldkey).is_none()); + // Log state after adding stake log::info!( "Total stake after adding: {}", @@ -594,6 +610,14 @@ fn test_do_swap_coldkey_success() { "Total stake changed unexpectedly" ); + // Verify identities were swapped + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert_eq!( + Identities::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + // Verify event emission System::assert_last_event( Event::ColdkeySwapped { @@ -1286,3 +1310,136 @@ fn test_coldkey_delegations() { assert_eq!(Stake::::get(delegate, coldkey), 0); }); } + +#[test] +fn test_coldkey_swap_delegate_identity_updated() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + let name: Vec = b"The Third Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(old_coldkey, identity.clone()); + + assert!(Identities::::get(old_coldkey).is_some()); + assert!(Identities::::get(new_coldkey).is_none()); + + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &new_coldkey + )); + + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert_eq!( + Identities::::get(new_coldkey).expect("Expected an Identity"), + identity + ); + }); +} + +#[test] +fn test_coldkey_swap_no_identity_no_changes() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(1); + let new_coldkey = U256::from(2); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey), + netuid, + old_coldkey + )); + + // Ensure the old coldkey does not have an identity before the swap + assert!(Identities::::get(old_coldkey).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + &new_coldkey, + )); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_none()); + }); +} + +#[test] +fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { + new_test_ext(1).execute_with(|| { + let old_coldkey_2 = U256::from(3); + let new_coldkey_2 = U256::from(4); + + let netuid = 1; + let burn_cost = 10; + let tempo = 1; + + SubtensorModule::set_burn(netuid, burn_cost); + add_network(netuid, tempo, 0); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey_2, 100_000_000_000); + + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(old_coldkey_2), + netuid, + old_coldkey_2 + )); + + let name: Vec = b"The Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(new_coldkey_2, identity.clone()); + // Ensure the new coldkey does have an identity before the swap + assert!(Identities::::get(new_coldkey_2).is_some()); + assert!(Identities::::get(old_coldkey_2).is_none()); + + // Perform the coldkey swap + assert_ok!(SubtensorModule::do_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey_2), + &new_coldkey_2, + )); + + // Ensure no identities have been changed + assert!(Identities::::get(old_coldkey_2).is_none()); + assert!(Identities::::get(new_coldkey_2).is_some()); + }); +} From 2fc87ca7b8eed5f2ae2a72ee924bd30632b43339 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:18:33 -0700 Subject: [PATCH 129/269] add swap_coldkey benchmark --- pallets/subtensor/src/benchmarks.rs | 57 ++++++++++++++++++++++ pallets/subtensor/src/macros/dispatches.rs | 7 +-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 03e087a92..e30cf9027 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -429,4 +429,61 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) + swap_coldkey { + // Set up initial state + 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 netuid = 1u16; + let stake_amount1 = 1000u64; + let stake_amount2 = 2000u64; + let swap_cost = Subtensor::::get_key_swap_cost(); + let free_balance_old = 12345u64 + swap_cost; + let tempo: u16 = 1; + + // Setup initial state + Subtensor::::init_new_network(netuid, tempo); + Subtensor::::set_network_registration_allowed(netuid, true); + Subtensor::::set_network_pow_registration_allowed(netuid, true); + + let block_number: u64 = Subtensor::::get_current_block_as_u64(); + let (nonce, work): (u64, Vec) = Subtensor::::create_work_for_block_number( + netuid, + block_number, + 3, + &hotkey1, + ); + + let _ = Subtensor::::register( + ::RuntimeOrigin::from(RawOrigin::Signed(old_coldkey.clone())), + netuid, + block_number, + nonce, + work.clone(), + hotkey1.clone(), + old_coldkey.clone(), + ); + + // Add balance to old coldkey + Subtensor::::add_balance_to_coldkey_account( + &old_coldkey, + stake_amount1 + stake_amount2 + free_balance_old, + ); + + // Insert an Identity + let name: Vec = b"The fourth Coolest Identity".to_vec(); + let identity: ChainIdentity = ChainIdentity { + name: name.clone(), + url: vec![], + image: vec![], + discord: vec![], + description: vec![], + additional: vec![], + }; + + Identities::::insert(&old_coldkey, identity); + + // Benchmark setup complete, now execute the extrinsic +}: swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()) + } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 00865f8db..8057e9dd4 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -668,9 +668,10 @@ mod dispatches { /// /// Weight is calculated based on the number of database reads and writes. #[pallet::call_index(71)] - #[pallet::weight((Weight::from_parts(1_940_000_000, 0) - .saturating_add(T::DbWeight::get().reads(272)) - .saturating_add(T::DbWeight::get().writes(527)), DispatchClass::Operational, Pays::No))] + #[pallet::weight((Weight::from_parts(127_713_000, 0) + .saturating_add(Weight::from_parts(0, 11645)) + .saturating_add(T::DbWeight::get().reads(18)) + .saturating_add(T::DbWeight::get().writes(12)), DispatchClass::Operational, Pays::No))] pub fn swap_coldkey( origin: OriginFor, new_coldkey: T::AccountId, From dcb198f3b154b770cc605897b182ddeaf45408ac Mon Sep 17 00:00:00 2001 From: unconst Date: Sat, 3 Aug 2024 16:51:59 -0500 Subject: [PATCH 130/269] initial --- lock.ipynb | 412 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 lock.ipynb diff --git a/lock.ipynb b/lock.ipynb new file mode 100644 index 000000000..cb7425f1f --- /dev/null +++ b/lock.ipynb @@ -0,0 +1,412 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/YAAAIjCAYAAACpnIB8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzddXxUZ/b48c/4ZOIyySREkQQp0kIJ0AKl1Lfb7Vap7Na2tnWXpbp1d+9267/abrvfXXbrQilSrLS4RIhM3CfJyH1+f4wk04QQIBDhvF8vXoE79955Jncm5NzzPOfolFIKIYQQQgghhBBCDEr6/h6AEEIIIYQQQgghdp8E9kIIIYQQQgghxCAmgb0QQgghhBBCCDGISWAvhBBCCCGEEEIMYhLYCyGEEEIIIYQQg5gE9kIIIYQQQgghxCAmgb0QQgghhBBCCDGISWAvhBBCCCGEEEIMYhLYCyGEEEIIIYQQg5gE9kIIIXbZ3//+d3Q6HYWFhaFthx12GIcddli/jWlXffPNN+h0Or755pv+HspeFbxWy5cv75PzHXbYYRxwwAE73a+wsBCdTsff//73PnleIYQQQuyYBPZCCLGPBAMsnU7H999/3+VxpRQZGRnodDqOP/74fhhhh6lTp6LT6Xj++ef7dRwDwZ133olOp6O6urrbxw844IC9ekPjvvvu4+OPP95r5x+M6uvrsVqt6HQ61q9f39/D6XPr1q3jzjvvDLtx1huLFi3i97//PSkpKVgsFrKzs7n44ospLi7eOwPdA2VlZZx99tnk5eURHR1NXFwcU6dO5fXXX0cp1eOxxx57LPHx8VRUVHR5rKGhgdTUVPLz89E0bW8NXwghBhwJ7IUQYh+zWq288847XbZ/++23lJSUYLFY+mFUHTZv3syPP/5IdnY2b7/9dr+ORUhg350PPvgAnU6Hw+EYku/RdevWcdddd+1SYP/0008zc+ZMfv75Z6644gqee+45TjnlFN577z0mTJjADz/8sPcGvBuqq6spKSnhlFNO4ZFHHuGee+4hNTWVc889l7/85S89Hvvcc8/hdru55pprujx26623Ul1dzUsvvYReL7/mCiH2H/ITTwgh9rHjjjuODz74AK/XG7b9nXfeYfLkyTgcjn4amd9bb71FcnIyjz76KD/88MMuZw2F2NveeustjjvuOM4444xub5LtbxYtWsTVV1/NoYceypo1a5g/fz4XXHABjzzyCCtWrMBqtXLKKadQV1e3T8fV0tKyw8cmTJjAN998w7333svFF1/M5ZdfzieffMLxxx/PU089hc/n2+GxOTk53HHHHbz77rt89tlnoe0//vgjL7zwAtdeey0TJ07s09fSnba2NpkVIIQYMCSwF0KIfeyMM86gpqaGzz//PLTN7Xbz4YcfcuaZZ3Z7zCOPPMKMGTNITEwkIiKCyZMn8+GHH4bt89prr6HT6fjb3/4Wtv2+++5Dp9OxYMGCXo3vnXfe4ZRTTuH4448nNjZ2jwKnyspKLrjgAlJSUrBarUycOJHXX389bJ+DDjqIk046KWzb+PHj0el0rFmzJrTtvffe6zL1urS0lPPPPz809XjcuHFdXj9ASUkJJ554IpGRkSQnJ3PNNdfQ3t6+26+rJ8G1+++//z733nsv6enpWK1W5s6dy5YtW8L23bx5MyeffDIOhwOr1Up6ejrz5s2joaEBAJ1OR0tLC6+//npoGce5554LQFFREX/+85/Jy8sjIiKCxMRETj311F7diKmrq2Pq1Kmkp6ezceNGAD755BN+85vfkJaWhsViYcSIEfz1r3/dYYC1YsUKZsyYQUREBDk5Obzwwgu9+v5s2LCBU045hYSEBKxWK1OmTOFf//pXr44FKC4uZuHChcybN4958+ZRUFDQbTY6WAtgzZo1zJ49G5vNxsiRI0Ofm2+//Zb8/HwiIiLIy8vjiy++6HKOVatWceyxxxITE0NUVBRz585lyZIlYfsEl2r8Wnd1KLKzszn++OP5/vvvmTp1KlarleHDh/PGG2+EHXfqqacCMGfOnNB176kWxF//+ld0Oh2vv/46Npst7LERI0bw0EMPUV5ezosvvgj4f57odDqKioq6nOuWW27BbDaH3QRYunQpxxxzDLGxsdhsNmbPns2iRYu6/T6sW7eOM888k/j4eA499NAdjnlHsrOzcblcuN3uHve79tprmTBhAn/+859pa2vD5/NxySWXkJWVxR133AH07r1WW1vL9ddfz/jx44mKiiImJoZjjz2Wn376KWy/4Of6//2//8f8+fMZNmwYNpuNxsZGPB4Pd911F6NGjcJqtZKYmMihhx4a9jNeCCH2NgnshRBiH8vOzmb69Om8++67oW3//e9/aWhoYN68ed0e8+STT3LggQdy9913c99992E0Gjn11FP5z3/+E9rnvPPO4/jjj+faa69l+/btAPz888/cddddXHDBBRx33HE7HdvSpUvZsmULZ5xxBmazmZNOOmm3pzq3trZy2GGH8eabb3LWWWfx8MMPExsby7nnnsuTTz4Z2m/mzJlhNQdqa2tZu3Yter2ehQsXhrYvXLgQu93OmDFjAKioqGDatGl88cUXXH755Tz55JOMHDmSCy64gCeeeCJsHHPnzuXTTz/l8ssv5y9/+QsLFy7kxhtv3K3X1VsPPPAA//znP7n++uu55ZZbWLJkCWeddVbocbfbzdFHH82SJUu44oorePbZZ7nooovYtm0b9fX1ALz55ptYLBZmzpzJm2++yZtvvsnFF18M+LOTP/zwA/PmzeOpp57ikksu4csvv+Swww7D5XLtcFzV1dUcfvjhVFRU8O2335KXlwf4A8qoqCiuvfZannzySSZPnsztt9/OzTff3OUcdXV1HHfccUyePJmHHnqI9PR0Lr300m5vqnS2du1apk2bxvr167n55pt59NFHiYyM5MQTT+Sf//xnr76v7777LpGRkRx//PFMnTqVESNG7PA9WldXx/HHH09+fj4PPfQQFouFefPm8d577zFv3jyOO+44HnjgAVpaWjjllFNoamoKG+vMmTP56aefuPHGG7ntttsoKCjgsMMOY+nSpb0aa3e2bNnCKaecwpFHHsmjjz5KfHw85557LmvXrgVg1qxZXHnllYB/Wnnwugff97/mcrn48ssvmTlzJjk5Od3uc/rpp2OxWPj3v/8NwGmnnRa6+fRr77//PkcddRTx8fEAfPXVV8yaNYvGxkbuuOMO7rvvPurr6zn88MNZtmxZl+NPPfVUXC4X9913HxdeeOFOvx+tra1UV1dTWFjI66+/zmuvvcb06dOJiIjo8Tij0chLL71EQUEBf/3rX3nmmWdYuXIlzz//PDabrdfvtW3btvHxxx9z/PHH89hjj3HDDTfw888/M3v2bMrKyro871//+lf+85//cP3113PfffdhNpu58847ueuuu5gzZw7PPPMMf/nLX8jMzGTlypU7ff1CCNFnlBBCiH3itddeU4D68ccf1TPPPKOio6OVy+VSSil16qmnqjlz5iillMrKylK/+c1vwo4N7hfkdrvVAQccoA4//PCw7eXl5SohIUEdeeSRqr29XR144IEqMzNTNTQ09GqMl19+ucrIyFCapimllPrss88UoFatWtXtaykoKAhtmz17tpo9e3bo30888YQC1FtvvRU27unTp6uoqCjV2NiolFLqgw8+UIBat26dUkqpf/3rX8pisagTTjhBnX766aFjJ0yYoH7/+9+H/n3BBReo1NRUVV1dHTa2efPmqdjY2ND3LDiO999/P7RPS0uLGjlypALU119/3eP35I477lCAqqqq6vbxcePGhb3ur7/+WgFqzJgxqr29PbT9ySefVID6+eeflVJKrVq1SgHqgw8+6PH5IyMj1TnnnNNl+6/fE0optXjxYgWoN954I7St8/uuvLxcjRs3Tg0fPlwVFhbu9HwXX3yxstlsqq2tLbRt9uzZClCPPvpoaFt7e7uaNGmSSk5OVm63WymlVEFBgQLUa6+9Ftpv7ty5avz48WHn0zRNzZgxQ40aNarH70PQ+PHj1VlnnRX696233qqSkpKUx+MJ2y84znfeeSe0bcOGDQpQer1eLVmyJLT9008/7TLWE088UZnNZrV169bQtrKyMhUdHa1mzZoV2hZ8f/xad5+RrKwsBajvvvsutK2yslJZLBZ13XXXhbYFPxM7e28qpdTq1asVoK666qoe95swYYJKSEgI/Xv69Olq8uTJYfssW7Ys7P2jaZoaNWqUOvroo0M/E5Tyv1dycnLUkUceGdoW/D6cccYZOx1zZ/fff78CQn/mzp2riouLe3385Zdfrkwmk4qKigp77t6+19ra2pTP5ws7Z0FBgbJYLOruu+8ObQt+rocPH97lszJx4sQuP7OFEGJfk4y9EEL0g9NOO43W1lb+/e9/09TUxL///e8dTsMHwrJXdXV1NDQ0MHPmzC4ZIYfDwbPPPsvnn3/OzJkzWb16NX/729+IiYnZ6Zi8Xi/vvfcep59+emhq8eGHH05ycvJuZe0XLFiAw+HgjDPOCG0zmUxceeWVNDc38+233wL+jD3Ad999B/gz8wcffDBHHnlkKGNfX1/PL7/8EtpXKcVHH33Eb3/7W5RSVFdXh/4cffTRNDQ0hL43CxYsIDU1lVNOOSU0DpvNxkUXXbTLr2lXnHfeeZjN5tC/g2Pftm0bALGxsQB8+umnPWbYd6Tze8Lj8VBTU8PIkSOJi4vrNlNYUlLC7Nmz8Xg8fPfdd2RlZe3wfE1NTVRXVzNz5kxcLhcbNmwI29doNIZmDgCYzWYuvvhiKisrWbFiRbfjra2t5auvvuK0004Lnb+6upqamhqOPvpoNm/eTGlpaY+vec2aNfz8889h76kzzjiD6upqPv300y77R0VFhc2CycvLIy4ujjFjxpCfnx/aHvx78Nr4fD4+++wzTjzxRIYPHx7aLzU1lTPPPJPvv/+exsbGHse6I2PHjg29FwDsdjt5eXmh595VwVkG0dHRPe4XHR0dNubTTz+dFStWsHXr1tC29957D4vFwu9+9zsAVq9ezebNmznzzDOpqakJXbOWlhbmzp3Ld99912WN+SWXXLJL4z/jjDP4/PPPeeedd0I/A1tbW3t9/L333ktiYiJ6vZ7HH38c2LX3msViCRXZ8/l81NTUEBUVRV5eXrefo3POOafLbIK4uDjWrl3L5s2bd+m1CyFEX5LAXggh+oHdbueII47gnXfe4R//+Ac+ny8s8Py1f//730ybNg2r1UpCQgJ2u53nn38+tBa7s3nz5vGb3/yGZcuWceGFFzJ37txejemzzz6jqqqKqVOnsmXLFrZs2UJBQQFz5szh3Xff3eUiUUVFRYwaNapLZerglOLg+t6UlBRGjRoVCuIXLlzIzJkzmTVrFmVlZWzbto1FixahaVooIKqqqqK+vp6XXnoJu90e9ue8884D/Ov7g88zcuTILuugg1PQ+0J3a6wzMzPD/h2c2hxcu5yTk8O1117LK6+8QlJSEkcffTTPPvtst9e0O62trdx+++1kZGRgsVhISkrCbrdTX1/f7Tn+8Ic/UFlZybfffsuwYcO6PL527Vp+//vfExsbS0xMDHa7nbPPPhugy/nS0tKIjIwM25abmwuwwzX+W7ZsQSnFbbfd1uWaBddEB6/Zjrz11ltERkYyfPjw0HvUarXusINDenp6l2sTGxtLRkZGl23QcW2qqqpwuVzdvkfGjBmDpmmh5S676tfvC/C/N3a3sF0woO+8jKA7TU1NYcH/qaeeil6v57333gP8N8s++OCDUE0BIBSonnPOOV2u2SuvvEJ7e3uX98aOlgPsSFZWFkcccQRnnHEGb7/9NsOHD+eII47odXAfExNDXl4eGRkZpKSkALv2XtM0jccff5xRo0aFfY7WrFnT7eeou9d39913U19fT25uLuPHj+eGG24Iqw8ihBD7grG/ByCEEPurM888kwsvvBCn08mxxx5LXFxct/stXLiQE044gVmzZvHcc8+RmpqKyWTitdde67awXU1NDcuXLwf8bbM0TetV26dgYHTaaad1+/i3337LnDlzevnqds2hhx7Kl19+SWtrKytWrOD222/ngAMOIC4ujoULF7J+/XqioqI48MADAUI3Gc4++2zOOeecbs85YcKEPhmb1WoFdpxFdLlcoX06MxgM3e6vOvXofvTRRzn33HP55JNP+Oyzz7jyyiu5//77WbJkCenp6T2O64orruC1117j6quvZvr06cTGxqLT6Zg3b163N2FOOukk3njjDZ588knuv//+sMfq6+uZPXs2MTEx3H333YwYMQKr1crKlSu56aab+qTyd/Ac119/PUcffXS3+4wcOXKHxyulePfdd2lpaWHs2LFdHq+srKS5uZmoqKjQth1dg95cm97q7qYOsMOig3353OD/nhmNxh4Dyfb2djZu3MiUKVNC29LS0pg5cybvv/8+t956K0uWLKG4uJgHH3wwtE/wmj388MNMmjSp23N3/n4DO10bvzOnnHIKL7/8Mt99990O3yc7syvvtfvuu4/bbruN888/n7/+9a8kJCSg1+u5+uqru33fd/f6Zs2axdatW0Of41deeYXHH3+cF154gT/96U+79RqEEGJXSWAvhBD95Pe//z0XX3wxS5YsCWXNuvPRRx9htVr59NNPw3rcv/baa93uf9lll9HU1MT999/PLbfcwhNPPMG1117b41haWlr45JNPOP3007udOXDllVfy9ttv71Jgn5WVxZo1a7rcWAhO6+48FXzmzJm89tpr/L//9//w+XzMmDEDvV7PoYceGgrsZ8yYEQqK7HY70dHR+Hw+jjjiiJ2O45dffkEpFRaEBavB9+Z1BPf/dabX5XKxfft2jjrqqF6dqzvjx49n/PjxzJ8/nx9++IFDDjmEF154gXvuuQfYceD44Ycfcs455/Doo4+GtrW1tYUK7/3aFVdcwciRI7n99tuJjY0NK4r3zTffUFNTwz/+8Q9mzZoV2l5QUNDtucrKymhpaQnL2m/atAnwF4fsTnBKu8lk2uk16863335LSUkJd999d5dCcnV1dVx00UV8/PHHoVkGe8Jut2Oz2bp9j2zYsAG9Xh96LwRnYtTX14fdnOuu4nxv7eiadycyMpI5c+bw1VdfUVRU1GWJBfgL4rW3t3P88ceHbT/99NP585//zMaNG3nvvfew2Wz89re/DT0+YsQIwJ8V351rtjuCN9B6O3OlO7vyXvvwww+ZM2cOr776atj2+vp6kpKSev2cCQkJnHfeeZx33nk0Nzcza9Ys7rzzTgnshRD7jEzFF0KIfhIVFcXzzz/PnXfeGfbL9K8ZDAZ0Ol1YBrCwsJCPP/64y74ffvgh7733Hg888AA333wz8+bNY/78+aGga0f++c9/0tLSwmWXXcYpp5zS5c/xxx/PRx99tEst4o477jicTmfYTQuv18vTTz9NVFQUs2fPDm0PTrF/8MEHmTBhQmhq9MyZM/nyyy9Zvnx52Lpkg8HAySefzEcffcQvv/zS5bmrqqrCxlFWVhbWHtDlcvHSSy/16nXMnTsXs9nM888/3yWD99JLL+H1ejn22GN7da7OGhsb8Xq9YdvGjx+PXq8P+z5HRkZ2G6wbDIYuWd6nn366x/7ft912W6hK//PPPx92LgjPGrvdbp577rluz+P1ekOt04L7vvjii9jtdiZPntztMcnJyRx22GG8+OKLlJeXd3m88zXrTnAa/g033NDl/XnhhRcyatSo3e7g8GsGg4GjjjqKTz75JGxpQUVFBe+88w6HHnpoaLp6MPgN1ogAQi0Kd1fwhsmObtL82vz581FKce6553aZWVJQUMCNN95IampqWF0EgJNPPhmDwcC7777LBx98wPHHHx92s2by5MmMGDGCRx55hObm5i7Pu7Nr1pMdHfvqq6+i0+k46KCDdvvcu/Je6+5z9MEHH+y03kNnNTU1Yf+Oiopi5MiRe62lphBCdEcy9kII0Y92NI28s9/85jc89thjHHPMMZx55plUVlby7LPPMnLkyLDpt5WVlVx66aXMmTOHyy+/HIBnnnmGr7/+mnPPPZfvv/9+h1Py3377bRITE5kxY0a3j59wwgm8/PLL/Oc//+nSc35HLrroIl588UXOPfdcVqxYQXZ2Nh9++CGLFi3iiSeeCFvvO3LkSBwOBxs3buSKK64IbZ81axY33XQTQFhgD/52cl9//TX5+flceOGFjB07ltraWlauXMkXX3xBbW0tABdeeCHPPPMMf/zjH1mxYgWpqam8+eabXfp970hycjK333478+fPZ9asWZxwwgnYbDZ++OEH3n33XY466qgeb8zsyFdffcXll1/OqaeeSm5uLl6vlzfffDN00yJo8uTJfPHFFzz22GOkpaWRk5NDfn4+xx9/PG+++SaxsbGMHTuWxYsX88UXX5CYmNjj8z788MM0NDRw2WWXER0dzdlnn82MGTOIj4/nnHPO4corr0Sn0/Hmm2/ucHp4WloaDz74IIWFheTm5vLee++xevVqXnrpJUwm0w6f+9lnn+XQQw9l/PjxXHjhhQwfPpyKigoWL15MSUlJl97hQe3t7Xz00UcceeSR3S57AP979Mknn6SyspLk5OQevwe9cc899/D5559z6KGH8uc//xmj0ciLL75Ie3s7Dz30UGi/o446iszMTC644AJuuOEGDAYDf/vb37Db7RQXF+/Wc0+aNAmDwcCDDz5IQ0MDFoslVMiyO7NmzeKRRx4J9XY/99xzSU1NZcOGDbz88stomsaCBQtCswuCkpOTmTNnDo899hhNTU2cfvrpYY/r9XpeeeUVjj32WMaNG8d5553HsGHDKC0t5euvvyYmJob/+7//263XeO+997Jo0SKOOeYYMjMzqa2t5aOPPuLHH38MzS7ZE719rx1//PHcfffdnHfeecyYMYOff/45tNa/t8aOHcthhx3G5MmTSUhIYPny5Xz44Yehn8NCCLFP9EMlfiGE2C91bjvWk+7a3b366qtq1KhRymKxqNGjR6vXXnutS5utk046SUVHR3dpY/bJJ58oQD344IPdPl9FRYUyGo3qD3/4ww7H5HK5lM1mC7Wb6027u+C5zzvvPJWUlKTMZrMaP358WEuxzk499VQFqPfeey+0ze12K5vNpsxms2ptbe127JdddpnKyMhQJpNJORwONXfuXPXSSy+F7VdUVKROOOEEZbPZVFJSkrrqqqvU//73v163FFNKqbfeektNmzZNRUZGhq7DXXfdFdZOS6mOtli/bmP36/Zv27ZtU+eff74aMWKEslqtKiEhQc2ZM0d98cUXYcdt2LBBzZo1S0VERCgg1Pqurq4u9L2NiopSRx99tNqwYYPKysoKa4/X3fvO5/OpM844QxmNRvXxxx8rpZRatGiRmjZtmoqIiFBpaWnqxhtvDLWB6/w9mj17tho3bpxavny5mj59urJarSorK0s988wzPb7eoK1bt6o//vGPyuFwKJPJpIYNG6aOP/549eGHH+7we//RRx8pQL366qs73Oebb75RgHryySfDxvlr3X2+lFIKUJdddlnYtpUrV6qjjz5aRUVFKZvNpubMmaN++OGHLseuWLFC5efnK7PZ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GTWStEcexZqX288jGzje3fAuS8uXYjVYuffQezHqB1mx66JAYJ81Y7cOr3NJxn6HHnvsMW677TYuv/zyULG877//nksuuYTq6mquueaaPh+kEEKIrrq2kiukZntRL1vJZWON9BedMtsiSErPwmQdROvthBCDQkNVK5uWOdm41ElDZWtoe0SMmdyDUxhxoJ2kjGiZZi92mbuwkNLrb6Dtl18AiDtjHik33YRe/i8L2Va/jcdXPA7AdVOuIzs2u38HtDtChfN2fX09QI1Mxd+xp59+mueff54//vGPoW0nnHAC48aN484775TAXggh+pi0khNCDCZtLR62rKhk01In5VsbQtuNZj3DJ9nJzXeQMToevUFqcohdp5Si4ZNPcN79V5TLhSE2ltR77yH6iCP6e2gDikfzcMv3t9Dua+eQtEM4Pe/0/h7Srmuthwr/jZvdDexljX0PysvLmTGj61SIGTNmUF5e3ieDEkKI/VV4Kzn/NHppJSeEGOh8Ho3CX6rZtLSCwl+q0bwdBfDSR8eTm+9g+CQ7ZusgmwYsBhStuZmye++j8d//BsB28MGkPfwQJoejn0c28Ly05iXW1awjxhzDXTPuGpzL6rYvAxQkDIfolN06hayx78HIkSN5//33ufXWW8O2v/fee4waNarPBiaEEENZ51ZylcFAfiet5OyBCvT+ID6HpIwsaSUnhOg3SinKtzawaamTLSsqaXd1dNFITI8iL99B7sEpRMZJATyx56zFxRSfcire0lIwGLBffhmJF12EziDLOH7th9IfeGnNSwDMnzaflMjdC4r7XbBw3m5m60HW2Pforrvu4vTTT+e7774LrbFftGgRX375Je+//36fD1AIIQa7YCu5ioKtVPzwPe98/zm1pSU7biXnSMWeGWgll52DPTNHWskJIQaM+goXG5c62bTMSWN1W2h7ZJyF3Kkp5OU7SBwmS39E3/DV11P9/PNkvPkWXk3DNGwYaY88jO3AA/t7aANSSVMJNy68EU1pnDTqJI7NOba/h7T79jCwV0pRFyjUGS+BfVcnn3wyS5cu5fHHH+fjjz8GYMyYMSxbtowD5QMmhNiPeT0eaku3U11c2JGFLyro0kquKfC1cyu5YEE7aSUnhBiIWpvcbF5eycalTioLG0PbTRYDIw60kzvNwbDcePT6QTjdVwxIStOoe/sdqp5+Gq2xER0Q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ENEZxr7WrNerLysr45tvvuGHH35g0KBBAMycOZMlS5bwzttvc9vUGfjo1bz22mtMnz6d8ePHo1ar+fjjj4mIiOD777/nmmuuOetzm81mJk2axLfffoufnx/Tp08/45jFixfz2muvkZycjK+vLxdffDHz5s0jPDwcRVFISEjgrrvuqvLYXbt20b17dw4ePEibNm14+umn+eCDD8jKyiIkJISrrrqK+fPn1803rBY16MT+r0wmVylZcHAwANu3b8dmszFs2DD3Me3btyc2NpZNmzbRt29fNm3axAUXXFClNH/kyJFMmjSJvXv30r179zNex2KxYDltfm5RUREANpsNm81WJ++ttlTG19DjFNXTFM+ns7QU6+HDWJKTsaYcxJKSgjUlBWdx8RnHqv38MI4Yjs9FF2FITEQbFXXGKLwTcDaS709TPJ/NmZzPpqWxnU+H08G2rG0sO7qMn0/+TJm9DACtSssVba/gjs53EOIdAoDT4cTpcP7T0zU5je18in/WYM+nw4bm64moT25D8QrAfuUiUOmhluK02WwoioLT6cRZsZKPoiju/+/1yVvrjaoaFyysVisOhwO9Xu+OGcDb25vNmzZy21TISU8lMzOTIUOGuN+fn58fffr0YePGjfzf//3fWZ97+vTprFu3ju+++47w8HAee+wxduzYQdeuXd2vZbFYePrpp0lMTCQ7O5vp06czceJEli1bBsDNN9/MokWLmDZtmvt5P/jgAwYNGkTr1q356quvmDt3Lp999hmdOnUiMzOT3bt3V3kvtc3pdKIoCjabDY2m6kXYmvzMN5rE3ul0MmXKFAYMGEDnzp0ByMzMRK/XE/iXLtYRERFkVoz0ZWZmVknqK/dX7jubF198kaeffvqM7atWrcLHx+d830q9WL16tadDELWoUZ5PRUFbUIAhMxNDeobrNiMDXV4eKuXMdUsVtRpreDiWFpFYIltgadGCstatUHQ6sFphzx7XvyagUZ5P8bfkfDYtDf18Zjgy2GXdxR/WPyhWTl0QDVYH01XXle767gTnBLPlly0ejLLhaOjnU9RMgzqfipMex98lpmAjDpWOjTH3kr9pP7C/1l5Cq9USGRlJSUkJ1orpiGX2MkYsG1Frr1Fdq0avwltbvX5FvXr14umnnyY6Oprw8HC+/vprNm3aREx8a1RAeupxAMLCwig+bWAnODiYkydP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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import math\n", + "\n", + "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", + " lock_duration = max(end_day - current_day, 0)\n", + " time_factor = -lock_duration / interval \n", + " exp_term = 1 - math.exp(time_factor)\n", + " conviction_score = lock_amount * exp_term\n", + " return int(conviction_score)\n", + "\n", + "\n", + "def calculate_max_allowed_unstakable(alpha_locked: int, end_day: int, current_day: int, interval: int) -> int:\n", + " return alpha_locked - calculate_conviction(alpha_locked, end_day=end_day, current_day = current_day, interval=interval)\n", + " \n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Define intervals from 10 days to 3 years\n", + "intervals = [10, 30, 90, 180, 365, 365*2, 365*3]\n", + "\n", + "# Generate data for 3 years (assuming 1 block per day for simplicity)\n", + "days = range(0, 365)\n", + "\n", + "# Create the plot\n", + "plt.figure(figsize=(12, 6))\n", + "\n", + "for interval in intervals:\n", + " unstakable_amounts = [calculate_max_allowed_unstakable(1000, 365, day, interval=interval) for day in days]\n", + " plt.plot(days, unstakable_amounts, label=f'{interval} days')\n", + "\n", + "plt.title('Max Allowed Unstakable Amount Over 3 Years')\n", + "plt.xlabel('Days')\n", + "plt.ylabel('Max Allowed Unstakable Amount')\n", + "plt.legend()\n", + "plt.grid(True)\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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3do4e/Zsgj4P+mz9d5nGH3/80ahVq5bh6+trfa3vvPOOceHChQe+pyIitmIyjGTMriQiIiIiIiIiaY7mHBARERERERF5wSkcEBEREREREXnBKRwQERERERERecEpHBARERERERF5wSkcEBEREREREXnBKRwQERERERERecE52LqAF4nZbOb8+fN4enpiMplsXY6IiIiIiIikcYZhEB4ejr+/P3Z2Dx4foHDgGTp//jwBAQG2LkNEREREREReMGfOnCFbtmwP3K5w4Bny9PQELCfFy8vLxtU8WExMDMuWLaNWrVo4Ojrauhx5QjqfaYvOZ9qi85l26FymLTqfaYvOZ9qi85l8YWFhBAQEWK9HH0ThwDMUfyuBl5dXqg8H3Nzc8PLy0i9cGqDzmbbofKYtOp9ph85l2qLzmbbofKYtOp+P71G3tmtCQhEREREREZEXnMIBERERERERkRecwgERERERERGRF5zmHEhlDMMgNjaWuLg4m9UQExODg4MDd+7csWkdkjJ0PlOWvb09Dg4OWo5URERERNIUhQOpSHR0NBcuXCAyMtKmdRiGQZYsWThz5owugNIAnc+U5+bmhp+fH05OTrYuRUREREQkRSgcSCXMZjMnTpzA3t4ef39/nJycbHYhZzabuXXrFh4eHtjZ6c6T553OZ8oxDIPo6GiuXLnCiRMnyJMnj95TEREREUkTFA6kEtHR0ZjNZgICAnBzc7NpLWazmejoaFxcXHThkwbofKYsV1dXHB0dOXXqlPV9FRERERF53ulKIZXRxZtI6qffUxERERFJa/QvXBEREREREZEXnMIBERERERERkRecwgFJM4YOHUrRokVtvo+kWrlyJfnz50+1ywtWqVKFnj17PrPjlS1blp9++umZHU9ERERERP6lcECe2MWLF+nWrRs5c+bE2dmZgIAAGjZsyMqVK59pHX379k3WMU0mE7/++usT7eNJ9O/fn/fffx97e3sA/v77b0JCQsiQIQOurq7ky5ePzz77LNHzzp07x+uvv27tV6hQIbZt22bd3rZtW0wmk/XH3t6eZs2aPZPX9CTef/99Bg4ciNlstnUpIiIiIiIvHK1WIE/k5MmThISE4OPjw5gxYyhUqBAxMTEsXbqULl26cPDgwWdWi4eHBx4eHjbfR1L8/fffHDt2jKZNm1rb3N3d6dq1K4ULF8bd3Z2///6b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jVaciIiI/E51tbDxfTMUKE432/wiYNgd0P968PRzb30ictqV1JTw9+V/Z376fBYdWsSzo5/FYrEoGBCRBk4qHDj33HPx8vJyfa0XEhERkWaurgY2vAdLnoGSg2abf5QZCvS7Djx93VufiDSJDbkbuHfxvWRVZOFh9aBfVD93lyQizdRJhQMPP/yw6+u///3vp6sWERER+b3qamDd/2Dpc1B6yGzzj4bhd0K/a8Hu48biRKSpOA0nb2x5g5fWv0S9UU9CQAJPjXqKbmHd3F2aiDRTpzznwI033shVV13F6NGjT0M5IiIi8ps4qg+HAs9CWabZFhADw++CvtcoFBBpQwqqCrh/yf38mPUjAFOSp/Dg4Afx99TcIiLyy045HMjLy2Py5MlERERw2WWXcdVVV9GrV6/TUZuIiIiciKMK1r4Ny56DsiyzLTDODAX6XA12b7eWJyJNz8Pqwf7S/fh4+PDAwAc4r+N5uixYRE7olMOBL7/8kqKiImbOnMn777/PM888Q5cuXbjyyiu54oorSEpKOg1lioiISAOOKljzphkKlOeYbYHxMOJu6HMVeHi5tTwRaVp1zjpsFhsWi4UgryCeHf0svh6+tA9u7+7SRKSFsP6WO4WEhHDTTTexcOFCDhw4wLXXXss777xDx44dG7s+ERER+anaClj+EjzXE757wAwGgtrBWc/B7ethwA0KBkTamIzyDKbOmcrnez53tXUP765gQEROyW8KB45wOBysWbOGlStXsn//fqKiok7p/o899hgDBgwgICCAyMhIzjvvPHbu3Nlgn+rqam699VbCwsLw9/fnwgsvJCcnp8E+6enpnHnmmfj6+hIZGck999xDXV1dg30WLlxI37598fLyomPHjrz11lvH1DNjxgySkpLw9vZm0KBBrFq16pRrEREROS1qK2DZ8/B8L5j7V6jIheB2cPYLcNta6H8deHi6u0oRaUKGYfD13q+56KuL2JS3iRnrZ1BTX+PuskSkhfpN4cAPP/zAtGnTiIqK4tprryUwMJBZs2Zx6NChU3qcRYsWceutt/Ljjz8yb948HA4HEydOpKKiwrXPXXfdxddff83MmTNZtGgRmZmZXHDBBa7t9fX1nHnmmdTW1rJ8+XLefvtt3nrrLR566CHXPmlpaZx55pmMGTOGDRs2cOedd3LjjTfy3Xffufb56KOPuPvuu3n4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wgERERFpEtdeey0WiwWLxYLdbicqKooJEybwxhtv4HQ63V2eiIhIm6ZwQERERJrM5MmTycrKYv/+/Xz77beMGTOGO+64g7POOou6ujp3lyciItJmKRwQERGRJuPl5UV0dDRxcXH07duXv/zlL3z55Zd8++23vPXWWwA888wz9OjRAz8/PxISEvjjH/9IeXk5ABUVFQQGBvLJJ580eNwvvvgCPz8/ysrKqK2tZfr06cTExODt7U1iYiKPPfZYUz9VERGRFkXhgIiIiLjV2LFj6dWrF5999hkAVquVF154ga1bt/L222/z/fffc++99wLg5+fHZZddxptvvtngMd58800uuugiAgICeOGFF/jqq6/4+OOP2blzJ++99x5JSUlN/bRERERaFA93FyAiIiLSpUsXNm3aBMCdd97pak9KSuL//u//uPnmm3n55ZcBuPHGGxk6dChZWVnExMSQm5vL7NmzmT9/PgDp6emkpKQwfPhwLBYLiYmJTf58REREWhqNHBARERG3MwwDi8UCwPz58xk3bhxxcXEEBARw9dVXU1BQQGVlJQADBw6kW7duvP322wC8++67JCYmMnLkSMCc+HDDhg107tyZ22+/nblz57rnSYmIiLQgCgdERETE7bZv305ycjL79+/nrLPOomfPnnz66aesXbuWGTNmAFBbW+va/8Ybb3TNUfDmm29y3XXXucKFvn37kpaWxqOPPkpVVRWXXHIJF110UZM/JxERkZZE4YCIiIi41ffff8/mzZu58MILWbt2LU6nk6effprBgwfTqVMnMjMzj7nPVVddxYEDB3jhhRfYtm0bU6dObbA9MDCQSy+9lP/+97989NFHfPrppxQWFjbVUxIREWlxNOeAiIiINJmamhqys7Opr68nJyeHOXPm8Nhjj3HWWWdxzTXXsGXLFhwOBy+++CJnn302y5Yt49VXXz3mcUJCQrjgggu45557mDhxIvHx8a5tzzzzDDExMfTp0wer1crMmTOJjo4mODi4CZ+piIhIy6KRAyIiItJk5syZQ0xMDElJSUyePJkffviBF154gS+//BKbzUavXr145plneOKJJ+jevTvvvffeLy5DeMMNN1BbW8v111/foD0gIIAnn3yS/v37M2DAAPbv38/s2bOxWvVvj4iI4kX0zgAAALZJREFUyC+xGIZhuLsIERERkVP1zjvvcNddd5GZmYmnp6e7yxEREWnRdFmBiIiItCiVlZVkZWXx+OOP84c//EHBgIiISCPQ+DoRERFpUZ588km6dOlCdHQ0DzzwgLvLERERaRV0WYGIiIiIiIhIG6eRAyIiIiIiIiJtnMIBEfn/7diBAAAAAIAgf+tBLowAAIA5OQAAAABzcgAAAADm5AAAAADMyQEAAACYkwMAAAAwJwcAAABgLkrZF520n7l4AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import math\n", + "\n", + "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", + " lock_duration = max(end_day - current_day, 0)\n", + " time_factor = -lock_duration / interval \n", + " exp_term = 1 - math.exp(time_factor)\n", + " conviction_score = lock_amount * exp_term\n", + " return int(conviction_score)\n", + "\n", + "# Calculate conviction for 1000 over a range of durations from 10 days to a year\n", + "lock_amount = 1000\n", + "interval = 180\n", + "duration = 365\n", + "\n", + "plt.figure(figsize=(12, 6))\n", + "\n", + "unlockable = [0]\n", + "days = range(0, duration + 1) # +1 to include the last day\n", + "locked_amount = []\n", + "convictions = []\n", + "unlockable = [0]\n", + "for day in days:\n", + " current_locked = lock_amount + (7200 * 0.18 * day) - unlockable[day-1] * 0\n", + " locked_amount.append(current_locked)\n", + " conviction = calculate_conviction(current_locked, duration, day, interval=interval)\n", + " convictions.append(conviction)\n", + " unlockable.append(current_locked - conviction)\n", + "\n", + "plt.plot(days, convictions, label=f'Conviction ({duration} days)')\n", + "plt.plot(days, locked_amount, label=f'Locked Amount ({duration} days)')\n", + "plt.plot(days, unlockable[:-1], label=f'Unlockable ({duration} days)', linestyle='--')\n", + "\n", + "plt.title('Conviction Score for Various Lock Durations')\n", + "plt.xlabel('Days')\n", + "plt.ylabel('Conviction Score')\n", + "plt.grid(True)\n", + "plt.legend()\n", + "plt.show()\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Lion's Share Distribution:\n", + "Participant_0's lock: 6974, share: 0.0072\n", + "Participant_1's lock: 9165, share: 0.8613\n", + "Participant_2's lock: 8303, share: 0.1312\n", + "Participant_3's lock: 5278, share: 0.0002\n", + "Participant_4's lock: 2642, share: 0.0000\n", + "Participant_5's lock: 4272, share: 0.0000\n", + "Participant_6's lock: 4160, share: 0.0000\n", + "Participant_7's lock: 2101, share: 0.0000\n", + "Participant_8's lock: 3090, share: 0.0000\n", + "Participant_9's lock: 4980, share: 0.0001\n", + "\n", + "Lion's Share Skew Factors:\n", + "Participant_0's skew factor: 0.0528\n", + "Participant_1's skew factor: 4.7893\n", + "Participant_2's skew factor: 0.8054\n", + "Participant_3's skew factor: 0.0017\n", + "Participant_4's skew factor: 0.0000\n", + "Participant_5's skew factor: 0.0002\n", + "Participant_6's skew factor: 0.0002\n", + "Participant_7's skew factor: 0.0000\n", + "Participant_8's skew factor: 0.0000\n", + "Participant_9's skew factor: 0.0010\n" + ] + }, + { + "data": { + "image/png": "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e/yd2ujNbcDX11djxozRTz/9pGLFiumBBx7Q2LFjderUqdx6CQAcFCEdgFMKDg5W8eLF9dtvv2XpvMyOT/T09MzSfuOmCeEyY+3atXrkkUfk5+enqVOnavHixVq+fLm6dOmS4fPdeJftdjp27KjDhw9r0qRJKl68uN59911VqVJFP/30U5ZrlG79mh1BQECAGjVqpMmTJ+u1117ThQsXsv06M5IX7SCnff755/Y152fOnKklS5Zo+fLlatasWYZLZTnya5Hu/jN7p9cxfvx4/frrr3r11Vd17do1DRgwQFWqVMn0RH2S9fdRy5Yt9cUXX6h79+46dOhQmrHmmakvJSVFLVu21I8//qhXXnlFCxYs0PLlyzVr1ixJSvez8/X1TRcOU1NTFRISouXLl2f4deN8GNlh+317u2USQ0JCtGPHDi1cuFCPPPKIVq1apQcffFDdu3fP9HUy+7suJ2R2AsyckJk2+vzzz2v//v0aPXq0/Pz8NGzYMFWuXDnDnhQAXBchHYDTevjhh3Xo0CFt3Ljxjsfec889Sk1N1YEDB9LsP336tC5evKh77rknR2tLTU1N1812//79kmTvPj1//nz5+fnZ1yB+8MEH1aJFixy5flhYmJ599lktWLBAR44cUeHChfXWW29Jkv217tu3L915+/bty7H34lbXSUxM1JEjR3L8Pa9du7Yka/fsjISEhMjPzy/djNqSMtx3N+7mPba11UOHDqU7LzO++eYblSlTRt9++62efPJJtW7dWi1atEh3ZzezbLXe/NnJSk3ZvW5efWarVaum1157TWvWrNHatWt14sQJTZs2LVvPdad2eCu7du3S/v37NX78eL3yyit69NFH1aJFizRDM+6kbNmyOn/+vBo2bKgWLVqk+4qKispSTTf77LPPJOmOXdF9fHzUrl07TZ06VYcOHdLTTz+tTz/91P45y8pkbplxcxu5cuWKTp48mWaoSsGCBdOsKiFZfxfd/HPKSm333HOPDhw4kO4PKHv37rU/nh1ly5bViy++qGXLlum3335TYmKixo8fn63nAuCcCOkAnNbLL7+swMBAPfXUUzp9+nS6xw8dOmRf9uehhx6SJE2cODHNMRMmTJCkTM+cnRWTJ0+2bxuGocmTJ8vb21vNmzeXZL2rYrFY0tzJOXr0qBYsWJDta6akpKTrFhsSEqLixYvbl62qXbu2QkJCNG3atDRLWf3000/as2dPjr0XLVq0kI+Pj95///00d4pmzpypS5cuZfs6MTExGe63jT+9VRdsT09PtWjRQgsWLEgzXv/gwYM5evddurv32DY+/v3330+z/+a2eyu2u3U3vuebNm3K1B+zMhIWFqYaNWpo9uzZadrW8uXL9fvvv2frOTMjLz6zcXFxSk5OTrOvWrVq8vDwSLfM243i4+Nv+X7a2lJWhwJk9HMzDMP+OywzOnbsqJSUFL3xxhvpHktOTk4XUrNizpw5+uijj1S/fn3777CMnD9/Ps33Hh4e9tUabO+pbT33u6nnRtOnT1dSUpL9+w8++EDJyclp5pooW7as1qxZk+68m++kZ6W2hx56SKdOndLcuXPt+5KTkzVp0iQFBQXZhypkVnx8fLo/ppUtW1b58uW7bXsE4HpYgg2A0ypbtqzmzJmjTp06qXLlyurWrZuqVq2qxMREbdiwwb4UjiRFRUWpe/fumj59ui5evKjGjRtr8+bNmj17th577DE1bdo0R2vz8/PTkiVL1L17d9WtW1c//fSTfvzxR7366qv28d1t27bVhAkT1KZNG3Xp0kVnzpzRlClTVK5cOf3666/Zuu7ly5dVsmRJPf7444qKilJQUJBWrFihLVu22O/EeHt7a8yYMerZs6caN26szp0725cHi4iI0AsvvJAj70HRokU1dOhQjRo1Sm3atNEjjzyiffv2aerUqbrvvvv0n//8J1vP++ijj6p06dJq166dypYtq6tXr2rFihX64YcfdN9996ldu3a3PHfkyJFatmyZGjZsqH79+iklJUWTJ09W1apVtWPHjmy+0vTu5j2uUaOGOnfurKlTp+rSpUtq0KCBYmJiMn23/+GHH9a3336rf/3rX2rbtq2OHDmiadOmKTIy0j6mOKtGjx6ttm3b6v7771evXr30999/a9KkSapSpUqmn/Ps2bN688030+0vXbq0fY3vG+XFZ3blypWKjo5Whw4dVKFCBSUnJ+uzzz6Tp6en2rdvf8vz4uPj1aBBA9WrV09t2rRReHi4Ll68qAULFmjt2rV67LHHVLNmzSzVUqlSJZUtW1aDBw/WiRMnFBwcrPnz52dpArHGjRvr6aef1ujRo7Vjxw61atVK3t7eOnDggObNm6f33ntPjz/++B2f55tvvlFQUJASExN14sQJLV26VOvXr1dUVFS6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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", + " lock_duration = max(end_day - current_day, 0)\n", + " time_factor = -lock_duration / interval \n", + " exp_term = 1 - math.exp(time_factor)\n", + " conviction_score = lock_amount * exp_term\n", + " return int(conviction_score)\n", + "\n", + "\n", + "import random\n", + "\n", + "interval = 365\n", + "duration = 365\n", + "N = 10 # Number of participants\n", + "temperature = 5 # Adjust this value to control the steepness of the sigmoid\n", + "\n", + "# Generate random lock amounts for N participants\n", + "participants = [f\"Participant_{i}\" for i in range(N)]\n", + "locks = [random.randint(10, 10000) for _ in range(N)]\n", + "\n", + "# Calculate convictions\n", + "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", + "\n", + "# Calculate mean conviction\n", + "mean_conviction = sum(convictions) / len(convictions)\n", + "\n", + "# Calculate powered convictions using sigmoid function\n", + "powered_convictions = [1 / (1 + math.exp(-(conv - mean_conviction) / temperature)) for conv in convictions]\n", + "\n", + "# Calculate total powered conviction\n", + "total_powered = sum(powered_convictions)\n", + "\n", + "# Calculate shares\n", + "shares = [powered / total_powered for powered in powered_convictions]\n", + "\n", + "# # Print results\n", + "# for i, (participant, lock, share) in enumerate(zip(participants, locks, shares)):\n", + "# print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", + "\n", + "# # Calculate and print skew factors\n", + "# base_ratio = locks[0] / sum(locks)\n", + "# for i, (participant, lock, share) in enumerate(zip(participants, locks, shares)):\n", + "# skew_factor = (share / base_ratio) / (lock / locks[0])\n", + "# print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", + "\n", + "\n", + "import numpy as np\n", + "\n", + "# Function to calculate the \"lion's share\" distribution\n", + "def calculate_lions_share(convictions, sharpness=20):\n", + " # Normalize convictions\n", + " normalized_convictions = np.array(convictions) / np.max(convictions)\n", + " \n", + " # Apply exponential function to create a sharp drop-off\n", + " powered_convictions = np.exp(sharpness * (normalized_convictions - 1))\n", + " \n", + " # Calculate shares\n", + " total_powered = np.sum(powered_convictions)\n", + " shares = powered_convictions / total_powered\n", + " \n", + " return shares\n", + "\n", + "# Calculate convictions\n", + "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", + "\n", + "# Calculate shares using the lion's share distribution\n", + "lions_shares = calculate_lions_share(convictions)\n", + "\n", + "# Print results\n", + "print(\"\\nLion's Share Distribution:\")\n", + "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", + " print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", + "\n", + "# Calculate and print skew factors for lion's share\n", + "base_ratio = locks[0] / sum(locks)\n", + "print(\"\\nLion's Share Skew Factors:\")\n", + "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", + " skew_factor = (share / base_ratio) / (lock / locks[0])\n", + " print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", + "\n", + "# Visualize the difference between sigmoid and lion's share distributions\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.figure(figsize=(12, 6))\n", + "plt.plot(range(N), sorted(shares, reverse=True), 'b-', label='Sigmoid Distribution')\n", + "plt.plot(range(N), sorted(lions_shares, reverse=True), 'r-', label=\"Lion's Share Distribution\")\n", + "plt.xlabel('Participant Rank')\n", + "plt.ylabel('Share')\n", + "plt.title('Comparison of Sigmoid and Lion\\'s Share Distributions')\n", + "plt.legend()\n", + "plt.grid(True)\n", + "plt.show()\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Lion's Share Distribution:\n", + "Participant_0's lock: 3916, share: 0.0015\n", + "Participant_1's lock: 440, share: 0.0000\n", + "Participant_2's lock: 8807, share: 0.3472\n", + "Participant_3's lock: 8077, share: 0.1545\n", + "Participant_4's lock: 6539, share: 0.0281\n", + "Participant_5's lock: 6470, share: 0.0260\n", + "Participant_6's lock: 148, share: 0.0000\n", + "Participant_7's lock: 701, share: 0.0000\n", + "Participant_8's lock: 2765, share: 0.0004\n", + "Participant_9's lock: 9026, share: 0.4422\n", + "\n", + "Lion's Share Skew Factors:\n", + "Participant_0's skew factor: 0.0184\n", + "Participant_1's skew factor: 0.0035\n", + "Participant_2's skew factor: 1.8483\n", + "Participant_3's skew factor: 0.8967\n", + "Participant_4's skew factor: 0.2016\n", + "Participant_5's skew factor: 0.1886\n", + "Participant_6's skew factor: 0.0075\n", + "Participant_7's skew factor: 0.0029\n", + "Participant_8's skew factor: 0.0073\n", + "Participant_9's skew factor: 2.2970\n" + ] + }, + { + "data": { + "image/png": "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euWVVxQYGCgfHx9VqFBBCxcuTLBNaj5rZ86cUZcuXVSoUCF5e3urQIECevbZZzn9HnAxzvGVNQAkYfXq1SpWrJhq1qyZou27du2qhQsXqlWrVurfv7927dqlsLAwHThwIFHB8eeff6pdu3bq3r27XnrpJU2cOFHNmzfXrFmzNGzYMPXq1UuSFBYWphdeeCHRaZixsbFq1KiRqlevrgkTJmjdunUaNWqUYmJiNGbMGPt2U6ZM0TPPPKP27dsrKipKS5YsUevWrfXVV1+padOmCTJt2bJF//vf/9SnTx/lzZs32dNEe/TooeXLl6tPnz4qU6aM/v33X3377bc6cOCAHn30UUnmf0a7dOmiqlWrKiwsTGfPntWUKVO0Y8cO/fjjjwl6tGNjY9WwYUNVq1ZNEydO1KZNmzRp0iQVL15cPXv2vOt7Pnr0aIWGhqpevXrq2bOnDh06pJkzZ2rPnj3asWOHPD09NXnyZC1atEiff/65/TTb8uXLJ7vPBx98UJs3b9bff/+tQoUK3fX4d/rxxx/VqFEjFShQQKGhoYqNjdWYMWOUL1++JLffvn27vvzyS/Xu3VuS+ftu1qyZBg0apBkzZqhXr166ePGiJkyYoJdffllbtmyxPzc17/Gdunbtqk8++UTt2rVTzZo1tWXLlkTtITkRERH68MMP9eKLL6pbt266cuWK5s2bp4YNG2r37t2qWLFigu0XL16sK1euqHv37rLZbJowYYKef/55HT161N5LumHDBrVs2VJlypRRWFiY/v33X3shkZFS+5lt1aqVXnnlFXXq1EkfffSROnfurMqVK+uRRx5Jcv9RUVFq2LChIiMj9dprrykoKEinTp3SV199pUuXLilHjhxJPi9//vzy9fXV6tWr9dprryl37tz3fC3jxo2Tm5ubBgwYoMuXL2vChAlq3769du3aZd9m2bJlun79unr27Kk8efJo9+7dmjp1qv7++28tW7Yswf5iYmLUsGFDPfHEE5o4caL8/PwkSd27d7e3vb59++rYsWOaNm2afvzxR/tnLq06dOigOXPmaMOGDapfv36S22zcuFEvvviinn76aY0fP16SdODAAe3YsUOvv/66nnrqKfXt21cffPCBhg0bptKlS0uS/adkntb+4osvqnv37urWrZsefvjhu+bq06ePcubMqdGjR9v/xpw4ccI+BkFKpSTb7W7c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f37NmjUrwVRWa9eu1YEDB9LtvahXr568vLz0wQcfJOgpmjdvni5fvpzm42zevDnJ9fHXnyZ3Cra7u7vq1aunVatWJbhe/88//0zX3nfp/t7j+OvjP/jggwTr72y7yYnvrbv9Pd+1a1eKvsxKSoECBVSxYkUtXLgwQdvauHGjfv/99zTtMyUy4zMbERGhmJiYBOvKlSsnNze3RNO83e769evJvp/xbSm1lwIk9XszDMP+NywlXnjhBcXGxmrs2LGJHouJiUlUpKbG4sWL9eGHH6pGjRr2v2FJ+ffffxPcd3Nzs8/WEP+exs/nfj95bjdnzhxFR0fb78+cOVMxMTEJxpooXry4tm3bluh5d/akpyZbkyZNdObMGS1dutS+LiYmRlOnTpW/v7/9UoWUun79eqIv04oXL67s2bPftT0CyHqYgg2A0ypevLgWL16sNm3aqHTp0urYsaPKli2rqKgofffdd/apcCSpQoUK6tSpk+bMmaNLly6pVq1a2r17txYuXKgWLVqoTp066ZrNx8dH69atU6dOnVStWjWtXbtWX3/9tYYNG2a/vrtp06Z677331KhRI7Vr107nzp3T9OnTVaJECf38889pOu6VK1dUqFAhtWrVShUqVJC/v782bdqkPXv22HtiPD09NX78eHXp0kW1atXSiy++aJ8eLDg4WG+88Ua6vAf58uXT0KFDFRoaqkaNGumZZ57RoUOHNGPGDFWtWlUvvfRSmvb77LPPqmjRomrevLmKFy+ua9euadOmTVq9erWqVq2q5s2bJ/vc0aNHa8OGDXr88cfVs2dPxcbGatq0aSpbtqz279+fxlea2P28xxUrVtSLL76oGTNm6PLly6pZs6Y2b96c4t7+Zs2aaeXKlXruuefUtGlTHTt2TLNmzVKZMmXs1xSnVlhYmJo2baonnnhCL7/8sv777z9NnTpVjzzySIr3ef78eb311luJ1hctWtQ+x/ftMuMzu2XLFvXp00etW7dWyZIlFRMTo48//lju7u5q2bJlss+7fv26atasqerVq6tRo0YqXLiwLl26pFWrVmn79u1q0aKFKlWqlKospUqVUvHixTVgwACdOnVKAQEBWrFiRaoGEKtVq5a6d++usLAw7d+/Xw0aNJCnp6cOHz6sZcuWacqUKWrVqtU997N8+XL5+/srKipKp06d0vr167Vjxw5VqFAh0VRwd+ratav+++8/1a1bV4UKFdKJEyc0depUVaxY0X6tdsWKFeXu7q7x48fr8uXL8vb2Vt26dZOcXz4loqKi9PTTT9unw5wxY4aeeOIJPfPMMwly9ejRQy1btlT9+vX1008/af369cqbN2+CfaUm26uvvqrZs2erc+fO2rt3r4KDg7V8+XLt2LFDkydPTvHApvH++OMP++soU6aMPDw89Pnnn+vs2bNq27Ztmt4bAE7KkjHlASAd/fHHH0a3bt2M4OBgw8vLy8iePbvx+OOPG1OnTjVu3rxp3y46OtoIDQ01ihYtanh6ehqFCxc2hg4dmmAbwzCn5GnatGmi40hKNLVZ/NQ6t0+Z06lTJyNbtmzGkSNHjAYNGhh+fn5GYGCgMWrUqETThM2bN8946KGHDG9vb6NUqVLG/PnzE01Fldyxb38sfmqhyMhIY+DAgUaFChWM7NmzG9myZTMqVKhgzJgxI9Hzli5dalSqVMnw9vY2cufObbRv3974+++/E2wT/1rulFTG5EybNs0oVaqU4enpaQQGBho9e/Y0Ll68mOT+UjIF22effWa0bdvWKF68uOHr62v4+PgYZcqUMd58880E06IZRtLTLm3evNmoVKmS4eXlZRQvXtz48MMPjf79+xs+Pj6JnpuS37dh3Jpm686pvFLyHif1Xt64ccPo27evkSdPHiNbtmxG8+bNjb/++itFU7DFxcUZ77zzjvHggw8a3t7eRqVKlYyvvvoq0VRZyb2W+Nd+53FWrFhhlC5d2vD29jbKlCljrFy5MtE+k1OrVi1DUpK3p59+Otn34X4/s3dOmXWno0ePGi+//LJRvHhxw8fHx8idO7dRp04dY9OmTXd9PdHR0cbcuXONFi1a2N9nPz8/o1KlSsa7776bYKq15NpG/Ps/f/58+7rff//dqFevnuHv72/kzZvX6Natm/HTTz8l2i65z2W8OXPmGJUrVzZ8fX2N7NmzG+XKlTMGDRpk/PPPP3d9XfG/g/ibj4+PUahQIaNZs2bGRx99lOh9j89yextYvny50aBBAyN//vyGl5eXUaRIEaN79+7G6dOnEzxv7ty5RrFixQx3d/cEU54l97uMfyypKdi++eYb49VXXzVy5cpl+Pv7G+3btzf+/fffBM+NjY01Bg8ebOTNm9fw8/MzGjZsaPz555+J9nm3bEm1p7NnzxpdunQx8ubNa3h5eRnlypVL8LsyjJR/1i5cuGD07t3bKFWqlJEtWzYjR44cRrVq1Yz//e9/Sb4fALIum2E4yMgwAJBFdO7cWcuXL09zryUyX4sWLfTbb78led01AABAZuKadACAS7lx40aC+4cPH9aaNWtUu3ZtawIBAADchmvSAQAupVixYurcubN9rvaZM2fKy8tLgwYNsjoaAAAARToAwLU0atRIn332mc6cOSNvb2/VqFFD77zzjh566CGrowEAAIhr0gEAAAAAcBBckw4AAAAAgIOgSAcAAAAAwEG43DXpcXFx+ueff5Q9e3bZbDar4wAAAAAAsjjDMHTlyhUVLFhQbm537yt3uSL9n3/+UeHCha2OAQAAAABwMX/99ZcKFSp0121crkjPnj27JPPNCQgIsDjN3UVHR2vDhg1q0KCBPD09rY4DZBjaOlwJ7R2uhPYOV0J7x91ERESocOHC9nr0blyuSI8/xT0gIMApinQ/Pz8FBATwQUeWRluHK6G9w5XQ3uFKaO9IiZRccs3AcQAAAAAAOAiKdAAAAAAAHARFOgAAAAAADsLlrkkHAAAAXI1hGIqJiVFsbKzVUbKs6OhoeXh46ObNm7zPLsrT01Pu7u73vR+KdAAAACALi4qK0unTp3X9+nWro2RphmEoKChIf/31V4oGB0PWY7PZVKhQIfn7+9/XfijSAQAAgCwqLi5Ox44dk7u7uwoWLCgvLy8KyAwSFxenq1evyt/fX25uXFXsagzD0Pnz5/X333/roYceuq8edYp0AAAAIIuKiopSXFycChcuLD8/P6vjZGlxcXGKioqSj48PRbqLypcvn44fP67o6Oj7KtJpPQAAAEAWR9EIZLz0OkuFTysAAAAAAA6CIh0AAAAAAAdBkQ4AAADAKdlsNq1atcrqGAoPD5e7u7suX76c7DYLFixQzpw50+V46bmv2x0/flw2m0379++XZL4um82mS5cuZfixcAtFOgAAAACHc/78efXs2VNFihSRt7e3goKC1LBhQ+3YscO+zenTp9W4cWMLU5pq1qypU6dOKSAg4L72Y7PZ7Lds2bLpoYceUufOnbV3794E27Vp00Z//PFHivaZmoK+cOHCOn36tMqWLZva6HfVuXNntWjRIlOOlRVQpAMAAABwOC1bttSPP/6ohQsX6o8//tCXX36p2rVr699//7VvExQUJG9vbwtTmry8vBQU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tX//fvvtwIEDCU43T6l///1X58+fV9GiRZN8vGvXrjp69Kg6dOigX375RVWqVNHUqVPvud/bC+K0cnNzk3HHJbwZeY14atpMeqFIdyVt20rLl0teXtKyZVLLltLNm1anAgAAAO5L6dKl9dNPP+natWv2dTt27JCbm5sefvjhNO/X19dXzZs31wcffKDw8HDt3LkzVb2oZcqUkaQEue5m7969iouL06RJk1S9enWVLFlS//zzzz2fV6lSJcXGxurcuXMqUaJEgtvdTvlOzpQpU+Tm5nbXKfAKFy6sHj16aOXKlerfv7/mzp0ryezll8xR59Pq+++/ty/HxMRo7969Kl26tCQpX758unLlSoL39M551b28vO55/IxqM+mBIt3VtGghffGF5OMjrV4tPfNMhg3gAQAAANyPy5cvJ+gZ3r9/v/76669E27Vv314+Pj7q1KmTfv31V23dulWvvfaaOnToYD9tObXie8F//fVXHT16VJ988ol8fX314IMPJrl969atNWPGDO3atUsnTpxQeHi4evfurZIlS6pUqVIpOmaJEiUUHR2tqVOn6ujRo/r444/vOsBavJIlS6p9+/bq2LGjVq5cqWPHjmn37t0KCwvT119/fdfnXrlyRWfOnNFff/2lbdu26dVXX9Vbb72lt99+WyVKlEjyOf369dP69et17Ngx7du3T1u3brUX0Q8++KBsNpu++uornT9/PsFo6Sk1ffp0ff755zp48KB69+6tixcv6uWXX5YkVatWTX5+fho2bJiOHDmixYsXJxrILTg4WMeOHdP+/ft14cIFRUZGJjpGRrSZ9EKR7ooaNZLWrjVPOdq4UWrcWLpyxepUAAAAQALh4eGqVKlSgltoaGii7fz8/LR+/Xr9999/qlq1qlq1aqWnn35a06ZNS/Oxc+bMqblz5+rxxx9X+fLltWnTJq1evTrZa7wbNGigdevW6dlnn1XJkiXVqVMnlSpVShs2bEj2OvY7VahQQe+9957Gjx+vsmXL6tNPP1VYWFiKnjt//nx17NhR/fv318MPP6wWLVpoz549KlKkyF2fN3LkSBUoUEAlSpRQhw4ddPnyZW3evFmDBw9O9jmxsbHq3bu3SpcurUaNGqlkyZL2ge0eeOABhYaGasiQIQoMDEw0WnpKjBs3TuPGjVOFChX07bff6ssvv1TevHklmQPTffLJJ1qz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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "\n", + "# Function to calculate the \"lion's share\" distribution\n", + "def calculate_lions_share(convictions, sharpness=10):\n", + " # Normalize convictions\n", + " normalized_convictions = np.array(convictions) / np.max(convictions)\n", + " \n", + " # Apply exponential function to create a sharp drop-off\n", + " powered_convictions = np.exp(sharpness * (normalized_convictions - 1))\n", + " \n", + " # Calculate shares\n", + " total_powered = np.sum(powered_convictions)\n", + " shares = powered_convictions / total_powered\n", + " \n", + " return shares\n", + "\n", + "# Calculate convictions\n", + "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", + "\n", + "# Calculate shares using the lion's share distribution\n", + "lions_shares = calculate_lions_share(convictions)\n", + "\n", + "# Print results\n", + "print(\"\\nLion's Share Distribution:\")\n", + "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", + " print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", + "\n", + "# Calculate and print skew factors for lion's share\n", + "base_ratio = locks[0] / sum(locks)\n", + "print(\"\\nLion's Share Skew Factors:\")\n", + "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", + " skew_factor = (share / base_ratio) / (lock / locks[0])\n", + " print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", + "\n", + "# Visualize the difference between sigmoid and lion's share distributions\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.figure(figsize=(12, 6))\n", + "plt.plot(range(N), sorted(shares, reverse=True), 'b-', label='Sigmoid Distribution')\n", + "plt.plot(range(N), sorted(lions_shares, reverse=True), 'r-', label=\"Lion's Share Distribution\")\n", + "plt.xlabel('Participant Rank')\n", + "plt.ylabel('Share')\n", + "plt.title('Comparison of Sigmoid and Lion\\'s Share Distributions')\n", + "plt.legend()\n", + "plt.grid(True)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "770" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import math\n", + "\n", + "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", + " lock_duration = max(end_day - current_day, 0)\n", + " time_factor = -lock_duration / interval \n", + " exp_term = 1 - math.exp(time_factor)\n", + " conviction_score = lock_amount * exp_term\n", + " return int(conviction_score)\n", + "\n", + "\n", + "lock = 1000\n", + "calculate_conviction(lock, 365, 100, 180)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 7a77dfd7f3d6028e6c22491b9cb467f9dd6ad551 Mon Sep 17 00:00:00 2001 From: unconst Date: Sat, 3 Aug 2024 16:52:24 -0500 Subject: [PATCH 131/269] initia; --- .gitignore | 2 + lock.ipynb | 412 ----------------------------------------------------- 2 files changed, 2 insertions(+), 412 deletions(-) delete mode 100644 lock.ipynb diff --git a/.gitignore b/.gitignore index f394de80c..5921b6b93 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ **/*.lock +*.ipynb + # Generated by code coverage *.profraw *.profdata diff --git a/lock.ipynb b/lock.ipynb deleted file mode 100644 index cb7425f1f..000000000 --- a/lock.ipynb +++ /dev/null @@ -1,412 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 33, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/YAAAIjCAYAAACpnIB8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzddXxUZ/b48c/4ZOIyySREkQQp0kIJ0AKl1Lfb7Vap7Na2tnWXpbp1d+9267/abrvfXXbrQilSrLS4RIhM3CfJyH1+f4wk04QQIBDhvF8vXoE79955Jncm5NzzPOfolFIKIYQQQgghhBBCDEr6/h6AEEIIIYQQQgghdp8E9kIIIYQQQgghxCAmgb0QQgghhBBCCDGISWAvhBBCCCGEEEIMYhLYCyGEEEIIIYQQg5gE9kIIIYQQQgghxCAmgb0QQgghhBBCCDGISWAvhBBCCCGEEEIMYhLYCyGEEEIIIYQQg5gE9kIIIXbZ3//+d3Q6HYWFhaFthx12GIcddli/jWlXffPNN+h0Or755pv+HspeFbxWy5cv75PzHXbYYRxwwAE73a+wsBCdTsff//73PnleIYQQQuyYBPZCCLGPBAMsnU7H999/3+VxpRQZGRnodDqOP/74fhhhh6lTp6LT6Xj++ef7dRwDwZ133olOp6O6urrbxw844IC9ekPjvvvu4+OPP95r5x+M6uvrsVqt6HQ61q9f39/D6XPr1q3jzjvvDLtx1huLFi3i97//PSkpKVgsFrKzs7n44ospLi7eOwPdA2VlZZx99tnk5eURHR1NXFwcU6dO5fXXX0cp1eOxxx57LPHx8VRUVHR5rKGhgdTUVPLz89E0bW8NXwghBhwJ7IUQYh+zWq288847XbZ/++23lJSUYLFY+mFUHTZv3syPP/5IdnY2b7/9dr+ORUhg350PPvgAnU6Hw+EYku/RdevWcdddd+1SYP/0008zc+ZMfv75Z6644gqee+45TjnlFN577z0mTJjADz/8sPcGvBuqq6spKSnhlFNO4ZFHHuGee+4hNTWVc889l7/85S89Hvvcc8/hdru55pprujx26623Ul1dzUsvvYReL7/mCiH2H/ITTwgh9rHjjjuODz74AK/XG7b9nXfeYfLkyTgcjn4amd9bb71FcnIyjz76KD/88MMuZw2F2NveeustjjvuOM4444xub5LtbxYtWsTVV1/NoYceypo1a5g/fz4XXHABjzzyCCtWrMBqtXLKKadQV1e3T8fV0tKyw8cmTJjAN998w7333svFF1/M5ZdfzieffMLxxx/PU089hc/n2+GxOTk53HHHHbz77rt89tlnoe0//vgjL7zwAtdeey0TJ07s09fSnba2NpkVIIQYMCSwF0KIfeyMM86gpqaGzz//PLTN7Xbz4YcfcuaZZ3Z7zCOPPMKMGTNITEwkIiKCyZMn8+GHH4bt89prr6HT6fjb3/4Wtv2+++5Dp9OxYMGCXo3vnXfe4ZRTTuH4448nNjZ2jwKnyspKLrjgAlJSUrBarUycOJHXX389bJ+DDjqIk046KWzb+PHj0el0rFmzJrTtvffe6zL1urS0lPPPPz809XjcuHFdXj9ASUkJJ554IpGRkSQnJ3PNNdfQ3t6+26+rJ8G1+++//z733nsv6enpWK1W5s6dy5YtW8L23bx5MyeffDIOhwOr1Up6ejrz5s2joaEBAJ1OR0tLC6+//npoGce5554LQFFREX/+85/Jy8sjIiKCxMRETj311F7diKmrq2Pq1Kmkp6ezceNGAD755BN+85vfkJaWhsViYcSIEfz1r3/dYYC1YsUKZsyYQUREBDk5Obzwwgu9+v5s2LCBU045hYSEBKxWK1OmTOFf//pXr44FKC4uZuHChcybN4958+ZRUFDQbTY6WAtgzZo1zJ49G5vNxsiRI0Ofm2+//Zb8/HwiIiLIy8vjiy++6HKOVatWceyxxxITE0NUVBRz585lyZIlYfsEl2r8Wnd1KLKzszn++OP5/vvvmTp1KlarleHDh/PGG2+EHXfqqacCMGfOnNB176kWxF//+ld0Oh2vv/46Npst7LERI0bw0EMPUV5ezosvvgj4f57odDqKioq6nOuWW27BbDaH3QRYunQpxxxzDLGxsdhsNmbPns2iRYu6/T6sW7eOM888k/j4eA499NAdjnlHsrOzcblcuN3uHve79tprmTBhAn/+859pa2vD5/NxySWXkJWVxR133AH07r1WW1vL9ddfz/jx44mKiiImJoZjjz2Wn376KWy/4Of6//2//8f8+fMZNmwYNpuNxsZGPB4Pd911F6NGjcJqtZKYmMihhx4a9jNeCCH2NgnshRBiH8vOzmb69Om8++67oW3//e9/aWhoYN68ed0e8+STT3LggQdy9913c99992E0Gjn11FP5z3/+E9rnvPPO4/jjj+faa69l+/btAPz888/cddddXHDBBRx33HE7HdvSpUvZsmULZ5xxBmazmZNOOmm3pzq3trZy2GGH8eabb3LWWWfx8MMPExsby7nnnsuTTz4Z2m/mzJlhNQdqa2tZu3Yter2ehQsXhrYvXLgQu93OmDFjAKioqGDatGl88cUXXH755Tz55JOMHDmSCy64gCeeeCJsHHPnzuXTTz/l8ssv5y9/+QsLFy7kxhtv3K3X1VsPPPAA//znP7n++uu55ZZbWLJkCWeddVbocbfbzdFHH82SJUu44oorePbZZ7nooovYtm0b9fX1ALz55ptYLBZmzpzJm2++yZtvvsnFF18M+LOTP/zwA/PmzeOpp57ikksu4csvv+Swww7D5XLtcFzV1dUcfvjhVFRU8O2335KXlwf4A8qoqCiuvfZannzySSZPnsztt9/OzTff3OUcdXV1HHfccUyePJmHHnqI9PR0Lr300m5vqnS2du1apk2bxvr167n55pt59NFHiYyM5MQTT+Sf//xnr76v7777LpGRkRx//PFMnTqVESNG7PA9WldXx/HHH09+fj4PPfQQFouFefPm8d577zFv3jyOO+44HnjgAVpaWjjllFNoamoKG+vMmTP56aefuPHGG7ntttsoKCjgsMMOY+nSpb0aa3e2bNnCKaecwpFHHsmjjz5KfHw85557LmvXrgVg1qxZXHnllYB/Wnnwugff97/mcrn48ssvmTlzJjk5Od3uc/rpp2OxWPj3v/8NwGmnnRa6+fRr77//PkcddRTx8fEAfPXVV8yaNYvGxkbuuOMO7rvvPurr6zn88MNZtmxZl+NPPfVUXC4X9913HxdeeOFOvx+tra1UV1dTWFjI66+/zmuvvcb06dOJiIjo8Tij0chLL71EQUEBf/3rX3nmmWdYuXIlzz//PDabrdfvtW3btvHxxx9z/PHH89hjj3HDDTfw888/M3v2bMrKyro871//+lf+85//cP3113PfffdhNpu58847ueuuu5gzZw7PPPMMf/nLX8jMzGTlypU7ff1CCNFnlBBCiH3itddeU4D68ccf1TPPPKOio6OVy+VSSil16qmnqjlz5iillMrKylK/+c1vwo4N7hfkdrvVAQccoA4//PCw7eXl5SohIUEdeeSRqr29XR144IEqMzNTNTQ09GqMl19+ucrIyFCapimllPrss88UoFatWtXtaykoKAhtmz17tpo9e3bo30888YQC1FtvvRU27unTp6uoqCjV2NiolFLqgw8+UIBat26dUkqpf/3rX8pisagTTjhBnX766aFjJ0yYoH7/+9+H/n3BBReo1NRUVV1dHTa2efPmqdjY2ND3LDiO999/P7RPS0uLGjlypALU119/3eP35I477lCAqqqq6vbxcePGhb3ur7/+WgFqzJgxqr29PbT9ySefVID6+eeflVJKrVq1SgHqgw8+6PH5IyMj1TnnnNNl+6/fE0optXjxYgWoN954I7St8/uuvLxcjRs3Tg0fPlwVFhbu9HwXX3yxstlsqq2tLbRt9uzZClCPPvpoaFt7e7uaNGmSSk5OVm63WymlVEFBgQLUa6+9Ftpv7ty5avz48WHn0zRNzZgxQ40aNarH70PQ+PHj1VlnnRX696233qqSkpKUx+MJ2y84znfeeSe0bcOGDQpQer1eLVmyJLT9008/7TLWE088UZnNZrV169bQtrKyMhUdHa1mzZoV2hZ8f/xad5+RrKwsBajvvvsutK2yslJZLBZ13XXXhbYFPxM7e28qpdTq1asVoK666qoe95swYYJKSEgI/Xv69Olq8uTJYfssW7Ys7P2jaZoaNWqUOvroo0M/E5Tyv1dycnLUkUceGdoW/D6cccYZOx1zZ/fff78CQn/mzp2riouLe3385Zdfrkwmk4qKigp77t6+19ra2pTP5ws7Z0FBgbJYLOruu+8ObQt+rocPH97lszJx4sQuP7OFEGJfk4y9EEL0g9NOO43W1lb+/e9/09TUxL///e8dTsMHwrJXdXV1NDQ0MHPmzC4ZIYfDwbPPPsvnn3/OzJkzWb16NX/729+IiYnZ6Zi8Xi/vvfcep59+emhq8eGHH05ycvJuZe0XLFiAw+HgjDPOCG0zmUxceeWVNDc38+233wL+jD3Ad999B/gz8wcffDBHHnlkKGNfX1/PL7/8EtpXKcVHH33Eb3/7W5RSVFdXh/4cffTRNDQ0hL43CxYsIDU1lVNOOSU0DpvNxkUXXbTLr2lXnHfeeZjN5tC/g2Pftm0bALGxsQB8+umnPWbYd6Tze8Lj8VBTU8PIkSOJi4vrNlNYUlLC7Nmz8Xg8fPfdd2RlZe3wfE1NTVRXVzNz5kxcLhcbNmwI29doNIZmDgCYzWYuvvhiKisrWbFiRbfjra2t5auvvuK0004Lnb+6upqamhqOPvpoNm/eTGlpaY+vec2aNfz8889h76kzzjiD6upqPv300y77R0VFhc2CycvLIy4ujjFjxpCfnx/aHvx78Nr4fD4+++wzTjzxRIYPHx7aLzU1lTPPPJPvv/+exsbGHse6I2PHjg29FwDsdjt5eXmh595VwVkG0dHRPe4XHR0dNubTTz+dFStWsHXr1tC29957D4vFwu9+9zsAVq9ezebNmznzzDOpqakJXbOWlhbmzp3Ld99912WN+SWXXLJL4z/jjDP4/PPPeeedd0I/A1tbW3t9/L333ktiYiJ6vZ7HH38c2LX3msViCRXZ8/l81NTUEBUVRV5eXrefo3POOafLbIK4uDjWrl3L5s2bd+m1CyFEX5LAXggh+oHdbueII47gnXfe4R//+Ac+ny8s8Py1f//730ybNg2r1UpCQgJ2u53nn38+tBa7s3nz5vGb3/yGZcuWceGFFzJ37txejemzzz6jqqqKqVOnsmXLFrZs2UJBQQFz5szh3Xff3eUiUUVFRYwaNapLZerglOLg+t6UlBRGjRoVCuIXLlzIzJkzmTVrFmVlZWzbto1FixahaVooIKqqqqK+vp6XXnoJu90e9ue8884D/Ov7g88zcuTILuugg1PQ+0J3a6wzMzPD/h2c2hxcu5yTk8O1117LK6+8QlJSEkcffTTPPvtst9e0O62trdx+++1kZGRgsVhISkrCbrdTX1/f7Tn+8Ic/UFlZybfffsuwYcO6PL527Vp+//vfExsbS0xMDHa7nbPPPhugy/nS0tKIjIwM25abmwuwwzX+W7ZsQSnFbbfd1uWaBddEB6/Zjrz11ltERkYyfPjw0HvUarXusINDenp6l2sTGxtLRkZGl23QcW2qqqpwuVzdvkfGjBmDpmmh5S676tfvC/C/N3a3sF0woO+8jKA7TU1NYcH/qaeeil6v57333gP8N8s++OCDUE0BIBSonnPOOV2u2SuvvEJ7e3uX98aOlgPsSFZWFkcccQRnnHEGb7/9NsOHD+eII47odXAfExNDXl4eGRkZpKSkALv2XtM0jccff5xRo0aFfY7WrFnT7eeou9d39913U19fT25uLuPHj+eGG24Iqw8ihBD7grG/ByCEEPurM888kwsvvBCn08mxxx5LXFxct/stXLiQE044gVmzZvHcc8+RmpqKyWTitdde67awXU1NDcuXLwf8bbM0TetV26dgYHTaaad1+/i3337LnDlzevnqds2hhx7Kl19+SWtrKytWrOD222/ngAMOIC4ujoULF7J+/XqioqI48MADAUI3Gc4++2zOOeecbs85YcKEPhmb1WoFdpxFdLlcoX06MxgM3e6vOvXofvTRRzn33HP55JNP+Oyzz7jyyiu5//77WbJkCenp6T2O64orruC1117j6quvZvr06cTGxqLT6Zg3b163N2FOOukk3njjDZ588knuv//+sMfq6+uZPXs2MTEx3H333YwYMQKr1crKlSu56aab+qTyd/Ac119/PUcffXS3+4wcOXKHxyulePfdd2lpaWHs2LFdHq+srKS5uZmoqKjQth1dg95cm97q7qYOsMOig3353OD/nhmNxh4Dyfb2djZu3MiUKVNC29LS0pg5cybvv/8+t956K0uWLKG4uJgHH3wwtE/wmj388MNMmjSp23N3/n4DO10bvzOnnHIKL7/8Mt99990O3yc7syvvtfvuu4/bbruN888/n7/+9a8kJCSg1+u5+uqru33fd/f6Zs2axdatW0Of41deeYXHH3+cF154gT/96U+79RqEEGJXSWAvhBD95Pe//z0XX3wxS5YsCWXNuvPRRx9htVr59NNPw3rcv/baa93uf9lll9HU1MT999/PLbfcwhNPPMG1117b41haWlr45JNPOP3007udOXDllVfy9ttv71Jgn5WVxZo1a7rcWAhO6+48FXzmzJm89tpr/L//9//w+XzMmDEDvV7PoYceGgrsZ8yYEQqK7HY70dHR+Hw+jjjiiJ2O45dffkEpFRaEBavB9+Z1BPf/dabX5XKxfft2jjrqqF6dqzvjx49n/PjxzJ8/nx9++IFDDjmEF154gXvuuQfYceD44Ycfcs455/Doo4+GtrW1tYUK7/3aFVdcwciRI7n99tuJjY0NK4r3zTffUFNTwz/+8Q9mzZoV2l5QUNDtucrKymhpaQnL2m/atAnwF4fsTnBKu8lk2uk16863335LSUkJd999d5dCcnV1dVx00UV8/PHHoVkGe8Jut2Oz2bp9j2zYsAG9Xh96LwRnYtTX14fdnOuu4nxv7eiadycyMpI5c+bw1VdfUVRU1GWJBfgL4rW3t3P88ceHbT/99NP585//zMaNG3nvvfew2Wz89re/DT0+YsQIwJ8V351rtjuCN9B6O3OlO7vyXvvwww+ZM2cOr776atj2+vp6kpKSev2cCQkJnHfeeZx33nk0Nzcza9Ys7rzzTgnshRD7jEzFF0KIfhIVFcXzzz/PnXfeGfbL9K8ZDAZ0Ol1YBrCwsJCPP/64y74ffvgh7733Hg888AA333wz8+bNY/78+aGga0f++c9/0tLSwmWXXcYpp5zS5c/xxx/PRx99tEst4o477jicTmfYTQuv18vTTz9NVFQUs2fPDm0PTrF/8MEHmTBhQmhq9MyZM/nyyy9Zvnx52Lpkg8HAySefzEcffcQvv/zS5bmrqqrCxlFWVhbWHtDlcvHSSy/16nXMnTsXs9nM888/3yWD99JLL+H1ejn22GN7da7OGhsb8Xq9YdvGjx+PXq8P+z5HRkZ2G6wbDIYuWd6nn366x/7ft912W6hK//PPPx92LgjPGrvdbp577rluz+P1ekOt04L7vvjii9jtdiZPntztMcnJyRx22GG8+OKLlJeXd3m88zXrTnAa/g033NDl/XnhhRcyatSo3e7g8GsGg4GjjjqKTz75JGxpQUVFBe+88w6HHnpoaLp6MPgN1ogAQi0Kd1fwhsmObtL82vz581FKce6553aZWVJQUMCNN95IampqWF0EgJNPPhmDwcC7777LBx98wPHHHx92s2by5MmMGDGCRx55hObm5i7Pu7Nr1pMdHfvqq6+i0+k46KCDdvvcu/Je6+5z9MEHH+y03kNnNTU1Yf+Oiopi5MiRe62lphBCdEcy9kII0Y92NI28s9/85jc89thjHHPMMZx55plUVlby7LPPMnLkyLDpt5WVlVx66aXMmTOHyy+/HIBnnnmGr7/+mnPPPZfvv/9+h1Py3377bRITE5kxY0a3j59wwgm8/PLL/Oc//+nSc35HLrroIl588UXOPfdcVqxYQXZ2Nh9++CGLFi3iiSeeCFvvO3LkSBwOBxs3buSKK64IbZ81axY33XQTQFhgD/52cl9//TX5+flceOGFjB07ltraWlauXMkXX3xBbW0tABdeeCHPPPMMf/zjH1mxYgWpqam8+eabXfp970hycjK333478+fPZ9asWZxwwgnYbDZ++OEH3n33XY466qgeb8zsyFdffcXll1/OqaeeSm5uLl6vlzfffDN00yJo8uTJfPHFFzz22GOkpaWRk5NDfn4+xx9/PG+++SaxsbGMHTuWxYsX88UXX5CYmNjj8z788MM0NDRw2WWXER0dzdlnn82MGTOIj4/nnHPO4corr0Sn0/Hmm2/ucHp4WloaDz74IIWFheTm5vLee++xevVqXnrpJUwm0w6f+9lnn+XQQw9l/PjxXHjhhQwfPpyKigoWL15MSUlJl97hQe3t7Xz00UcceeSR3S57AP979Mknn6SyspLk5OQevwe9cc899/D5559z6KGH8uc//xmj0ciLL75Ie3s7Dz30UGi/o446iszMTC644AJuuOEGDAYDf/vb37Db7RQXF+/Wc0+aNAmDwcCDDz5IQ0MDFoslVMiyO7NmzeKRRx4J9XY/99xzSU1NZcOGDbz88stomsaCBQtCswuCkpOTmTNnDo899hhNTU2cfvrpYY/r9XpeeeUVjj32WMaNG8d5553HsGHDKC0t5euvvyYmJob/+7//263XeO+997Jo0SKOOeYYMjMzqa2t5aOPPuLHH38MzS7ZE719rx1//PHcfffdnHfeecyYMYOff/45tNa/t8aOHcthhx3G5MmTSUhIYPny5Xz44Yehn8NCCLFP9EMlfiGE2C91bjvWk+7a3b366qtq1KhRymKxqNGjR6vXXnutS5utk046SUVHR3dpY/bJJ58oQD344IPdPl9FRYUyGo3qD3/4ww7H5HK5lM1mC7Wb6027u+C5zzvvPJWUlKTMZrMaP358WEuxzk499VQFqPfeey+0ze12K5vNpsxms2ptbe127JdddpnKyMhQJpNJORwONXfuXPXSSy+F7VdUVKROOOEEZbPZVFJSkrrqqqvU//73v163FFNKqbfeektNmzZNRUZGhq7DXXfdFdZOS6mOtli/bmP36/Zv27ZtU+eff74aMWKEslqtKiEhQc2ZM0d98cUXYcdt2LBBzZo1S0VERCgg1Pqurq4u9L2NiopSRx99tNqwYYPKysoKa4/X3fvO5/OpM844QxmNRvXxxx8rpZRatGiRmjZtmoqIiFBpaWnqxhtvDLWB6/w9mj17tho3bpxavny5mj59urJarSorK0s988wzPb7eoK1bt6o//vGPyuFwKJPJpIYNG6aOP/549eGHH+7we//RRx8pQL366qs73Oebb75RgHryySfDxvlr3X2+lFIKUJdddlnYtpUrV6qjjz5aRUVFKZvNpubMmaN++OGHLseuWLFC5efnK7PZ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GTWStEcexZqX288jGzje3fAuS8uXYjVYuffQezHqB1mx66JAYJ81Y7cOr3NJxn6HHnvsMW677TYuv/zyULG877//nksuuYTq6mquueaaPh+kEEKIrrq2kiukZntRL1vJZWON9BedMtsiSErPwmQdROvthBCDQkNVK5uWOdm41ElDZWtoe0SMmdyDUxhxoJ2kjGiZZi92mbuwkNLrb6Dtl18AiDtjHik33YRe/i8L2Va/jcdXPA7AdVOuIzs2u38HtDtChfN2fX09QI1Mxd+xp59+mueff54//vGPoW0nnHAC48aN484775TAXggh+pi0khNCDCZtLR62rKhk01In5VsbQtuNZj3DJ9nJzXeQMToevUFqcohdp5Si4ZNPcN79V5TLhSE2ltR77yH6iCP6e2gDikfzcMv3t9Dua+eQtEM4Pe/0/h7Srmuthwr/jZvdDexljX0PysvLmTGj61SIGTNmUF5e3ieDEkKI/VV4Kzn/NHppJSeEGOh8Ho3CX6rZtLSCwl+q0bwdBfDSR8eTm+9g+CQ7ZusgmwYsBhStuZmye++j8d//BsB28MGkPfwQJoejn0c28Ly05iXW1awjxhzDXTPuGpzL6rYvAxQkDIfolN06hayx78HIkSN5//33ufXWW8O2v/fee4waNarPBiaEEENZ51ZylcFAfiet5OyBCvT+ID6HpIwsaSUnhOg3SinKtzawaamTLSsqaXd1dNFITI8iL99B7sEpRMZJATyx56zFxRSfcire0lIwGLBffhmJF12EziDLOH7th9IfeGnNSwDMnzaflMjdC4r7XbBw3m5m60HW2Pforrvu4vTTT+e7774LrbFftGgRX375Je+//36fD1AIIQa7YCu5ioKtVPzwPe98/zm1pSU7biXnSMWeGWgll52DPTNHWskJIQaM+goXG5c62bTMSWN1W2h7ZJyF3Kkp5OU7SBwmS39E3/DV11P9/PNkvPkWXk3DNGwYaY88jO3AA/t7aANSSVMJNy68EU1pnDTqJI7NOba/h7T79jCwV0pRFyjUGS+BfVcnn3wyS5cu5fHHH+fjjz8GYMyYMSxbtowD5QMmhNiPeT0eaku3U11c2JGFLyro0kquKfC1cyu5YEE7aSUnhBiIWpvcbF5eycalTioLG0PbTRYDIw60kzvNwbDcePT6QTjdVwxIStOoe/sdqp5+Gq2xER0Q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ENEZxr7WrNerLysr45tvvuGHH35g0KBBAMycOZMlS5bwzttvc9vUGfjo1bz22mtMnz6d8ePHo1ar+fjjj4mIiOD777/nmmuuOetzm81mJk2axLfffoufnx/Tp08/45jFixfz2muvkZycjK+vLxdffDHz5s0jPDwcRVFISEjgrrvuqvLYXbt20b17dw4ePEibNm14+umn+eCDD8jKyiIkJISrrrqK+fPn1803rBY16MT+r0wmVylZcHAwANu3b8dmszFs2DD3Me3btyc2NpZNmzbRt29fNm3axAUXXFClNH/kyJFMmjSJvXv30r179zNex2KxYDltfm5RUREANpsNm81WJ++ttlTG19DjFNXTFM+ns7QU6+HDWJKTsaYcxJKSgjUlBWdx8RnHqv38MI4Yjs9FF2FITEQbFXXGKLwTcDaS709TPJ/NmZzPpqWxnU+H08G2rG0sO7qMn0/+TJm9DACtSssVba/gjs53EOIdAoDT4cTpcP7T0zU5je18in/WYM+nw4bm64moT25D8QrAfuUiUOmhluK02WwoioLT6cRZsZKPoiju/+/1yVvrjaoaFyysVisOhwO9Xu+OGcDb25vNmzZy21TISU8lMzOTIUOGuN+fn58fffr0YePGjfzf//3fWZ97+vTprFu3ju+++47w8HAee+wxduzYQdeuXd2vZbFYePrpp0lMTCQ7O5vp06czceJEli1bBsDNN9/MokWLmDZtmvt5P/jgAwYNGkTr1q356quvmDt3Lp999hmdOnUiMzOT3bt3V3kvtc3pdKIoCjabDY2m6kXYmvzMN5rE3ul0MmXKFAYMGEDnzp0ByMzMRK/XE/iXLtYRERFkVoz0ZWZmVknqK/dX7jubF198kaeffvqM7atWrcLHx+d830q9WL16tadDELWoUZ5PRUFbUIAhMxNDeobrNiMDXV4eKuXMdUsVtRpreDiWFpFYIltgadGCstatUHQ6sFphzx7XvyagUZ5P8bfkfDYtDf18Zjgy2GXdxR/WPyhWTl0QDVYH01XXle767gTnBLPlly0ejLLhaOjnU9RMgzqfipMex98lpmAjDpWOjTH3kr9pP7C/1l5Cq9USGRlJSUkJ1orpiGX2MkYsG1Frr1Fdq0avwltbvX5FvXr14umnnyY6Oprw8HC+/vprNm3aREx8a1RAeupxAMLCwig+bWAnODiYkydP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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import math\n", - "\n", - "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", - " lock_duration = max(end_day - current_day, 0)\n", - " time_factor = -lock_duration / interval \n", - " exp_term = 1 - math.exp(time_factor)\n", - " conviction_score = lock_amount * exp_term\n", - " return int(conviction_score)\n", - "\n", - "\n", - "def calculate_max_allowed_unstakable(alpha_locked: int, end_day: int, current_day: int, interval: int) -> int:\n", - " return alpha_locked - calculate_conviction(alpha_locked, end_day=end_day, current_day = current_day, interval=interval)\n", - " \n", - "\n", - "import matplotlib.pyplot as plt\n", - "\n", - "# Define intervals from 10 days to 3 years\n", - "intervals = [10, 30, 90, 180, 365, 365*2, 365*3]\n", - "\n", - "# Generate data for 3 years (assuming 1 block per day for simplicity)\n", - "days = range(0, 365)\n", - "\n", - "# Create the plot\n", - "plt.figure(figsize=(12, 6))\n", - "\n", - "for interval in intervals:\n", - " unstakable_amounts = [calculate_max_allowed_unstakable(1000, 365, day, interval=interval) for day in days]\n", - " plt.plot(days, unstakable_amounts, label=f'{interval} days')\n", - "\n", - "plt.title('Max Allowed Unstakable Amount Over 3 Years')\n", - "plt.xlabel('Days')\n", - "plt.ylabel('Max Allowed Unstakable Amount')\n", - "plt.legend()\n", - "plt.grid(True)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 84, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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3do4e/Zsgj4P+mz9d5nGH3/80ahVq5bh6+trfa3vvPOOceHChQe+pyIitmIyjGTMriQiIiIiIiIiaY7mHBARERERERF5wSkcEBEREREREXnBKRwQERERERERecEpHBARERERERF5wSkcEBEREREREXnBKRwQERERERERecE52LqAF4nZbOb8+fN4enpiMplsXY6IiIiIiIikcYZhEB4ejr+/P3Z2Dx4foHDgGTp//jwBAQG2LkNEREREREReMGfOnCFbtmwP3K5w4Bny9PQELCfFy8vLxtU8WExMDMuWLaNWrVo4Ojrauhx5QjqfaYvOZ9qi85l26FymLTqfaYvOZ9qi85l8YWFhBAQEWK9HH0ThwDMUfyuBl5dXqg8H3Nzc8PLy0i9cGqDzmbbofKYtOp9ph85l2qLzmbbofKYtOp+P71G3tmtCQhEREREREZEXnMIBERERERERkRecwgERERERERGRF5zmHEhlDMMgNjaWuLg4m9UQExODg4MDd+7csWkdkjJ0PlOWvb09Dg4OWo5URERERNIUhQOpSHR0NBcuXCAyMtKmdRiGQZYsWThz5owugNIAnc+U5+bmhp+fH05OTrYuRUREREQkRSgcSCXMZjMnTpzA3t4ef39/nJycbHYhZzabuXXrFh4eHtjZ6c6T553OZ8oxDIPo6GiuXLnCiRMnyJMnj95TEREREUkTFA6kEtHR0ZjNZgICAnBzc7NpLWazmejoaFxcXHThkwbofKYsV1dXHB0dOXXqlPV9FRERERF53ulKIZXRxZtI6qffUxERERFJa/QvXBEREREREZEXnMIBERERERERkRecwgFJM4YOHUrRokVtvo+kWrlyJfnz50+1ywtWqVKFnj17PrPjlS1blp9++umZHU9ERERERP6lcECe2MWLF+nWrRs5c+bE2dmZgIAAGjZsyMqVK59pHX379k3WMU0mE7/++usT7eNJ9O/fn/fffx97e3sA/v77b0JCQsiQIQOurq7ky5ePzz77LNHzzp07x+uvv27tV6hQIbZt22bd3rZtW0wmk/XH3t6eZs2aPZPX9CTef/99Bg4ciNlstnUpIiIiIiIvHK1WIE/k5MmThISE4OPjw5gxYyhUqBAxMTEsXbqULl26cPDgwWdWi4eHBx4eHjbfR1L8/fffHDt2jKZNm1rb3N3d6dq1K4ULF8bd3Z2///6b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jVaciIiI/E51tbDxfTMUKE432/wiYNgd0P968PRzb30ictqV1JTw9+V/Z376fBYdWsSzo5/FYrEoGBCRBk4qHDj33HPx8vJyfa0XEhERkWaurgY2vAdLnoGSg2abf5QZCvS7Djx93VufiDSJDbkbuHfxvWRVZOFh9aBfVD93lyQizdRJhQMPP/yw6+u///3vp6sWERER+b3qamDd/2Dpc1B6yGzzj4bhd0K/a8Hu48biRKSpOA0nb2x5g5fWv0S9UU9CQAJPjXqKbmHd3F2aiDRTpzznwI033shVV13F6NGjT0M5IiIi8ps4qg+HAs9CWabZFhADw++CvtcoFBBpQwqqCrh/yf38mPUjAFOSp/Dg4Afx99TcIiLyy045HMjLy2Py5MlERERw2WWXcdVVV9GrV6/TUZuIiIiciKMK1r4Ny56DsiyzLTDODAX6XA12b7eWJyJNz8Pqwf7S/fh4+PDAwAc4r+N5uixYRE7olMOBL7/8kqKiImbOnMn777/PM888Q5cuXbjyyiu54oorSEpKOg1lioiISAOOKljzphkKlOeYbYHxMOJu6HMVeHi5tTwRaVp1zjpsFhsWi4UgryCeHf0svh6+tA9u7+7SRKSFsP6WO4WEhHDTTTexcOFCDhw4wLXXXss777xDx44dG7s+ERER+anaClj+EjzXE757wAwGgtrBWc/B7ethwA0KBkTamIzyDKbOmcrnez53tXUP765gQEROyW8KB45wOBysWbOGlStXsn//fqKiok7p/o899hgDBgwgICCAyMhIzjvvPHbu3Nlgn+rqam699VbCwsLw9/fnwgsvJCcnp8E+6enpnHnmmfj6+hIZGck999xDXV1dg30WLlxI37598fLyomPHjrz11lvH1DNjxgySkpLw9vZm0KBBrFq16pRrEREROS1qK2DZ8/B8L5j7V6jIheB2cPYLcNta6H8deHi6u0oRaUKGYfD13q+56KuL2JS3iRnrZ1BTX+PuskSkhfpN4cAPP/zAtGnTiIqK4tprryUwMJBZs2Zx6NChU3qcRYsWceutt/Ljjz8yb948HA4HEydOpKKiwrXPXXfdxddff83MmTNZtGgRmZmZXHDBBa7t9fX1nHnmmdTW1rJ8+XLefvtt3nrrLR566CHXPmlpaZx55pmMGTOGDRs2cOedd3LjjTfy3Xffufb56KOPuPvuu3n4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wgERERFpEtdeey0WiwWLxYLdbicqKooJEybwxhtv4HQ63V2eiIhIm6ZwQERERJrM5MmTycrKYv/+/Xz77beMGTOGO+64g7POOou6ujp3lyciItJmKRwQERGRJuPl5UV0dDRxcXH07duXv/zlL3z55Zd8++23vPXWWwA888wz9OjRAz8/PxISEvjjH/9IeXk5ABUVFQQGBvLJJ580eNwvvvgCPz8/ysrKqK2tZfr06cTExODt7U1iYiKPPfZYUz9VERGRFkXhgIiIiLjV2LFj6dWrF5999hkAVquVF154ga1bt/L222/z/fffc++99wLg5+fHZZddxptvvtngMd58800uuugiAgICeOGFF/jqq6/4+OOP2blzJ++99x5JSUlN/bRERERaFA93FyAiIiLSpUsXNm3aBMCdd97pak9KSuL//u//uPnmm3n55ZcBuPHGGxk6dChZWVnExMSQm5vL7NmzmT9/PgDp6emkpKQwfPhwLBYLiYmJTf58REREWhqNHBARERG3MwwDi8UCwPz58xk3bhxxcXEEBARw9dVXU1BQQGVlJQADBw6kW7duvP322wC8++67JCYmMnLkSMCc+HDDhg107tyZ22+/nblz57rnSYmIiLQgCgdERETE7bZv305ycjL79+/nrLPOomfPnnz66aesXbuWGTNmAFBbW+va/8Ybb3TNUfDmm29y3XXXucKFvn37kpaWxqOPPkpVVRWXXHIJF110UZM/JxERkZZE4YCIiIi41ffff8/mzZu58MILWbt2LU6nk6effprBgwfTqVMnMjMzj7nPVVddxYEDB3jhhRfYtm0bU6dObbA9MDCQSy+9lP/+97989NFHfPrppxQWFjbVUxIREWlxNOeAiIiINJmamhqys7Opr68nJyeHOXPm8Nhjj3HWWWdxzTXXsGXLFhwOBy+++CJnn302y5Yt49VXXz3mcUJCQrjgggu45557mDhxIvHx8a5tzzzzDDExMfTp0wer1crMmTOJjo4mODi4CZ+piIhIy6KRAyIiItJk5syZQ0xMDElJSUyePJkffviBF154gS+//BKbzUavXr145plneOKJJ+jevTvvvffeLy5DeMMNN1BbW8v111/foD0gIIAnn3yS/v37M2DAAPbv38/s2bOxWvVvj4iI4kX0zgAAALZJREFUyC+xGIZhuLsIERERkVP1zjvvcNddd5GZmYmnp6e7yxEREWnRdFmBiIiItCiVlZVkZWXx+OOP84c//EHBgIiISCPQ+DoRERFpUZ588km6dOlCdHQ0DzzwgLvLERERaRV0WYGIiIiIiIhIG6eRAyIiIiIiIiJtnMIBEfn/7diBAAAAAIAgf+tBLowAAIA5OQAAAABzcgAAAADm5AAAAADMyQEAAACYkwMAAAAwJwcAAABgLkrZF520n7l4AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import math\n", - "\n", - "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", - " lock_duration = max(end_day - current_day, 0)\n", - " time_factor = -lock_duration / interval \n", - " exp_term = 1 - math.exp(time_factor)\n", - " conviction_score = lock_amount * exp_term\n", - " return int(conviction_score)\n", - "\n", - "# Calculate conviction for 1000 over a range of durations from 10 days to a year\n", - "lock_amount = 1000\n", - "interval = 180\n", - "duration = 365\n", - "\n", - "plt.figure(figsize=(12, 6))\n", - "\n", - "unlockable = [0]\n", - "days = range(0, duration + 1) # +1 to include the last day\n", - "locked_amount = []\n", - "convictions = []\n", - "unlockable = [0]\n", - "for day in days:\n", - " current_locked = lock_amount + (7200 * 0.18 * day) - unlockable[day-1] * 0\n", - " locked_amount.append(current_locked)\n", - " conviction = calculate_conviction(current_locked, duration, day, interval=interval)\n", - " convictions.append(conviction)\n", - " unlockable.append(current_locked - conviction)\n", - "\n", - "plt.plot(days, convictions, label=f'Conviction ({duration} days)')\n", - "plt.plot(days, locked_amount, label=f'Locked Amount ({duration} days)')\n", - "plt.plot(days, unlockable[:-1], label=f'Unlockable ({duration} days)', linestyle='--')\n", - "\n", - "plt.title('Conviction Score for Various Lock Durations')\n", - "plt.xlabel('Days')\n", - "plt.ylabel('Conviction Score')\n", - "plt.grid(True)\n", - "plt.legend()\n", - "plt.show()\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 124, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Lion's Share Distribution:\n", - "Participant_0's lock: 6974, share: 0.0072\n", - "Participant_1's lock: 9165, share: 0.8613\n", - "Participant_2's lock: 8303, share: 0.1312\n", - "Participant_3's lock: 5278, share: 0.0002\n", - "Participant_4's lock: 2642, share: 0.0000\n", - "Participant_5's lock: 4272, share: 0.0000\n", - "Participant_6's lock: 4160, share: 0.0000\n", - "Participant_7's lock: 2101, share: 0.0000\n", - "Participant_8's lock: 3090, share: 0.0000\n", - "Participant_9's lock: 4980, share: 0.0001\n", - "\n", - "Lion's Share Skew Factors:\n", - "Participant_0's skew factor: 0.0528\n", - "Participant_1's skew factor: 4.7893\n", - "Participant_2's skew factor: 0.8054\n", - "Participant_3's skew factor: 0.0017\n", - "Participant_4's skew factor: 0.0000\n", - "Participant_5's skew factor: 0.0002\n", - "Participant_6's skew factor: 0.0002\n", - "Participant_7's skew factor: 0.0000\n", - "Participant_8's skew factor: 0.0000\n", - "Participant_9's skew factor: 0.0010\n" - ] - }, - { - "data": { - "image/png": "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e/yd2ujNbcDX11djxozRTz/9pGLFiumBBx7Q2LFjderUqdx6CQAcFCEdgFMKDg5W8eLF9dtvv2XpvMyOT/T09MzSfuOmCeEyY+3atXrkkUfk5+enqVOnavHixVq+fLm6dOmS4fPdeJftdjp27KjDhw9r0qRJKl68uN59911VqVJFP/30U5ZrlG79mh1BQECAGjVqpMmTJ+u1117ThQsXsv06M5IX7SCnff755/Y152fOnKklS5Zo+fLlatasWYZLZTnya5Hu/jN7p9cxfvx4/frrr3r11Vd17do1DRgwQFWqVMn0RH2S9fdRy5Yt9cUXX6h79+46dOhQmrHmmakvJSVFLVu21I8//qhXXnlFCxYs0PLlyzVr1ixJSvez8/X1TRcOU1NTFRISouXLl2f4deN8GNlh+317u2USQ0JCtGPHDi1cuFCPPPKIVq1apQcffFDdu3fP9HUy+7suJ2R2AsyckJk2+vzzz2v//v0aPXq0/Pz8NGzYMFWuXDnDnhQAXBchHYDTevjhh3Xo0CFt3Ljxjsfec889Sk1N1YEDB9LsP336tC5evKh77rknR2tLTU1N1812//79kmTvPj1//nz5+fnZ1yB+8MEH1aJFixy5flhYmJ599lktWLBAR44cUeHChfXWW29Jkv217tu3L915+/bty7H34lbXSUxM1JEjR3L8Pa9du7Yka/fsjISEhMjPzy/djNqSMtx3N+7mPba11UOHDqU7LzO++eYblSlTRt9++62efPJJtW7dWi1atEh3ZzezbLXe/NnJSk3ZvW5efWarVaum1157TWvWrNHatWt14sQJTZs2LVvPdad2eCu7du3S/v37NX78eL3yyit69NFH1aJFizRDM+6kbNmyOn/+vBo2bKgWLVqk+4qKispSTTf77LPPJOmOXdF9fHzUrl07TZ06VYcOHdLTTz+tTz/91P45y8pkbplxcxu5cuWKTp48mWaoSsGCBdOsKiFZfxfd/HPKSm333HOPDhw4kO4PKHv37rU/nh1ly5bViy++qGXLlum3335TYmKixo8fn63nAuCcCOkAnNbLL7+swMBAPfXUUzp9+nS6xw8dOmRf9uehhx6SJE2cODHNMRMmTJCkTM+cnRWTJ0+2bxuGocmTJ8vb21vNmzeXZL2rYrFY0tzJOXr0qBYsWJDta6akpKTrFhsSEqLixYvbl62qXbu2QkJCNG3atDRLWf3000/as2dPjr0XLVq0kI+Pj95///00d4pmzpypS5cuZfs6MTExGe63jT+9VRdsT09PtWjRQgsWLEgzXv/gwYM5evddurv32DY+/v3330+z/+a2eyu2u3U3vuebNm3K1B+zMhIWFqYaNWpo9uzZadrW8uXL9fvvv2frOTMjLz6zcXFxSk5OTrOvWrVq8vDwSLfM243i4+Nv+X7a2lJWhwJk9HMzDMP+OywzOnbsqJSUFL3xxhvpHktOTk4XUrNizpw5+uijj1S/fn3777CMnD9/Ps33Hh4e9tUabO+pbT33u6nnRtOnT1dSUpL9+w8++EDJyclp5pooW7as1qxZk+68m++kZ6W2hx56SKdOndLcuXPt+5KTkzVp0iQFBQXZhypkVnx8fLo/ppUtW1b58uW7bXsE4HpYgg2A0ypbtqzmzJmjTp06qXLlyurWrZuqVq2qxMREbdiwwb4UjiRFRUWpe/fumj59ui5evKjGjRtr8+bNmj17th577DE1bdo0R2vz8/PTkiVL1L17d9WtW1c//fSTfvzxR7366qv28d1t27bVhAkT1KZNG3Xp0kVnzpzRlClTVK5cOf3666/Zuu7ly5dVsmRJPf7444qKilJQUJBWrFihLVu22O/EeHt7a8yYMerZs6caN26szp0725cHi4iI0AsvvJAj70HRokU1dOhQjRo1Sm3atNEjjzyiffv2aerUqbrvvvv0n//8J1vP++ijj6p06dJq166dypYtq6tXr2rFihX64YcfdN9996ldu3a3PHfkyJFatmyZGjZsqH79+iklJUWTJ09W1apVtWPHjmy+0vTu5j2uUaOGOnfurKlTp+rSpUtq0KCBYmJiMn23/+GHH9a3336rf/3rX2rbtq2OHDmiadOmKTIy0j6mOKtGjx6ttm3b6v7771evXr30999/a9KkSapSpUqmn/Ps2bN688030+0vXbq0fY3vG+XFZ3blypWKjo5Whw4dVKFCBSUnJ+uzzz6Tp6en2rdvf8vz4uPj1aBBA9WrV09t2rRReHi4Ll68qAULFmjt2rV67LHHVLNmzSzVUqlSJZUtW1aDBw/WiRMnFBwcrPnz52dpArHGjRvr6aef1ujRo7Vjxw61atVK3t7eOnDggObNm6f33ntPjz/++B2f55tvvlFQUJASExN14sQJLV26VOvXr1dUVFS6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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", - " lock_duration = max(end_day - current_day, 0)\n", - " time_factor = -lock_duration / interval \n", - " exp_term = 1 - math.exp(time_factor)\n", - " conviction_score = lock_amount * exp_term\n", - " return int(conviction_score)\n", - "\n", - "\n", - "import random\n", - "\n", - "interval = 365\n", - "duration = 365\n", - "N = 10 # Number of participants\n", - "temperature = 5 # Adjust this value to control the steepness of the sigmoid\n", - "\n", - "# Generate random lock amounts for N participants\n", - "participants = [f\"Participant_{i}\" for i in range(N)]\n", - "locks = [random.randint(10, 10000) for _ in range(N)]\n", - "\n", - "# Calculate convictions\n", - "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", - "\n", - "# Calculate mean conviction\n", - "mean_conviction = sum(convictions) / len(convictions)\n", - "\n", - "# Calculate powered convictions using sigmoid function\n", - "powered_convictions = [1 / (1 + math.exp(-(conv - mean_conviction) / temperature)) for conv in convictions]\n", - "\n", - "# Calculate total powered conviction\n", - "total_powered = sum(powered_convictions)\n", - "\n", - "# Calculate shares\n", - "shares = [powered / total_powered for powered in powered_convictions]\n", - "\n", - "# # Print results\n", - "# for i, (participant, lock, share) in enumerate(zip(participants, locks, shares)):\n", - "# print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", - "\n", - "# # Calculate and print skew factors\n", - "# base_ratio = locks[0] / sum(locks)\n", - "# for i, (participant, lock, share) in enumerate(zip(participants, locks, shares)):\n", - "# skew_factor = (share / base_ratio) / (lock / locks[0])\n", - "# print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", - "\n", - "\n", - "import numpy as np\n", - "\n", - "# Function to calculate the \"lion's share\" distribution\n", - "def calculate_lions_share(convictions, sharpness=20):\n", - " # Normalize convictions\n", - " normalized_convictions = np.array(convictions) / np.max(convictions)\n", - " \n", - " # Apply exponential function to create a sharp drop-off\n", - " powered_convictions = np.exp(sharpness * (normalized_convictions - 1))\n", - " \n", - " # Calculate shares\n", - " total_powered = np.sum(powered_convictions)\n", - " shares = powered_convictions / total_powered\n", - " \n", - " return shares\n", - "\n", - "# Calculate convictions\n", - "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", - "\n", - "# Calculate shares using the lion's share distribution\n", - "lions_shares = calculate_lions_share(convictions)\n", - "\n", - "# Print results\n", - "print(\"\\nLion's Share Distribution:\")\n", - "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", - " print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", - "\n", - "# Calculate and print skew factors for lion's share\n", - "base_ratio = locks[0] / sum(locks)\n", - "print(\"\\nLion's Share Skew Factors:\")\n", - "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", - " skew_factor = (share / base_ratio) / (lock / locks[0])\n", - " print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", - "\n", - "# Visualize the difference between sigmoid and lion's share distributions\n", - "import matplotlib.pyplot as plt\n", - "\n", - "plt.figure(figsize=(12, 6))\n", - "plt.plot(range(N), sorted(shares, reverse=True), 'b-', label='Sigmoid Distribution')\n", - "plt.plot(range(N), sorted(lions_shares, reverse=True), 'r-', label=\"Lion's Share Distribution\")\n", - "plt.xlabel('Participant Rank')\n", - "plt.ylabel('Share')\n", - "plt.title('Comparison of Sigmoid and Lion\\'s Share Distributions')\n", - "plt.legend()\n", - "plt.grid(True)\n", - "plt.show()\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 121, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Lion's Share Distribution:\n", - "Participant_0's lock: 3916, share: 0.0015\n", - "Participant_1's lock: 440, share: 0.0000\n", - "Participant_2's lock: 8807, share: 0.3472\n", - "Participant_3's lock: 8077, share: 0.1545\n", - "Participant_4's lock: 6539, share: 0.0281\n", - "Participant_5's lock: 6470, share: 0.0260\n", - "Participant_6's lock: 148, share: 0.0000\n", - "Participant_7's lock: 701, share: 0.0000\n", - "Participant_8's lock: 2765, share: 0.0004\n", - "Participant_9's lock: 9026, share: 0.4422\n", - "\n", - "Lion's Share Skew Factors:\n", - "Participant_0's skew factor: 0.0184\n", - "Participant_1's skew factor: 0.0035\n", - "Participant_2's skew factor: 1.8483\n", - "Participant_3's skew factor: 0.8967\n", - "Participant_4's skew factor: 0.2016\n", - "Participant_5's skew factor: 0.1886\n", - "Participant_6's skew factor: 0.0075\n", - "Participant_7's skew factor: 0.0029\n", - "Participant_8's skew factor: 0.0073\n", - "Participant_9's skew factor: 2.2970\n" - ] - }, - { - "data": { - "image/png": "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euWVVxQYGCgfHx9VqFBBCxcuTLBNaj5rZ86cUZcuXVSoUCF5e3urQIECevbZZzn9HnAxzvGVNQAkYfXq1SpWrJhq1qyZou27du2qhQsXqlWrVurfv7927dqlsLAwHThwIFHB8eeff6pdu3bq3r27XnrpJU2cOFHNmzfXrFmzNGzYMPXq1UuSFBYWphdeeCHRaZixsbFq1KiRqlevrgkTJmjdunUaNWqUYmJiNGbMGPt2U6ZM0TPPPKP27dsrKipKS5YsUevWrfXVV1+padOmCTJt2bJF//vf/9SnTx/lzZs32dNEe/TooeXLl6tPnz4qU6aM/v33X3377bc6cOCAHn30UUnmf0a7dOmiqlWrKiwsTGfPntWUKVO0Y8cO/fjjjwl6tGNjY9WwYUNVq1ZNEydO1KZNmzRp0iQVL15cPXv2vOt7Pnr0aIWGhqpevXrq2bOnDh06pJkzZ2rPnj3asWOHPD09NXnyZC1atEiff/65/TTb8uXLJ7vPBx98UJs3b9bff/+tQoUK3fX4d/rxxx/VqFEjFShQQKGhoYqNjdWYMWOUL1++JLffvn27vvzyS/Xu3VuS+ftu1qyZBg0apBkzZqhXr166ePGiJkyYoJdffllbtmyxPzc17/Gdunbtqk8++UTt2rVTzZo1tWXLlkTtITkRERH68MMP9eKLL6pbt266cuWK5s2bp4YNG2r37t2qWLFigu0XL16sK1euqHv37rLZbJowYYKef/55HT161N5LumHDBrVs2VJlypRRWFiY/v33X3shkZFS+5lt1aqVXnnlFXXq1EkfffSROnfurMqVK+uRRx5Jcv9RUVFq2LChIiMj9dprrykoKEinTp3SV199pUuXLilHjhxJPi9//vzy9fXV6tWr9dprryl37tz3fC3jxo2Tm5ubBgwYoMuXL2vChAlq3769du3aZd9m2bJlun79unr27Kk8efJo9+7dmjp1qv7++28tW7Yswf5iYmLUsGFDPfHEE5o4caL8/PwkSd27d7e3vb59++rYsWOaNm2afvzxR/tnLq06dOigOXPmaMOGDapfv36S22zcuFEvvviinn76aY0fP16SdODAAe3YsUOvv/66nnrqKfXt21cffPCBhg0bptKlS0uS/adkntb+4osvqnv37urWrZsefvjhu+bq06ePcubMqdGjR9v/xpw4ccI+BkFKpSTb7W7c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f37NmjUrwVRWa9eu1YEDB9LtvahXr568vLz0wQcfJOgpmjdvni5fvpzm42zevDnJ9fHXnyZ3Cra7u7vq1aunVatWJbhe/88//0zX3nfp/t7j+OvjP/jggwTr72y7yYnvrbv9Pd+1a1eKvsxKSoECBVSxYkUtXLgwQdvauHGjfv/99zTtMyUy4zMbERGhmJiYBOvKlSsnNze3RNO83e769evJvp/xbSm1lwIk9XszDMP+NywlXnjhBcXGxmrs2LGJHouJiUlUpKbG4sWL9eGHH6pGjRr2v2FJ+ffffxPcd3Nzs8/WEP+exs/nfj95bjdnzhxFR0fb78+cOVMxMTEJxpooXry4tm3bluh5d/akpyZbkyZNdObMGS1dutS+LiYmRlOnTpW/v7/9UoWUun79eqIv04oXL67s2bPftT0CyHqYgg2A0ypevLgWL16sNm3aqHTp0urYsaPKli2rqKgofffdd/apcCSpQoUK6tSpk+bMmaNLly6pVq1a2r17txYuXKgWLVqoTp066ZrNx8dH69atU6dOnVStWjWtXbtWX3/9tYYNG2a/vrtp06Z677331KhRI7Vr107nzp3T9OnTVaJECf38889pOu6VK1dUqFAhtWrVShUqVJC/v782bdqkPXv22HtiPD09NX78eHXp0kW1atXSiy++aJ8eLDg4WG+88Ua6vAf58uXT0KFDFRoaqkaNGumZZ57RoUOHNGPGDFWtWlUvvfRSmvb77LPPqmjRomrevLmKFy+ua9euadOmTVq9erWqVq2q5s2bJ/vc0aNHa8OGDXr88cfVs2dPxcbGatq0aSpbtqz279+fxlea2P28xxUrVtSLL76oGTNm6PLly6pZs6Y2b96c4t7+Zs2aaeXKlXruuefUtGlTHTt2TLNmzVKZMmXs1xSnVlhYmJo2baonnnhCL7/8sv777z9NnTpVjzzySIr3ef78eb311luJ1hctWtQ+x/ftMuMzu2XLFvXp00etW7dWyZIlFRMTo48//lju7u5q2bJlss+7fv26atasqerVq6tRo0YqXLiwLl26pFWrVmn79u1q0aKFKlWqlKospUqVUvHixTVgwACdOnVKAQEBWrFiRaoGEKtVq5a6d++usLAw7d+/Xw0aNJCnp6cOHz6sZcuWacqUKWrVqtU997N8+XL5+/srKipKp06d0vr167Vjxw5VqFAh0VRwd+ratav+++8/1a1bV4UKFdKJEyc0depUVaxY0X6tdsWKFeXu7q7x48fr8uXL8vb2Vt26dZOcXz4loqKi9PTTT9unw5wxY4aeeOIJPfPMMwly9ejRQy1btlT9+vX1008/af369cqbN2+CfaUm26uvvqrZs2erc+fO2rt3r4KDg7V8+XLt2LFDkydPTvHApvH++OMP++soU6aMPDw89Pnnn+vs2bNq27Ztmt4bAE7KkjHlASAd/fHHH0a3bt2M4OBgw8vLy8iePbvx+OOPG1OnTjVu3rxp3y46OtoIDQ01ihYtanh6ehqFCxc2hg4dmmAbwzCn5GnatGmi40hKNLVZ/NQ6t0+Z06lTJyNbtmzGkSNHjAYNGhh+fn5GYGCgMWrUqETThM2bN8946KGHDG9vb6NUqVLG/PnzE01Fldyxb38sfmqhyMhIY+DAgUaFChWM7NmzG9myZTMqVKhgzJgxI9Hzli5dalSqVMnw9vY2cufObbRv3974+++/E2wT/1rulFTG5EybNs0oVaqU4enpaQQGBho9e/Y0Ll68mOT+UjIF22effWa0bdvWKF68uOHr62v4+PgYZcqUMd58880E06IZRtLTLm3evNmoVKmS4eXlZRQvXtz48MMPjf79+xs+Pj6JnpuS37dh3Jpm686pvFLyHif1Xt64ccPo27evkSdPHiNbtmxG8+bNjb/++itFU7DFxcUZ77zzjvHggw8a3t7eRqVKlYyvvvoq0VRZyb2W+Nd+53FWrFhhlC5d2vD29jbKlCljrFy5MtE+k1OrVi1DUpK3p59+Otn34X4/s3dOmXWno0ePGi+//LJRvHhxw8fHx8idO7dRp04dY9OmTXd9PdHR0cbcuXONFi1a2N9nPz8/o1KlSsa7776bYKq15NpG/Ps/f/58+7rff//dqFevnuHv72/kzZvX6Natm/HTTz8l2i65z2W8OXPmGJUrVzZ8fX2N7NmzG+XKlTMGDRpk/PPPP3d9XfG/g/ibj4+PUahQIaNZs2bGRx99lOh9j89yextYvny50aBBAyN//vyGl5eXUaRIEaN79+7G6dOnEzxv7ty5RrFixQx3d/cEU54l97uMfyypKdi++eYb49VXXzVy5cpl+Pv7G+3btzf+/fffBM+NjY01Bg8ebOTNm9fw8/MzGjZsaPz555+J9nm3bEm1p7NnzxpdunQx8ubNa3h5eRnlypVL8LsyjJR/1i5cuGD07t3bKFWqlJEtWzYjR44cRrVq1Yz//e9/Sb4fALIum2E4yMgwAJBFdO7cWcuXL09zryUyX4sWLfTbb78led01AABAZuKadACAS7lx40aC+4cPH9aaNWtUu3ZtawIBAADchmvSAQAupVixYurcubN9rvaZM2fKy8tLgwYNsjoaAAAARToAwLU0atRIn332mc6cOSNvb2/VqFFD77zzjh566CGrowEAAIhr0gEAAAAAcBBckw4AAAAAgIOgSAcAAAAAwEG43DXpcXFx+ueff5Q9e3bZbDar4wAAAAAAsjjDMHTlyhUVLFhQbm537yt3uSL9n3/+UeHCha2OAQAAAABwMX/99ZcKFSp0121crkjPnj27JPPNCQgIsDjN3UVHR2vDhg1q0KCBPD09rY4DZBjaOlwJ7R2uhPYOV0J7x91ERESocOHC9nr0blyuSI8/xT0gIMApinQ/Pz8FBATwQUeWRluHK6G9w5XQ3uFKaO9IiZRccs3AcQAAAAAAOAiKdAAAAAAAHARFOgAAAAAADsLlrkkHAAAAXI1hGIqJiVFsbKzVUbKs6OhoeXh46ObNm7zPLsrT01Pu7u73vR+KdAAAACALi4qK0unTp3X9+nWro2RphmEoKChIf/31V4oGB0PWY7PZVKhQIfn7+9/XfijSAQAAgCwqLi5Ox44dk7u7uwoWLCgvLy8KyAwSFxenq1evyt/fX25uXFXsagzD0Pnz5/X333/roYceuq8edYp0AAAAIIuKiopSXFycChcuLD8/P6vjZGlxcXGKioqSj48PRbqLypcvn44fP67o6Oj7KtJpPQAAAEAWR9EIZLz0OkuFTysAAAAAAA6CIh0AAAAAAAdBkQ4AAADAKdlsNq1atcrqGAoPD5e7u7suX76c7DYLFixQzpw50+V46bmv2x0/flw2m0379++XZL4um82mS5cuZfixcAtFOgAAAACHc/78efXs2VNFihSRt7e3goKC1LBhQ+3YscO+zenTp9W4cWMLU5pq1qypU6dOKSAg4L72Y7PZ7Lds2bLpoYceUufOnbV3794E27Vp00Z//PFHivaZmoK+cOHCOn36tMqWLZva6HfVuXNntWjRIlOOlRVQpAMAAABwOC1bttSPP/6ohQsX6o8//tCXX36p2rVr699//7VvExQUJG9vbwtTmry8vBQU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tX//fvvtwIEDCU43T6l///1X58+fV9GiRZN8vGvXrjp69Kg6dOigX375RVWqVNHUqVPvud/bC+K0cnNzk3HHJbwZeY14atpMeqFIdyVt20rLl0teXtKyZVLLltLNm1anAgAAAO5L6dKl9dNPP+natWv2dTt27JCbm5sefvjhNO/X19dXzZs31wcffKDw8HDt3LkzVb2oZcqUkaQEue5m7969iouL06RJk1S9enWVLFlS//zzzz2fV6lSJcXGxurcuXMqUaJEgtvdTvlOzpQpU+Tm5nbXKfAKFy6sHj16aOXKlerfv7/mzp0ryezll8xR59Pq+++/ty/HxMRo7969Kl26tCQpX758unLlSoL39M551b28vO55/IxqM+mBIt3VtGghffGF5OMjrV4tPfNMhg3gAQAAANyPy5cvJ+gZ3r9/v/76669E27Vv314+Pj7q1KmTfv31V23dulWvvfaaOnToYD9tObXie8F//fVXHT16VJ988ol8fX314IMPJrl969atNWPGDO3atUsnTpxQeHi4evfurZIlS6pUqVIpOmaJEiUUHR2tqVOn6ujRo/r444/vOsBavJIlS6p9+/bq2LGjVq5cqWPHjmn37t0KCwvT119/fdfnXrlyRWfOnNFff/2lbdu26dVXX9Vbb72lt99+WyVKlEjyOf369dP69et17Ngx7du3T1u3brUX0Q8++KBsNpu++uornT9/PsFo6Sk1ffp0ff755zp48KB69+6tixcv6uWXX5YkVatWTX5+fho2bJiOHDmixYsXJxrILTg4WMeOHdP+/ft14cIFRUZGJjpGRrSZ9EKR7ooaNZLWrjVPOdq4UWrcWLpyxepUAAAAQALh4eGqVKlSgltoaGii7fz8/LR+/Xr9999/qlq1qlq1aqWnn35a06ZNS/Oxc+bMqblz5+rxxx9X+fLltWnTJq1evTrZa7wbNGigdevW6dlnn1XJkiXVqVMnlSpVShs2bEj2OvY7VahQQe+9957Gjx+vsmXL6tNPP1VYWFiKnjt//nx17NhR/fv318MPP6wWLVpoz549KlKkyF2fN3LkSBUoUEAlSpRQhw4ddPnyZW3evFmDBw9O9jmxsbHq3bu3SpcurUaNGqlkyZL2ge0eeOABhYaGasiQIQoMDEw0WnpKjBs3TuPGjVOFChX07bff6ssvv1TevHklmQPTffLJJ1qz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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import numpy as np\n", - "\n", - "# Function to calculate the \"lion's share\" distribution\n", - "def calculate_lions_share(convictions, sharpness=10):\n", - " # Normalize convictions\n", - " normalized_convictions = np.array(convictions) / np.max(convictions)\n", - " \n", - " # Apply exponential function to create a sharp drop-off\n", - " powered_convictions = np.exp(sharpness * (normalized_convictions - 1))\n", - " \n", - " # Calculate shares\n", - " total_powered = np.sum(powered_convictions)\n", - " shares = powered_convictions / total_powered\n", - " \n", - " return shares\n", - "\n", - "# Calculate convictions\n", - "convictions = [calculate_conviction(lock, duration, 0, interval) for lock in locks]\n", - "\n", - "# Calculate shares using the lion's share distribution\n", - "lions_shares = calculate_lions_share(convictions)\n", - "\n", - "# Print results\n", - "print(\"\\nLion's Share Distribution:\")\n", - "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", - " print(f\"{participant}'s lock: {lock}, share: {share:.4f}\")\n", - "\n", - "# Calculate and print skew factors for lion's share\n", - "base_ratio = locks[0] / sum(locks)\n", - "print(\"\\nLion's Share Skew Factors:\")\n", - "for i, (participant, lock, share) in enumerate(zip(participants, locks, lions_shares)):\n", - " skew_factor = (share / base_ratio) / (lock / locks[0])\n", - " print(f\"{participant}'s skew factor: {skew_factor:.4f}\")\n", - "\n", - "# Visualize the difference between sigmoid and lion's share distributions\n", - "import matplotlib.pyplot as plt\n", - "\n", - "plt.figure(figsize=(12, 6))\n", - "plt.plot(range(N), sorted(shares, reverse=True), 'b-', label='Sigmoid Distribution')\n", - "plt.plot(range(N), sorted(lions_shares, reverse=True), 'r-', label=\"Lion's Share Distribution\")\n", - "plt.xlabel('Participant Rank')\n", - "plt.ylabel('Share')\n", - "plt.title('Comparison of Sigmoid and Lion\\'s Share Distributions')\n", - "plt.legend()\n", - "plt.grid(True)\n", - "plt.show()\n" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "770" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import math\n", - "\n", - "def calculate_conviction(lock_amount: int, end_day: int, current_day: int, interval: int) -> int:\n", - " lock_duration = max(end_day - current_day, 0)\n", - " time_factor = -lock_duration / interval \n", - " exp_term = 1 - math.exp(time_factor)\n", - " conviction_score = lock_amount * exp_term\n", - " return int(conviction_score)\n", - "\n", - "\n", - "lock = 1000\n", - "calculate_conviction(lock, 365, 100, 180)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.9" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From d719cf6508589256e900f7eafb9b87d95cca50e5 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 4 Aug 2024 12:46:34 +0400 Subject: [PATCH 132/269] chore: fmt --- pallets/admin-utils/src/lib.rs | 2 +- runtime/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index c29323e4c..c9dddad19 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1128,7 +1128,7 @@ pub mod pallet { Ok(()) } - } + } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b08ddf539..3ba2ac180 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -16,7 +16,7 @@ use frame_support::traits::Imbalance; use frame_support::{ dispatch::DispatchResultWithPostInfo, genesis_builder_helper::{build_config, create_default_config}, - pallet_prelude::{DispatchError, Get}, + pallet_prelude::Get, traits::{fungible::HoldConsideration, Contains, LinearStoragePrice, OnUnbalanced}, }; use frame_system::{EnsureNever, EnsureRoot, EnsureRootWithSuccess, RawOrigin}; @@ -141,7 +141,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: 192, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From d35d20bcfdad66731fdb2e4d090551e279ec6f1f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 4 Aug 2024 12:50:33 +0400 Subject: [PATCH 133/269] chore: bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 303e7040f..fd23d1fd3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 165, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 02325f3620fba06bc405e986b7acfc4c1514f469 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 4 Aug 2024 13:08:13 +0400 Subject: [PATCH 134/269] bump spec , formats --- pallets/subtensor/src/lib.rs | 10 --- pallets/subtensor/tests/root.rs | 104 ----------------------------- pallets/subtensor/tests/staking.rs | 3 - 3 files changed, 117 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index dd6948c56..6388e0331 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1347,20 +1347,10 @@ where priority: Self::get_priority_vanilla(), ..Default::default() }), -<<<<<<< HEAD - - Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }) - } - } -======= Some(Call::dissolve_network { .. }) => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() }), ->>>>>>> origin/devnet-ready _ => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 9cc694a52..84df71d83 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -7,10 +7,6 @@ use frame_system::{EventRecord, Phase}; use pallet_subtensor::migrations; use pallet_subtensor::Error; use sp_core::{Get, H256, U256}; -use sp_runtime::{ - traits::{DispatchInfoOf, SignedExtension}, - transaction_validity::{InvalidTransaction, TransactionValidityError}, -}; mod mock; @@ -983,103 +979,3 @@ fn test_dissolve_network_does_not_exist_err() { ); }); } - -#[test] -fn test_dissolve_network_validate() { - fn generate_valid_pow(coldkey: &U256, block_number: u64, difficulty: U256) -> (H256, u64) { - let mut nonce: u64 = 0; - loop { - let work = SubtensorModule::create_seal_hash(block_number, nonce, coldkey); - if SubtensorModule::hash_meets_difficulty(&work, difficulty) { - return (work, nonce); - } - nonce += 1; - } - } - // Testing the signed extension validate function - // correctly filters the `dissolve_network` transaction. - - new_test_ext(0).execute_with(|| { - let netuid: u16 = 1; - let delegate_coldkey = U256::from(1); - let delegate_hotkey = U256::from(2); - let new_coldkey = U256::from(3); - let current_block = 0u64; - - add_network(netuid, 0, 0); - register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); - - // Make delegate a delegate - assert_ok!(SubtensorModule::become_delegate( - RuntimeOrigin::signed(delegate_coldkey), - delegate_hotkey - )); - - // Add more than 500 TAO of stake to the delegate's hotkey - let stake_amount = 501_000_000_000; // 501 TAO in RAO - let delegator = U256::from(4); - SubtensorModule::add_balance_to_coldkey_account(&delegator, stake_amount); - assert_ok!(SubtensorModule::add_stake( - RuntimeOrigin::signed(delegator), - delegate_hotkey, - stake_amount - )); - - // Ensure the delegate's coldkey has less than minimum balance - assert!( - SubtensorModule::get_coldkey_balance(&delegate_coldkey) - < MIN_BALANCE_TO_PERFORM_COLDKEY_SWAP, - "Delegate coldkey balance should be less than minimum required" - ); - - // Ensure the delegate's hotkey has more than 500 TAO delegated - assert!( - SubtensorModule::get_total_delegated_stake(&delegate_coldkey) >= 500_000_000_000, - "Delegate hotkey should have at least 500 TAO delegated" - ); - - // Generate valid PoW - let (work, nonce) = generate_valid_pow( - &delegate_coldkey, - current_block, - U256::from(4) * U256::from(BaseDifficulty::::get()), - ); - - // Schedule coldkey swap - assert_ok!(SubtensorModule::do_schedule_coldkey_swap( - &delegate_coldkey, - &new_coldkey, - work.to_fixed_bytes().to_vec(), - current_block, - nonce, - )); - - // Verify that the swap was scheduled - assert_eq!( - ColdkeySwapDestinations::::get(delegate_coldkey), - vec![new_coldkey] - ); - - // Check if the coldkey is in arbitration - assert!( - SubtensorModule::coldkey_in_arbitration(&delegate_coldkey), - "Delegate coldkey should be in arbitration after swap" - ); - - let who = delegate_coldkey; // The coldkey signs this transaction - let call = RuntimeCall::SubtensorModule(SubtensorCall::dissolve_network { netuid }); - - let info: DispatchInfo = - DispatchInfoOf::<::RuntimeCall>::default(); - - let extension = pallet_subtensor::SubtensorSignedExtension::::new(); - - // Submit to the signed extension validate function - let result_in_arbitration = extension.validate(&who, &call.clone(), &info, 10); - // Should fail with InvalidTransaction::Custom(6) because coldkey is in arbitration - assert_err!( - result_in_arbitration, - TransactionValidityError::Invalid(InvalidTransaction::Custom(6)) - ); - }); -} diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 3cf9609d7..5bf95841a 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,9 +1,6 @@ #![allow(clippy::unwrap_used)] #![allow(clippy::arithmetic_side_effects)] -use frame_support::pallet_prelude::{InvalidTransaction, TransactionValidity}; -use frame_support::traits::{OnFinalize, OnIdle, OnInitialize}; -use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; From 22cc4b5f664bfd1d611fff79f2cfdaf8126feb51 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 31 Jul 2024 11:40:56 +0400 Subject: [PATCH 135/269] chore: skip wasm builds on test scripts --- scripts/test_specific.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/test_specific.sh b/scripts/test_specific.sh index 018872d33..85f3ebe30 100755 --- a/scripts/test_specific.sh +++ b/scripts/test_specific.sh @@ -1,6 +1,4 @@ pallet="${3:-pallet-subtensor}" features="${4:-pow-faucet}" -# RUST_LOG="pallet_subtensor=info" cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact - -RUST_LOG=INFO cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file +SKIP_WASM_BUILD=1 RUST_LOG=DEBUG cargo test --release --features=$features -p $pallet --test $1 -- $2 --nocapture --exact \ No newline at end of file From 21e3c26a5f64c0dd135343c1277a3f19c4e62684 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Sun, 4 Aug 2024 13:37:53 +0400 Subject: [PATCH 136/269] feat: patch errors and assertions --- pallets/subtensor/src/macros/errors.rs | 4 ++++ pallets/subtensor/src/swap/swap_coldkey.rs | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index d51469482..9c344ab8d 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -174,5 +174,9 @@ mod errors { SwapAlreadyScheduled, /// failed to swap coldkey FailedToSchedule, + /// New coldkey is hotkey + NewColdKeyIsHotkey, + /// New coldkey is in arbitration + NewColdkeyIsInArbitration, } } diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 2b54d4313..5b02d49b5 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -35,18 +35,27 @@ impl Pallet { ) -> 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::::ColdKeyAlreadyAssociated + Error::::NewColdKeyIsHotkey ); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + + // TODO: Consider adding a check to ensure the new coldkey is not in arbitration + // ensure!( + // !Self::coldkey_in_arbitration(new_coldkey), + // Error::::NewColdkeyIsInArbitration + // ); + + // Note: We might want to add a cooldown period for coldkey swaps to prevent abuse // 5. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); From 9b557c43e484cb8feda2a4ccbc95561630fa7a41 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 5 Aug 2024 16:54:45 +0400 Subject: [PATCH 137/269] feat: fix child take tests --- pallets/subtensor/src/lib.rs | 6 +- pallets/subtensor/src/macros/dispatches.rs | 2 +- pallets/subtensor/src/staking/set_children.rs | 30 ++-- pallets/subtensor/src/utils/misc.rs | 78 +++++------ pallets/subtensor/src/utils/rate_limiting.rs | 17 ++- pallets/subtensor/tests/children.rs | 132 +++++++++--------- pallets/subtensor/tests/mock.rs | 3 +- runtime/src/lib.rs | 2 +- 8 files changed, 143 insertions(+), 127 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 93fa6a6f2..edc9040ac 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1137,7 +1137,11 @@ pub mod pallet { pub type LastTxBlock = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] - /// --- MAP ( key ) --> last_block + /// --- MAP ( key ) --> last_tx_block_childkey_take + pub type LastTxBlockChildKeyTake = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; + #[pallet::storage] + /// --- MAP ( key ) --> last_tx_block_delegate_take pub type LastTxBlockDelegateTake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultLastTxBlock>; #[pallet::storage] diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f62efd94c..a776cfc4f 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -737,7 +737,7 @@ mod dispatches { /// * `TxChildkeyTakeRateLimitExceeded`: /// - The rate limit for changing childkey take has been exceeded. /// - #[pallet::call_index(68)] + #[pallet::call_index(75)] #[pallet::weight(( Weight::from_parts(34_000, 0) .saturating_add(T::DbWeight::get().reads(4)) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index fda0ae27b..402e9fd2a 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -236,26 +236,28 @@ impl Pallet { Error::::InvalidChildkeyTake ); - // Ensure the hotkey passes the rate limit - ensure!( - Self::passes_rate_limit_on_subnet(&TransactionType::SetChildkeyTake, &hotkey, netuid), - Error::::TxChildkeyTakeRateLimitExceeded + // Check if the rate limit has been exceeded + let current_block = Self::get_current_block_as_u64(); + let last_tx_block = + Self::get_last_transaction_block(&hotkey, netuid, &TransactionType::SetChildkeyTake); + let rate_limit = TxChildkeyTakeRateLimit::::get(); + let passes = + Self::passes_rate_limit_on_subnet(&TransactionType::SetChildkeyTake, &hotkey, netuid); + + log::info!( + "Rate limit check: current_block: {}, last_tx_block: {}, rate_limit: {}, passes: {}", + current_block, + last_tx_block, + rate_limit, + passes ); + ensure!(passes, Error::::TxChildkeyTakeRateLimitExceeded); + // Set the new childkey take value for the given hotkey and network ChildkeyTake::::insert(hotkey.clone(), netuid, take); - // TODO: Consider adding a check to ensure the hotkey is registered on the specified network (netuid) - // before setting the childkey take. This could prevent setting takes for non-existent or - // unregistered hotkeys. - - // NOTE: The childkey take is now associated with both the hotkey and the network ID. - // This allows for different take values across different networks for the same hotkey. - // Update the last transaction block - let current_block: u64 = >::block_number() - .try_into() - .unwrap_or(0); Self::set_last_transaction_block( &hotkey, netuid, diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 9155357c0..c75ce1a8d 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -8,33 +8,6 @@ use sp_core::U256; use sp_runtime::Saturating; use substrate_fixed::types::I32F32; -/// Enum representing different types of transactions -#[derive(Copy, Clone)] -pub enum TransactionType { - SetChildren, - Unknown, -} - -/// Implement conversion from TransactionType to u16 -impl From for u16 { - fn from(tx_type: TransactionType) -> Self { - match tx_type { - TransactionType::SetChildren => 0, - TransactionType::Unknown => 1, - } - } -} - -/// Implement conversion from u16 to TransactionType -impl From for TransactionType { - fn from(value: u16) -> Self { - match value { - 0 => TransactionType::SetChildren, - _ => TransactionType::Unknown, - } - } -} - impl Pallet { pub fn ensure_subnet_owner_or_root( o: T::RuntimeOrigin, @@ -312,17 +285,7 @@ impl Pallet { pub fn coinbase(amount: u64) { TotalIssuance::::put(TotalIssuance::::get().saturating_add(amount)); } - pub fn get_default_take() -> u16 { - // Default to maximum - MaxTake::::get() - } - pub fn set_max_take(default_take: u16) { - MaxTake::::put(default_take); - Self::deposit_event(Event::DefaultTakeSet(default_take)); - } - pub fn get_min_take() -> u16 { - MinTake::::get() - } + pub fn set_subnet_locked_balance(netuid: u16, amount: u64) { SubnetLocked::::insert(netuid, amount); } @@ -357,18 +320,49 @@ impl Pallet { Self::deposit_event(Event::TxDelegateTakeRateLimitSet(tx_rate_limit)); } pub fn set_min_delegate_take(take: u16) { - MinTake::::put(take); + MinDelegateTake::::put(take); Self::deposit_event(Event::MinDelegateTakeSet(take)); } pub fn set_max_delegate_take(take: u16) { - MaxTake::::put(take); + MaxDelegateTake::::put(take); Self::deposit_event(Event::MaxDelegateTakeSet(take)); } pub fn get_min_delegate_take() -> u16 { - MinTake::::get() + MinDelegateTake::::get() } pub fn get_max_delegate_take() -> u16 { - MaxTake::::get() + MaxDelegateTake::::get() + } + pub fn get_default_delegate_take() -> u16 { + // Default to maximum + MaxDelegateTake::::get() + } + // get_default_childkey_take + pub fn get_default_childkey_take() -> u16 { + // Default to maximum + MinChildkeyTake::::get() + } + pub fn get_tx_childkey_take_rate_limit() -> u64 { + TxChildkeyTakeRateLimit::::get() + } + pub fn set_tx_childkey_take_rate_limit(tx_rate_limit: u64) { + TxChildkeyTakeRateLimit::::put(tx_rate_limit); + Self::deposit_event(Event::TxChildKeyTakeRateLimitSet(tx_rate_limit)); + } + pub fn set_min_childkey_take(take: u16) { + MinChildkeyTake::::put(take); + Self::deposit_event(Event::MinChildKeyTakeSet(take)); + } + pub fn set_max_childkey_take(take: u16) { + MaxChildkeyTake::::put(take); + Self::deposit_event(Event::MaxChildKeyTakeSet(take)); + } + pub fn get_min_childkey_take() -> u16 { + MinChildkeyTake::::get() + } + + pub fn get_max_childkey_take() -> u16 { + MaxChildkeyTake::::get() } pub fn get_serving_rate_limit(netuid: u16) -> u64 { diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index ffd17ca93..b02ad9855 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -5,6 +5,7 @@ use sp_core::Get; #[derive(Copy, Clone)] pub enum TransactionType { SetChildren, + SetChildkeyTake, Unknown, } @@ -13,7 +14,8 @@ impl From for u16 { fn from(tx_type: TransactionType) -> Self { match tx_type { TransactionType::SetChildren => 0, - TransactionType::Unknown => 1, + TransactionType::SetChildkeyTake => 1, + TransactionType::Unknown => 2, } } } @@ -23,6 +25,7 @@ impl From for TransactionType { fn from(value: u16) -> Self { match value { 0 => TransactionType::SetChildren, + 1 => TransactionType::SetChildkeyTake, _ => TransactionType::Unknown, } } @@ -35,6 +38,7 @@ impl Pallet { pub fn get_rate_limit(tx_type: &TransactionType) -> u64 { match tx_type { TransactionType::SetChildren => (DefaultTempo::::get().saturating_mul(2)).into(), // Cannot set children twice within the default tempo period. + TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) } } @@ -48,7 +52,9 @@ impl Pallet { let block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit(tx_type); let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); - block.saturating_sub(last_block) < limit + + // Allow the first transaction (when last_block is 0) or if the rate limit has passed + last_block == 0 || block.saturating_sub(last_block) >= limit } /// Check if a transaction should be rate limited globally @@ -93,6 +99,13 @@ impl Pallet { pub fn get_last_tx_block_delegate_take(key: &T::AccountId) -> u64 { LastTxBlockDelegateTake::::get(key) } + + pub fn set_last_tx_block_childkey_take(key: &T::AccountId, block: u64) { + LastTxBlockChildKeyTake::::insert(key, block) + } + pub fn get_last_tx_block_childkey_take(key: &T::AccountId) -> u64 { + LastTxBlockChildKeyTake::::get(key) + } pub fn exceeds_tx_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_rate_limit(); if rate_limit == 0 || prev_tx_block == 0 { diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index 6a37b5bea..b675ff8e9 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -2,7 +2,7 @@ use crate::mock::*; use frame_support::{assert_err, assert_noop, assert_ok}; mod mock; -use pallet_subtensor::{utils::TransactionType, *}; +use pallet_subtensor::{utils::rate_limiting::TransactionType, *}; use sp_core::U256; // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_success --exact --nocapture @@ -806,12 +806,12 @@ fn test_childkey_take_functionality() { // Test default and max childkey take let default_take = SubtensorModule::get_default_childkey_take(); - let max_take = SubtensorModule::get_max_childkey_take(); - log::info!("Default take: {}, Max take: {}", default_take, max_take); + let min_take = SubtensorModule::get_min_childkey_take(); + log::info!("Default take: {}, Max take: {}", default_take, min_take); // Check if default take and max take are the same assert_eq!( - default_take, max_take, + default_take, min_take, "Default take should be equal to max take" ); @@ -822,7 +822,7 @@ fn test_childkey_take_functionality() { ); // Test setting childkey take - let new_take: u16 = max_take / 2; // 50% of max_take + let new_take: u16 = SubtensorModule::get_max_childkey_take() / 2; // 50% of max_take assert_ok!(SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), hotkey, @@ -836,7 +836,7 @@ fn test_childkey_take_functionality() { assert_eq!(stored_take, new_take); // Test setting childkey take outside of allowed range - let invalid_take: u16 = max_take + 1; + let invalid_take: u16 = SubtensorModule::get_max_childkey_take() + 1; assert_noop!( SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), @@ -878,91 +878,75 @@ fn test_childkey_take_rate_limiting() { SubtensorModule::set_tx_childkey_take_rate_limit(rate_limit); log::info!( - "TxChildkeyTakeRateLimit: {:?}", + "Set TxChildkeyTakeRateLimit: {:?}", TxChildkeyTakeRateLimit::::get() ); + // Helper function to log rate limit information + let log_rate_limit_info = || { + let current_block = SubtensorModule::get_current_block_as_u64(); + let last_block = SubtensorModule::get_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + ); + let passes = SubtensorModule::passes_rate_limit_on_subnet( + &TransactionType::SetChildkeyTake, + &hotkey, + netuid, + ); + let limit = SubtensorModule::get_rate_limit(&TransactionType::SetChildkeyTake); + log::info!( + "Rate limit info: current_block: {}, last_block: {}, limit: {}, passes: {}, diff: {}", + current_block, + last_block, + limit, + passes, + current_block.saturating_sub(last_block) + ); + }; + // First transaction (should succeed) + log_rate_limit_info(); assert_ok!(SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), hotkey, netuid, 500 )); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let last_block = SubtensorModule::get_last_transaction_block( - &hotkey, - netuid, - &TransactionType::SetChildkeyTake, - ); - log::info!( - "After first transaction: current_block: {}, last_block: {}", - current_block, - last_block - ); + log_rate_limit_info(); // Second transaction (should fail due to rate limit) - let result = - SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 600); - log::info!("Second transaction result: {:?}", result); - let current_block = SubtensorModule::get_current_block_as_u64(); - let last_block = SubtensorModule::get_last_transaction_block( - &hotkey, - netuid, - &TransactionType::SetChildkeyTake, - ); - log::info!( - "After second transaction attempt: current_block: {}, last_block: {}", - current_block, - last_block + log_rate_limit_info(); + assert_noop!( + SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 600), + Error::::TxChildkeyTakeRateLimitExceeded ); - - assert_noop!(result, Error::::TxChildkeyTakeRateLimitExceeded); + log_rate_limit_info(); // Advance the block number to just before the rate limit - run_to_block(rate_limit); + run_to_block(rate_limit - 1); // Third transaction (should still fail) - let result = - SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 650); - log::info!("Third transaction result: {:?}", result); - let current_block = SubtensorModule::get_current_block_as_u64(); - let last_block = SubtensorModule::get_last_transaction_block( - &hotkey, - netuid, - &TransactionType::SetChildkeyTake, - ); - log::info!( - "After third transaction attempt: current_block: {}, last_block: {}", - current_block, - last_block + log_rate_limit_info(); + assert_noop!( + SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, netuid, 650), + Error::::TxChildkeyTakeRateLimitExceeded ); - - assert_noop!(result, Error::::TxChildkeyTakeRateLimitExceeded); + log_rate_limit_info(); // Advance the block number to just after the rate limit - run_to_block(rate_limit * 2); + run_to_block(rate_limit + 1); // Fourth transaction (should succeed) + log_rate_limit_info(); assert_ok!(SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), hotkey, netuid, 700 )); - - let current_block = SubtensorModule::get_current_block_as_u64(); - let last_block = SubtensorModule::get_last_transaction_block( - &hotkey, - netuid, - &TransactionType::SetChildkeyTake, - ); - log::info!( - "After fourth transaction: current_block: {}, last_block: {}", - current_block, - last_block - ); + log_rate_limit_info(); // Verify the final take was set let stored_take = SubtensorModule::get_childkey_take(&hotkey, netuid); @@ -987,7 +971,7 @@ fn test_multiple_networks_childkey_take() { register_ok_neuron(netuid, hotkey, coldkey, 0); // Set a unique childkey take value for each network - let take_value = (netuid + 1) * 1000; // Values will be 1000, 2000, ..., 10000 + let take_value = (netuid + 1) * 100; // Values will be 200, 300, ..., 1000 assert_ok!(SubtensorModule::set_childkey_take( RuntimeOrigin::signed(coldkey), hotkey, @@ -1019,6 +1003,26 @@ fn test_multiple_networks_childkey_take() { ); } } + + // Attempt to set childkey take again (should fail due to rate limit) + let result = + SubtensorModule::set_childkey_take(RuntimeOrigin::signed(coldkey), hotkey, 1, 1100); + assert_noop!(result, Error::::TxChildkeyTakeRateLimitExceeded); + + // Advance blocks to bypass rate limit + run_to_block(SubtensorModule::get_tx_childkey_take_rate_limit() + 1); + + // Now setting childkey take should succeed + assert_ok!(SubtensorModule::set_childkey_take( + RuntimeOrigin::signed(coldkey), + hotkey, + 1, + 1100 + )); + + // Verify the new take value + let new_take = SubtensorModule::get_childkey_take(&hotkey, 1); + assert_eq!(new_take, 1100, "Childkey take not updated after rate limit"); }); } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 4de74f4ab..4039054e0 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -134,8 +134,7 @@ parameter_types! { pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production pub const InitialMinDelegateTake: u16 = 5_898; // 9%; pub const InitialDefaultChildKeyTake: u16 = 11_796; // 18%, same as in production - pub const InitialMinChildKeyTake: u16 = 5_898; // 9%; - pub const InitialMinTake: u16 =5_898; // 9%; + pub const InitialMinChildKeyTake: u16 = 0; // 0 %; pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4dc0e5d72..dec65f60f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -873,7 +873,7 @@ parameter_types! { pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take - pub const SubtensorInitialMinChildKeyTake: u16 = 5_898; // 9% + pub const SubtensorInitialMinChildKeyTake: u16 = 0; // 0 % pub const SubtensorInitialWeightsVersionKey: u64 = 0; pub const SubtensorInitialMinDifficulty: u64 = 10_000_000; pub const SubtensorInitialMaxDifficulty: u64 = u64::MAX / 4; From 2625b5da9052d03bc1fb403d180ba99b4c17f9b6 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 5 Aug 2024 08:58:23 -0400 Subject: [PATCH 138/269] remove unneeded label checks now that we have a simpler process --- .github/workflows/check-rust.yml | 4 ---- .github/workflows/devnet-labels.yml | 19 ------------------- .github/workflows/devnet-ready-labels.yml | 17 ----------------- .github/workflows/testnet-labels.yml | 19 ------------------- .github/workflows/testnet-ready-labels.yml | 17 ----------------- 5 files changed, 76 deletions(-) delete mode 100644 .github/workflows/devnet-labels.yml delete mode 100644 .github/workflows/devnet-ready-labels.yml delete mode 100644 .github/workflows/testnet-labels.yml delete mode 100644 .github/workflows/testnet-ready-labels.yml diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index e95308861..d36718ef9 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -338,7 +338,3 @@ jobs: - name: Check features run: zepter run check - - - - diff --git a/.github/workflows/devnet-labels.yml b/.github/workflows/devnet-labels.yml deleted file mode 100644 index 85d9e7eed..000000000 --- a/.github/workflows/devnet-labels.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Tested on Devnet -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - branches: [main] -jobs: - check-labels: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: mheap/github-action-required-labels@v5 - with: - mode: minimum - count: 1 - labels: | - devnet-pass - devnet-skip diff --git a/.github/workflows/devnet-ready-labels.yml b/.github/workflows/devnet-ready-labels.yml deleted file mode 100644 index ab53327e7..000000000 --- a/.github/workflows/devnet-ready-labels.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: devnet-companion Label Check -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - branches: [devnet-ready] -jobs: - check-labels: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: mheap/github-action-required-labels@v5 - with: - mode: minimum - count: 1 - labels: devnet-companion diff --git a/.github/workflows/testnet-labels.yml b/.github/workflows/testnet-labels.yml deleted file mode 100644 index b4aabd958..000000000 --- a/.github/workflows/testnet-labels.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Tested on Testnet -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - branches: [main] -jobs: - check-labels: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: mheap/github-action-required-labels@v5 - with: - mode: minimum - count: 1 - labels: | - testnet-pass - testnet-skip diff --git a/.github/workflows/testnet-ready-labels.yml b/.github/workflows/testnet-ready-labels.yml deleted file mode 100644 index 8570d2011..000000000 --- a/.github/workflows/testnet-ready-labels.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: testnet-companion Label Check -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] - branches: [testnet-ready] -jobs: - check-labels: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: mheap/github-action-required-labels@v5 - with: - mode: minimum - count: 1 - labels: testnet-companion From 76541913340687e740d86b171a6371cf9d74c902 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 5 Aug 2024 09:15:22 -0400 Subject: [PATCH 139/269] update CONTRIBUTING.md --- CONTRIBUTING.md | 131 +++++++++--------------------------------------- 1 file changed, 25 insertions(+), 106 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 132d360b8..8ac806367 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ ## Lifecycle of a Pull Request 1. Individuals wishing to contribute to subtensor should develop their change/feature/fix in a - [Pull Request](https://github.com/opentensor/subtensor/compare) (PR) targeting the `main` + [Pull Request](https://github.com/opentensor/subtensor/compare) (PR) targeting the `devnet-ready` branch of the subtensor GitHub repository. It is recommended to start your pull request as a draft initially until you are ready to have other developers actively look at it. Any changes to pallet/runtime code should be accompanied by integration and/or unit tests fully @@ -13,69 +13,29 @@ Review" and request review from "Nucleus". 3. Core Nucleus team members will review your PR, possibly requesting changes, and will also add appropriate labels to your PR as shown below. Three positive reviews are required. -4. Once the required passing reviews have been obtained, you are ready to request that your PR - be included in the next `devnet` deploy. To do this, you should open a companion PR merging - a copy of your branch into the `devnet-ready` branch. You must include a link to the parent - PR in the description and preface your PR title with "(Devnet Ready)" or the PR will be - closed/ignored. Your companion PR should have the `devnet-companion` label. -5. A core team administrator will review your "(Devnet Ready)" PR, verifying that it logically - matches the changes introduced in the parent PR (there will sometimes be minor differences - due to merge conflicts) and will either request changes or approve the PR and merge it. Once - your companion PR is merged, the administrator will add the `devnet-ready` label to the - parent PR, indicating that the PR is on the `devnet-ready` branch and will be included in - the next deploy to `devnet`. -6. At some point, a core team administrator will open a PR merging the current `devnet-ready` +4. Once the required passing reviews have been obtained, you or an administrator may merge the + PR into the `devnet-ready` branch. +5. At some point, a core team administrator will open a PR merging the current `devnet-ready` branch into `devnet`, and the CI will enforce some additional safety checks on this PR including a requirement that the new `spec_version` be greater than the current on-chain `spec_version`. The PR should include a bulleted list of all PRs included in the deploy so - they can be easily found after the fact (TODO: automate this). This PR will require two - reviews from the core team as a sanity check. After merging, the administrator will then - need to update all PRs with the `devnet-ready` label to instead have the `on-devnet` label - (TODO: automate this upon merge). The administrator will then deploy `devnet`. -7. Once the `on-devnet` label appears on your PR, if you are a core team member it is your - responsibility to verify that the features/changes/fixes introduced by your PR are - functioning properly on `devnet` by interacting with the live network. If you are an - external contributor, a core team member will be assigned to test this for you. -8. If your feature/change/fix is confirmed working on `devnet`, the `devnet-pass` label should - be added. Otherwise if there are issues, the `devnet-fail` label should be added and you - will need to make changes to your PR and repeat the previous steps in this process. In some - cases a revert PR will need to be created reverting your changes from the `pre-devnet` and - `devnet` branches, respectively. -9. Once `devnet-pass` has been added to your PR, it is eligible for inclusion in the next - `testnet` deploy. We typically run `testnet` deploys every other wednesday. -10. On the appropriate date, an administrator will open a PR merging the current `devnet` + they can be easily found after the fact. +6. Once your feature/change/fix is on `devnet`, it is your responsibility to confirm it is + working properly. If it is not working and additional changes are needed, please coordinate + with a core team administrator and/or open up a new PR into `devnet` either reverting your + changes or making any required changes in order for the feature to function properly. +7. On the appropriate date, an administrator will open a PR merging the current `devnet` branch into `testnet`. This PR should include a bulleted list of all PRs included in the - deploy so they can be easily found after the fact (TODO: automate this). The PR should - exclude any PRs that currently have the `devnet-fail` label via a revert (TODO: enforce via - CI). This PR will require two reviews from the core team as a sanity check. After merging - into `testnet`, the administrator will then need to run the deploy and update all PRs - included in the deploy with the `on-testnet` label (TODO: automate this upon merge). Next - the administrator must cut a (pre-release) release in GitHub for `testnet` (TODO: github - action to generate the release and release notes). -11. Once the `on-testnet` label appears on your PR, if you are a core team member it is your - responsibility to once again verify that the features/changes/fixes introduced by your PR - are functioning properly on `testnet` by interacting with the live network, if applicable. - If you are an external contributor, a core team member may be assigned to do this testing - for you but otherwise it will be your responsibility to show evidence on the PR that the - testing is successful. Once this has been verified, the `testnet-pass` label should be - added. If testing fails, the `testnet-fail` label should be added and PRs should be opened - reverting the change from `devnet-ready`, and then a PR should be opened merging the - modified `devnet` into `testnet`. These revert PRs, if they occur, _must_ be merged before - a new deploy can be run (TODO: enforce this via CI). -12. After the SOP period (1 week on `testnet`) has passed and the `testnet-pass` label has been - added, the CI checks on your PR should now turn all green and a core team member will be - able to merge your PR into `main`. At this point your PR is done and is eligible to be - included in the next `finney` deploy (TODO: track and enforce SOP compliance on a per-PR - basis in CI based on the timestamps of label changes). We typically run `finney` deploys - every other Wednesday, so this will typically happen the Wednesday following the Wednesday - your PR was deployed to `testnet`. An administrator will run this deploy. The process the - administrator follows is to open a PR merging `main` into the `finney` branch, which will - always track the current state of `finney`. This PR automatically has some additional - checks on it such as asserting that the spec_version gets bumped properly and other sanity - checks designed to stop a bad deploy. Once the PR is reviewed and merged, the administrator - will run the actual deploy. Once that is successful, the administrator will cut a new - GitHub release tagged off of the latest `main` branch commit that was included in the - deploy, and announcements will be made regarding the release. + deploy so they can be easily found after the fact (TODO: automate this). This PR is merged, + the administrator will deploy `testnet` and cut a (pre-release) release in GitHub for + `testnet` (TODO: github action to generate the release and release notes). +11. It is now your responsibility to once again check that your feature/change/fix is working + properly, this time on `testnet`. Once again if it is not working or additional changes are + needed, please coordinate with a core team administrator ASAP and/or open up a new PR into + `testnet` either reverting your changes or making any required changes in order for the + feature to function properly. +12. At some point the administrator will merge current `testnet` into `main` and cut a new + deploy to mainnet/finney. ## PR Labels @@ -85,40 +45,24 @@ | `blue-team` | PR is focused on preventative/safety measures and/or dev UX improvements | none | | `runtime` | PR contains substantive changes to runtime / pallet code | none | | `breaking-change` | PR requires synchronized changes with bittensor | Triggers an automatic bot message so the relevant teams are made aware of the change well in advance | -| `migration` | PR contains one or more migrations | none | -| `devnet-companion` | Designates a devnet companion PR | Presence of `devnet-companion` label is checked | -| `devnet-ready` | PR's branch has been merged into the `devnet-ready` branch and will be included in the next `devnet` deploy | none | -| `on-devnet` | PR has been deployed to `devnet` | Removes `devnet-ready` | -| `devnet-pass` | PR has passed manual testing on `devnet` | `devnet-pass` or `devnet-skip` required | -| `devnet-skip` | Allows a critical hotfix PR to skip required testing on `devnet` | `devnet-pass` or `devnet-skip` required | -| `devnet-fail` | PR has failed manual testing on `devnet` and requires modification | none | -| `testnet-companion` | Designates a testnet companion PR | Presence of `testnet-companion` label is checked | -| `on-testnet` | PR has been deployed to `testnet` | none | -| `testnet-pass` | PR has passed manual testing on `testnet` | `testnet-pass` or `testnet-skip` required | -| `testnet-skip` | Allows a critical hotfix PR to skip required manual testing and SOP on `testnet` | `testnet-pass` or `testnet-skip` required | -| `testnet-fail` | PR has failed manual testing on `testnet` and requires modification | none | - ## Branches ### `devnet-ready` -Companion PRs merge into this branch, eventually accumulating into a merge of `devnet-ready` -into `devnet`, coinciding with a deploy of `devnet`. +All new feature/change/fix PRs should merge into this branch. #### Restrictions * no deleting the branch * no force pushes * no direct pushes -* require 1 positive review from an administrator -* new code changes invalidate existing reviews +* require 3 positive review from an administrator +* new code changes do _not_ invalidate existing reviews * only merge commit style merging allowed #### CI-Enforced Restrictions * `check-rust.yml` must pass -* TODO: parent PR must be linked to in description -* TODO: parent PR must have the required number of positive reviews ### `devnet` @@ -164,33 +108,8 @@ tags for `testnet` releases. ### `main` -Default branch for all new PRs. Slightly ahead of what is currently on `finney`. When a PR is all -green and "done", meaning it has been tested on `devnet` and `testnet`, it can be merged into -`main`. Contains tags for `finney` releases. - -#### Restrictions -* no deleting the branch -* no force pushes -* no direct pushes -* require 3 positive reviews from core team members -* new code changes invalidate existing reviews -* all conversations must be resolved -* only merge commit style merging allowed - -#### CI-Enforced Restrictions -* `check-rust.yml` must pass -* `check-labels.yml` must pass -* must have `devnet-skip` or `devnet-pass` label -* must have `testnet-skip` or `testnet-pass` label -* if `breaking-change` label is present, bot will message the appropriate teams -* TODO: when we get auditing, presence of `needs-audit` label = require a review from auditor -* TODO: track SOP on PR based on label age - - -### `finney` - Tracks the current state of what is deployed to `finney` (mainnet). Updated via an -administrator-submitted PR merging `main` into `finney` in concert with a `finney` deploy. +administrator-submitted PR merging `testnet` into `main` in concert with a `finney` deploy. #### Restrictions * no deleting the branch @@ -203,5 +122,5 @@ administrator-submitted PR merging `main` into `finney` in concert with a `finne #### CI-Enforced Restrictions * `check-rust.yml` must pass * `check-finney.yml` must pass -* spec_version must be greater than what is currently on live `finney` +* `spec_version` must be greater than what is currently on live `finney` * TODO: other pre-deploy sanity checks here From 483e8e7c490d8390df068b36819b3e3f7c32fa01 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 5 Aug 2024 15:17:09 +0200 Subject: [PATCH 140/269] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 66951b7fc..1863dd9db 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -139,7 +139,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: 191, + spec_version: 192, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From d1c40dd1b0d181d301caf9a314c51796d2a4ad2e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 5 Aug 2024 09:27:29 -0400 Subject: [PATCH 141/269] run checks more often --- .github/workflows/check-devnet.yml | 6 +++--- .github/workflows/check-testnet.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index fcc9809d3..ac45c440c 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -2,7 +2,7 @@ name: Devnet Deploy Check on: pull_request: - branches: [devnet] + branches: [devnet, devnet-ready] env: CARGO_TERM_COLOR: always @@ -37,7 +37,7 @@ jobs: echo "network spec_version: $spec_version" if (( $(echo "$local_spec_version <= $spec_version" | bc -l) )); then echo "$local_spec_version ≯ $spec_version ❌"; exit 1; fi echo "$local_spec_version > $spec_version ✅" - + check-devnet-migrations: name: check devnet migrations runs-on: ubuntu-22.04 @@ -51,4 +51,4 @@ jobs: runtime-package: "node-subtensor-runtime" node-uri: "wss://dev.chain.opentensor.ai:443" checks: "pre-and-post" - extra-args: "--disable-spec-version-check --no-weight-warnings" \ No newline at end of file + extra-args: "--disable-spec-version-check --no-weight-warnings" diff --git a/.github/workflows/check-testnet.yml b/.github/workflows/check-testnet.yml index 71c46557c..7b30c631c 100644 --- a/.github/workflows/check-testnet.yml +++ b/.github/workflows/check-testnet.yml @@ -2,7 +2,7 @@ name: Testnet Deploy Check on: pull_request: - branches: [testnet] + branches: [testnet, testnet-ready] env: CARGO_TERM_COLOR: always From 67de45c584cc6bcd077329613ff882b03b2293b2 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 6 Aug 2024 15:33:55 +0800 Subject: [PATCH 142/269] add schedule dissolve network --- pallets/admin-utils/tests/mock.rs | 5 + pallets/subtensor/src/benchmarks.rs | 19 ++++ pallets/subtensor/src/lib.rs | 69 +++++++++++-- pallets/subtensor/src/macros/config.rs | 7 +- pallets/subtensor/src/macros/dispatches.rs | 62 +++++++++++- pallets/subtensor/src/macros/events.rs | 9 ++ pallets/subtensor/tests/mock.rs | 5 +- pallets/subtensor/tests/networks.rs | 107 ++++++++++++++++++--- pallets/subtensor/tests/swap_coldkey.rs | 5 +- 9 files changed, 259 insertions(+), 29 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 3f3ef843d..077f07804 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -118,6 +118,9 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialHotkeyEmissionTempo: u64 = 1; pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + } impl pallet_subtensor::Config for Test { @@ -177,6 +180,8 @@ impl pallet_subtensor::Config for Test { type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; type InitialNetworkMaxStake = InitialNetworkMaxStake; type Preimages = (); + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 03e087a92..eaa4821ad 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -429,4 +429,23 @@ reveal_weights { }: reveal_weights(RawOrigin::Signed(hotkey.clone()), netuid, uids, weight_values, salt, version_key) + schedule_swap_coldkey { + let old_coldkey: T::AccountId = account("old_cold", 0, 1); + let new_coldkey: T::AccountId = account("new_cold", 1, 2); + let _ = Subtensor::::schedule_swap_coldkey( + ::RuntimeOrigin::from(RawOrigin::Signed(old_coldkey.clone())), + new_coldkey.clone(), + ); + + }: schedule_swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()) + + schedule_dissolve_network { + let coldkey: T::AccountId = account("old_cold", 0, 1); + let netuid = 1; + let _ = Subtensor::::schedule_dissolve_network( + ::RuntimeOrigin::from(RawOrigin::Signed(coldkey.clone())), + netuid, + ); + + }: schedule_dissolve_network(RawOrigin::Signed(coldkey.clone()), netuid) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a1002bf66..d77a87b37 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -588,6 +588,26 @@ pub mod pallet { T::InitialNetworkMaxStake::get() } + #[pallet::type_value] + /// Default value for coldkey swap schedule duration + pub fn DefaultColdkeySwapScheduleDuration() -> BlockNumberFor { + T::InitialColdkeySwapScheduleDuration::get() + } + + #[pallet::storage] + pub type ColdkeySwapScheduleDuration = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; + + #[pallet::type_value] + /// Default value for coldkey swap schedule duration + pub fn DefaultDissolveNetworkScheduleDuration() -> BlockNumberFor { + T::InitialDissolveNetworkScheduleDuration::get() + } + + #[pallet::storage] + pub type DissolveNetworkScheduleDuration = + StorageValue<_, BlockNumberFor, ValueQuery, DefaultDissolveNetworkScheduleDuration>; + #[pallet::storage] pub type SenateRequiredStakePercentage = StorageValue<_, u64, ValueQuery, DefaultSenateRequiredStakePercentage>; @@ -1219,6 +1239,19 @@ pub enum CallType { Other, } +#[derive(Debug, PartialEq)] +pub enum CustomTransactionError { + ColdkeyInSwapSchedule, +} + +impl From for u8 { + fn from(variant: CustomTransactionError) -> u8 { + match variant { + CustomTransactionError::ColdkeyInSwapSchedule => 0, + } + } +} + #[freeze_struct("61e2b893d5ce6701")] #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] pub struct SubtensorSignedExtension(pub PhantomData); @@ -1367,14 +1400,34 @@ where priority: Self::get_priority_vanilla(), ..Default::default() }), - Some(Call::dissolve_network { .. }) => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), - _ => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), + Some(Call::dissolve_network { .. }) => { + InvalidTransaction::Custom(CustomTransactionError::ColdkeyInSwapSchedule.into()) + .into() + } + _ => { + if let Some(balances_call) = call.is_sub_type() { + match balances_call { + BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. } + | BalancesCall::transfer_allow_death { .. } => { + if ColdkeySwapScheduled::::contains_key(who) { + return InvalidTransaction::Custom( + CustomTransactionError::ColdkeyInSwapSchedule.into(), + ) + .into(); + } + } + // Add other Balances call validations if needed + _ => {} + } + } + + // Default validation for other calls + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } } } diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 2f924905d..c5c5ac737 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -5,7 +5,6 @@ use frame_support::pallet_macros::pallet_section; /// This can later be imported into the pallet using [`import_section`]. #[pallet_section] mod config { - /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -193,5 +192,11 @@ mod config { /// Initial hotkey emission tempo. #[pallet::constant] type InitialHotkeyEmissionTempo: Get; + /// Coldkey swap schedule duartion. + #[pallet::constant] + type InitialColdkeySwapScheduleDuration: 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 48874fb1b..68d2d70ee 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -957,11 +957,9 @@ mod dispatches { Error::::SwapAlreadyScheduled ); - // Calculate the number of blocks in 5 days - let blocks_in_5_days: u32 = 5 * 24 * 60 * 60 / 12; - - let current_block = >::block_number(); - let when = current_block.saturating_add(BlockNumberFor::::from(blocks_in_5_days)); + 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(), @@ -991,6 +989,60 @@ mod dispatches { Ok(().into()) } + /// Schedule the dissolution of a network at a specified block number. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, must be signed by the sender. + /// * `netuid` - The u16 network identifier to be dissolved. + /// + /// # Returns + /// + /// 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(74)] + #[pallet::weight((Weight::from_parts(119_000_000, 0) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] + pub fn schedule_dissolve_network( + origin: OriginFor, + netuid: u16, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let current_block: BlockNumberFor = >::block_number(); + let duration: BlockNumberFor = DissolveNetworkScheduleDuration::::get(); + let when: BlockNumberFor = current_block.saturating_add(duration); + + let call = Call::::dissolve_network { netuid }; + + let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) + .map_err(|_| Error::::FailedToSchedule)?; + + T::Scheduler::schedule( + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Signed(who.clone()).into(), + bound_call, + ) + .map_err(|_| Error::::FailedToSchedule)?; + + ColdkeySwapScheduled::::insert(&who, ()); + // Emit the SwapScheduled event + Self::deposit_event(Event::DissolveNetworkScheduled { + account: who.clone(), + netuid: netuid, + execution_block: when, + }); + + Ok(().into()) + } + /// ---- Set prometheus information for the neuron. /// # Args: /// * 'origin': (Origin): diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 9d771e3e2..2fbffdb7c 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -179,5 +179,14 @@ mod events { NetworkMaxStakeSet(u16, u64), /// The identity of a coldkey has been set ChainIdentitySet(T::AccountId), + /// A dissolve network extrinsic scheduled. + DissolveNetworkScheduled { + /// The account ID schedule the dissolve network extrisnic + account: T::AccountId, + /// network ID will be dissolved + netuid: u16, + /// extrinsic execution block number + execution_block: BlockNumberFor, + }, } } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 3497b6d67..17ccfc87d 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -171,7 +171,8 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const InitialHotkeyEmissionTempo: u64 = 0; // Defaults to draining every block. pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500,000 TAO - + pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days } // Configure collective pallet for council @@ -390,6 +391,8 @@ impl pallet_subtensor::Config for Test { type InitialHotkeyEmissionTempo = InitialHotkeyEmissionTempo; type InitialNetworkMaxStake = InitialNetworkMaxStake; type Preimages = Preimage; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; } pub struct OriginPrivilegeCmp; diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index 93e563683..1929ff543 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -1,14 +1,99 @@ -// DEPRECATED mod mock; -// use frame_support::{ -// assert_ok, -// dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, -// sp_std::vec, -// }; -// use frame_system::Config; -// use frame_system::{EventRecord, Phase}; -// use mock::*; -// use pallet_subtensor::Error; -// use sp_core::{H256, U256}; +use crate::mock::*; +use frame_support::assert_ok; +use frame_system::Config; +use pallet_subtensor::{DissolveNetworkScheduleDuration, Event}; +use sp_core::U256; + +mod mock; + +#[test] +fn test_registration_ok() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert_ok!(SubtensorModule::user_remove_network( + <::RuntimeOrigin>::signed(coldkey_account_id), + netuid + )); + + assert!(!SubtensorModule::if_subnet_exist(netuid)) + }) +} + +#[test] +fn test_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(coldkey_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: coldkey_account_id, + netuid, + execution_block, + } + .into(), + ); + + run_to_block(execution_block); + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} // #[allow(dead_code)] // fn record(event: RuntimeEvent) -> EventRecord { diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index 48168addc..ec030ebee 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -10,7 +10,7 @@ use frame_support::traits::schedule::DispatchTime; use frame_support::traits::OnInitialize; use mock::*; use pallet_subtensor::*; -use pallet_subtensor::{Call, Error}; +use pallet_subtensor::{Call, ColdkeySwapScheduleDuration, Error}; use sp_core::H256; use sp_core::U256; use sp_runtime::DispatchError; @@ -1387,8 +1387,7 @@ fn test_schedule_swap_coldkey_execution() { // Get the scheduled execution block let current_block = System::block_number(); - let blocks_in_5_days = 5 * 24 * 60 * 60 / 12; - let execution_block = current_block + blocks_in_5_days; + let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); System::assert_last_event( Event::ColdkeySwapScheduled { From 106df9e84abcd2c31df5c9e3d2dc4b78cb03a54f Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 6 Aug 2024 15:52:09 +0800 Subject: [PATCH 143/269] fix clippy --- pallets/subtensor/src/lib.rs | 11 +++++++++-- pallets/subtensor/src/macros/dispatches.rs | 2 +- runtime/src/lib.rs | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d77a87b37..22fa0514e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1401,8 +1401,15 @@ where ..Default::default() }), Some(Call::dissolve_network { .. }) => { - InvalidTransaction::Custom(CustomTransactionError::ColdkeyInSwapSchedule.into()) - .into() + if ColdkeySwapScheduled::::contains_key(who) { + InvalidTransaction::Custom(CustomTransactionError::ColdkeyInSwapSchedule.into()) + .into() + } else { + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } } _ => { if let Some(balances_call) = call.is_sub_type() { diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 68d2d70ee..112ef10b5 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1036,7 +1036,7 @@ mod dispatches { // Emit the SwapScheduled event Self::deposit_event(Event::DissolveNetworkScheduled { account: who.clone(), - netuid: netuid, + netuid, execution_block: when, }); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f042c449a..20d565f9a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -902,6 +902,8 @@ parameter_types! { pub const InitialLiquidAlphaOn: bool = false; // Default value for LiquidAlphaOn pub const SubtensorInitialHotkeyEmissionTempo: u64 = 7200; // Drain every day. pub const SubtensorInitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO + pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days } @@ -962,6 +964,8 @@ impl pallet_subtensor::Config for Runtime { type InitialHotkeyEmissionTempo = SubtensorInitialHotkeyEmissionTempo; type InitialNetworkMaxStake = SubtensorInitialNetworkMaxStake; type Preimages = Preimage; + type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; } use sp_runtime::BoundedVec; From cc036e55b1daa35a5d7b5b6777e3ea77d57e3133 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 6 Aug 2024 16:10:49 +0800 Subject: [PATCH 144/269] fix clippy --- pallets/subtensor/src/lib.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 22fa0514e..f92f1102c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1412,24 +1412,19 @@ where } } _ => { - if let Some(balances_call) = call.is_sub_type() { - match balances_call { - BalancesCall::transfer_keep_alive { .. } - | BalancesCall::transfer_all { .. } - | BalancesCall::transfer_allow_death { .. } => { - if ColdkeySwapScheduled::::contains_key(who) { - return InvalidTransaction::Custom( - CustomTransactionError::ColdkeyInSwapSchedule.into(), - ) - .into(); - } - } - // Add other Balances call validations if needed - _ => {} + if let Some( + BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. } + | BalancesCall::transfer_allow_death { .. }, + ) = call.is_sub_type() + { + if ColdkeySwapScheduled::::contains_key(who) { + return InvalidTransaction::Custom( + CustomTransactionError::ColdkeyInSwapSchedule.into(), + ) + .into(); } } - - // Default validation for other calls Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() From 70f624fad7c5a5eddcc56c6c44c64f45cdc8629e Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 6 Aug 2024 22:17:18 +0800 Subject: [PATCH 145/269] udpate comments --- pallets/subtensor/src/benchmarks.rs | 2 +- pallets/subtensor/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index eaa4821ad..05d006ab0 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -440,7 +440,7 @@ reveal_weights { }: schedule_swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()) schedule_dissolve_network { - let coldkey: T::AccountId = account("old_cold", 0, 1); + let coldkey: T::AccountId = account("coldkey", 0, 1); let netuid = 1; let _ = Subtensor::::schedule_dissolve_network( ::RuntimeOrigin::from(RawOrigin::Signed(coldkey.clone())), diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f92f1102c..09f1446e7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -599,7 +599,7 @@ pub mod pallet { StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; #[pallet::type_value] - /// Default value for coldkey swap schedule duration + /// Default value for dissolve network schedule duration pub fn DefaultDissolveNetworkScheduleDuration() -> BlockNumberFor { T::InitialDissolveNetworkScheduleDuration::get() } From 771b120fc783a737f3b82c97c8dbcf59967f0ba8 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 7 Aug 2024 09:30:38 +0800 Subject: [PATCH 146/269] fix bechmarks --- pallets/subtensor/src/benchmarks.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 05d006ab0..2cb53e62c 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -432,20 +432,10 @@ reveal_weights { schedule_swap_coldkey { let old_coldkey: T::AccountId = account("old_cold", 0, 1); let new_coldkey: T::AccountId = account("new_cold", 1, 2); - let _ = Subtensor::::schedule_swap_coldkey( - ::RuntimeOrigin::from(RawOrigin::Signed(old_coldkey.clone())), - new_coldkey.clone(), - ); - }: schedule_swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()) schedule_dissolve_network { let coldkey: T::AccountId = account("coldkey", 0, 1); let netuid = 1; - let _ = Subtensor::::schedule_dissolve_network( - ::RuntimeOrigin::from(RawOrigin::Signed(coldkey.clone())), - netuid, - ); - - }: schedule_dissolve_network(RawOrigin::Signed(coldkey.clone()), netuid) + }: schedule_dissolve_network(RawOrigin::Signed(coldkey.clone()), netuid) } From eb4746f9ab2ad3af897ad673c3061718ed983476 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:11:18 -0400 Subject: [PATCH 147/269] fix warnings --- build.rs | 10 ++-------- lints/dummy_lint.rs | 2 -- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/build.rs b/build.rs index 96bc52b67..7e653ebb3 100644 --- a/build.rs +++ b/build.rs @@ -1,12 +1,6 @@ -use rayon::prelude::*; use std::env; use std::fs; use std::path::{Path, PathBuf}; -use std::process::exit; -use std::sync::mpsc::channel; -use syn::spanned::Spanned; -use syn::Error; -use syn::File; use syn::Result; use walkdir::WalkDir; @@ -50,8 +44,8 @@ fn main() { let end = error.span().end(); let start_line = start.line; let start_col = start.column; - let end_line = end.line; - let end_col = end.column; + let _end_line = end.line; + let _end_col = end.column; let file_path = file.display(); panic!("{}:{}:{}: {}", file_path, start_line, start_col, error); } diff --git a/lints/dummy_lint.rs b/lints/dummy_lint.rs index 4447b66b5..41338b484 100644 --- a/lints/dummy_lint.rs +++ b/lints/dummy_lint.rs @@ -1,5 +1,3 @@ -use syn::{spanned::Spanned, Error}; - use super::*; pub struct DummyLint; From 252fea5f76c91baba9f924e9c60ac9b8d2ec66e2 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:11:32 -0400 Subject: [PATCH 148/269] cargo +nightly fmt --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index e69de29bb..8b1378917 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1 @@ + From b2e5290bb9e8d5d793abfd1ada488990e90e47f0 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:13:24 -0400 Subject: [PATCH 149/269] cargo fix --workspace --- lints/lint.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lints/lint.rs b/lints/lint.rs index 69047c34c..4a69995d9 100644 --- a/lints/lint.rs +++ b/lints/lint.rs @@ -1,4 +1,3 @@ -use rayon::iter::IntoParallelIterator; use super::*; From 676463328de5d87663332adaaa9cb5cf1997352f Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 7 Aug 2024 18:16:26 +0400 Subject: [PATCH 150/269] feat: add more childkey tests --- pallets/subtensor/src/epoch/run_epoch.rs | 4 +- pallets/subtensor/src/lib.rs | 2 - pallets/subtensor/tests/children.rs | 1715 +++++++++++++++++++++- pallets/subtensor/tests/mock.rs | 18 + 4 files changed, 1682 insertions(+), 57 deletions(-) diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 1cb2c3448..9196bee4b 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -31,12 +31,10 @@ impl Pallet { /// This function does not explicitly panic, but underlying arithmetic operations /// use saturating arithmetic to prevent overflows. /// - /// TODO: check for self loops. - /// TODO: (@distributedstatemachine): check if we should return error , otherwise self loop - /// detection is impossible to test. pub fn get_stake_for_hotkey_on_subnet(hotkey: &T::AccountId, netuid: u16) -> u64 { // Retrieve the initial total stake for the hotkey without any child/parent adjustments. let initial_stake: u64 = Self::get_total_stake_for_hotkey(hotkey); + log::debug!("Initial stake: {:?}", initial_stake); let mut stake_to_children: u64 = 0; let mut stake_from_parents: u64 = 0; diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index edc9040ac..b0fbc9140 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -372,8 +372,6 @@ pub mod pallet { } T::InitialNetworkRateLimit::get() } - // #[pallet::type_value] /// Default value for network max stake. - // pub fn DefaultNetworkMaxStake() -> u64 { T::InitialNetworkMaxStake::get() } #[pallet::type_value] /// Default value for emission values. pub fn DefaultEmissionValues() -> u64 { diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index b675ff8e9..f491e8b64 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -5,6 +5,7 @@ mod mock; use pallet_subtensor::{utils::rate_limiting::TransactionType, *}; use sp_core::U256; +// 1: Successful setting of a single child // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_success --exact --nocapture #[test] fn test_do_set_child_singular_success() { @@ -33,6 +34,7 @@ fn test_do_set_child_singular_success() { }); } +// 2: Attempt to set child in non-existent network // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_network_does_not_exist --exact --nocapture #[test] fn test_do_set_child_singular_network_does_not_exist() { @@ -56,6 +58,7 @@ fn test_do_set_child_singular_network_does_not_exist() { }); } +// 3: Attempt to set invalid child (same as hotkey) // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_invalid_child --exact --nocapture #[test] fn test_do_set_child_singular_invalid_child() { @@ -84,6 +87,7 @@ fn test_do_set_child_singular_invalid_child() { }); } +// 4: Attempt to set child with non-associated coldkey // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_non_associated_coldkey --exact --nocapture #[test] fn test_do_set_child_singular_non_associated_coldkey() { @@ -111,6 +115,7 @@ fn test_do_set_child_singular_non_associated_coldkey() { }); } +// 5: Attempt to set child in root network // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_root_network --exact --nocapture #[test] fn test_do_set_child_singular_root_network() { @@ -137,6 +142,13 @@ fn test_do_set_child_singular_root_network() { }); } +// 6: Cleanup of old children when setting new ones +// This test verifies that when new children are set, the old ones are properly removed. +// It checks: +// - Setting an initial child +// - Replacing it with a new child +// - Ensuring the old child is no longer associated +// - Confirming the new child is correctly assigned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_old_children_cleanup --exact --nocapture #[test] fn test_do_set_child_singular_old_children_cleanup() { @@ -178,7 +190,13 @@ fn test_do_set_child_singular_old_children_cleanup() { }); } -// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_old_children_cleanup --exact --nocapture +// 7: Verify new children assignment +// This test checks if new children are correctly assigned to a parent. +// It verifies: +// - Setting a child for a parent +// - Confirming the child is correctly listed under the parent +// - Ensuring the parent is correctly listed for the child +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_new_children_assignment --exact --nocapture #[test] fn test_do_set_child_singular_new_children_assignment() { new_test_ext(1).execute_with(|| { @@ -210,6 +228,12 @@ fn test_do_set_child_singular_new_children_assignment() { }); } +// 8: Test edge cases for proportion values +// This test verifies that the system correctly handles minimum and maximum proportion values. +// It checks: +// - Setting a child with the minimum possible proportion (0) +// - Setting a child with the maximum possible proportion (u64::MAX) +// - Confirming both assignments are processed correctly // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_proportion_edge_cases --exact --nocapture #[test] fn test_do_set_child_singular_proportion_edge_cases() { @@ -251,6 +275,13 @@ fn test_do_set_child_singular_proportion_edge_cases() { }); } +// 9: Test setting multiple children +// This test verifies that when multiple children are set, only the last one remains. +// It checks: +// - Setting an initial child +// - Setting a second child +// - Confirming only the second child remains associated +// - Verifying the first child is no longer associated // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_child_singular_multiple_children --exact --nocapture #[test] fn test_do_set_child_singular_multiple_children() { @@ -296,6 +327,12 @@ fn test_do_set_child_singular_multiple_children() { }); } +// 10: Test adding a singular child with various error conditions +// This test checks different scenarios when adding a child, including: +// - Attempting to set a child in a non-existent network +// - Trying to set a child with an unassociated coldkey +// - Setting an invalid child +// - Successfully setting a valid child // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_add_singular_child --exact --nocapture #[test] fn test_add_singular_child() { @@ -343,73 +380,67 @@ fn test_add_singular_child() { }) } +// 11: Test getting stake for a hotkey on a subnet +// This test verifies the correct calculation of stake for a parent and child neuron: +// - Sets up a network with a parent and child neuron +// - Stakes tokens to both parent and child from different coldkeys +// - Establishes a parent-child relationship with 100% stake allocation +// - Checks that the parent's stake is correctly transferred to the child +// - Ensures the total stake is preserved in the system // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_stake_for_hotkey_on_subnet --exact --nocapture #[test] fn test_get_stake_for_hotkey_on_subnet() { new_test_ext(1).execute_with(|| { let netuid: u16 = 1; - let hotkey0 = U256::from(1); - let hotkey1 = U256::from(2); - let coldkey0 = U256::from(3); - let coldkey1 = U256::from(4); + let parent = U256::from(1); + let child = U256::from(2); + let coldkey1 = U256::from(3); + let coldkey2 = U256::from(4); add_network(netuid, 0, 0); - - let max_stake: u64 = 3000; - SubtensorModule::set_network_max_stake(netuid, max_stake); - - SubtensorModule::create_account_if_non_existent(&coldkey0, &hotkey0); - SubtensorModule::create_account_if_non_existent(&coldkey1, &hotkey1); - - SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey0, &hotkey0, 1000); - SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey0, &hotkey1, 1000); - SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &hotkey0, 1000); - SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &hotkey1, 1000); - - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey0), 2000); - assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&hotkey1), 2000); - - assert_eq!( - SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid), - 2000 - ); - assert_eq!( - SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid), - 2000 - ); - - // Set child relationship + register_ok_neuron(netuid, parent, coldkey1, 0); + register_ok_neuron(netuid, child, coldkey2, 0); + + // Stake 1000 to parent from coldkey1 + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &parent, 1000); + // Stake 1000 to parent from coldkey2 + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey2, &parent, 1000); + // Stake 1000 to child from coldkey1 + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &child, 1000); + // Stake 1000 to child from coldkey2 + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey2, &child, 1000); + + // Set parent-child relationship with 100% stake allocation assert_ok!(SubtensorModule::do_set_children( - RuntimeOrigin::signed(coldkey0), - hotkey0, + RuntimeOrigin::signed(coldkey1), + parent, netuid, - vec![(u64::MAX, hotkey1)] + vec![(u64::MAX, child)] )); - // Check stakes after setting child - let stake0 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid); - let stake1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid); + let parent_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); - assert_eq!(stake0, 0); - assert_eq!(stake1, max_stake); - - // Change child relationship to 50% - assert_ok!(SubtensorModule::do_set_children( - RuntimeOrigin::signed(coldkey0), - hotkey0, - netuid, - vec![(u64::MAX / 2, hotkey1)] - )); + println!("Parent stake: {}", parent_stake); + println!("Child stake: {}", child_stake); - // Check stakes after changing child relationship - let stake0 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey0, netuid); - let stake1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey1, netuid); + // The parent should have 0 stake as it's all allocated to the child + assert_eq!(parent_stake, 0); + // The child should have its original stake (2000) plus the parent's stake (2000) + assert_eq!(child_stake, 4000); - assert_eq!(stake0, 1001); - assert!(stake1 >= max_stake - 1 && stake1 <= max_stake); + // Ensure total stake is preserved + assert_eq!(parent_stake + child_stake, 4000); }); } +// 12: Test revoking a singular child successfully +// This test checks the process of revoking a child neuron: +// - Sets up a network with a parent and child neuron +// - Establishes a parent-child relationship +// - Revokes the child relationship +// - Verifies that the child is removed from the parent's children list +// - Ensures the parent is removed from the child's parents list // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_success --exact --nocapture #[test] fn test_do_revoke_child_singular_success() { @@ -454,6 +485,10 @@ fn test_do_revoke_child_singular_success() { }); } +// 13: Test revoking a child in a non-existent network +// This test verifies that attempting to revoke a child in a non-existent network results in an error: +// - Attempts to revoke a child in a network that doesn't exist +// - Checks that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_network_does_not_exist --exact --nocapture #[test] fn test_do_revoke_child_singular_network_does_not_exist() { @@ -475,6 +510,11 @@ fn test_do_revoke_child_singular_network_does_not_exist() { }); } +// 14: Test revoking a child with a non-associated coldkey +// This test ensures that attempting to revoke a child using an unassociated coldkey results in an error: +// - Sets up a network with a hotkey registered to a different coldkey +// - Attempts to revoke a child using an unassociated coldkey +// - Verifies that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_non_associated_coldkey --exact --nocapture #[test] fn test_do_revoke_child_singular_non_associated_coldkey() { @@ -500,6 +540,11 @@ fn test_do_revoke_child_singular_non_associated_coldkey() { }); } +// 15: Test revoking a non-associated child +// This test verifies that attempting to revoke a child that is not associated with the parent results in an error: +// - Sets up a network and registers a hotkey +// - Attempts to revoke a child that was never associated with the parent +// - Checks that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_child_singular_child_not_associated --exact --nocapture #[test] fn test_do_revoke_child_singular_child_not_associated() { @@ -524,6 +569,12 @@ fn test_do_revoke_child_singular_child_not_associated() { }); } +// 16: Test setting multiple children successfully +// This test verifies that multiple children can be set for a parent successfully: +// - Sets up a network and registers a hotkey +// - Sets multiple children with different proportions +// - Verifies that the children are correctly assigned to the parent +// - Checks that the parent is correctly assigned to each child // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_success --exact --nocapture #[test] fn test_do_set_children_multiple_success() { @@ -561,6 +612,10 @@ fn test_do_set_children_multiple_success() { }); } +// 17: Test setting multiple children in a non-existent network +// This test ensures that attempting to set multiple children in a non-existent network results in an error: +// - Attempts to set children in a network that doesn't exist +// - Verifies that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_network_does_not_exist --exact --nocapture #[test] fn test_do_set_children_multiple_network_does_not_exist() { @@ -584,6 +639,11 @@ fn test_do_set_children_multiple_network_does_not_exist() { }); } +// 18: Test setting multiple children with an invalid child +// This test verifies that attempting to set multiple children with an invalid child (same as parent) results in an error: +// - Sets up a network and registers a hotkey +// - Attempts to set a child that is the same as the parent hotkey +// - Checks that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_invalid_child --exact --nocapture #[test] fn test_do_set_children_multiple_invalid_child() { @@ -610,6 +670,11 @@ fn test_do_set_children_multiple_invalid_child() { }); } +// 19: Test setting multiple children with a non-associated coldkey +// This test ensures that attempting to set multiple children using an unassociated coldkey results in an error: +// - Sets up a network with a hotkey registered to a different coldkey +// - Attempts to set children using an unassociated coldkey +// - Verifies that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_non_associated_coldkey --exact --nocapture #[test] fn test_do_set_children_multiple_non_associated_coldkey() { @@ -637,6 +702,11 @@ fn test_do_set_children_multiple_non_associated_coldkey() { }); } +// 20: Test setting multiple children in root network +// This test verifies that attempting to set children in the root network results in an error: +// - Sets up the root network +// - Attempts to set children in the root network +// - Checks that the appropriate error is returned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_root_network --exact --nocapture #[test] fn test_do_set_children_multiple_root_network() { @@ -663,6 +733,13 @@ fn test_do_set_children_multiple_root_network() { }); } +// 21: Test cleanup of old children when setting multiple new ones +// This test ensures that when new children are set, the old ones are properly removed: +// - Sets up a network and registers a hotkey +// - Sets an initial child +// - Replaces it with multiple new children +// - Verifies that the old child is no longer associated +// - Confirms the new children are correctly assigned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_old_children_cleanup --exact --nocapture #[test] fn test_do_set_children_multiple_old_children_cleanup() { @@ -708,6 +785,11 @@ fn test_do_set_children_multiple_old_children_cleanup() { }); } +// 22: Test setting multiple children with edge case proportions +// This test verifies the behavior when setting multiple children with minimum and maximum proportions: +// - Sets up a network and registers a hotkey +// - Sets two children with minimum and maximum proportions respectively +// - Verifies that the children are correctly assigned with their respective proportions // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_proportion_edge_cases --exact --nocapture #[test] fn test_do_set_children_multiple_proportion_edge_cases() { @@ -741,6 +823,13 @@ fn test_do_set_children_multiple_proportion_edge_cases() { }); } +// 23: Test overwriting existing children with new ones +// This test ensures that when new children are set, they correctly overwrite the existing ones: +// - Sets up a network and registers a hotkey +// - Sets initial children +// - Overwrites with new children +// - Verifies that the final children assignment is correct +// - Checks that old children are properly removed and new ones are correctly assigned // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_overwrite_existing --exact --nocapture #[test] fn test_do_set_children_multiple_overwrite_existing() { @@ -792,6 +881,14 @@ fn test_do_set_children_multiple_overwrite_existing() { }); } +// 24: Test childkey take functionality +// This test verifies the functionality of setting and getting childkey take: +// - Sets up a network and registers a hotkey +// - Checks default and maximum childkey take values +// - Sets a new childkey take value +// - Verifies the new take value is stored correctly +// - Attempts to set an invalid take value and checks for appropriate error +// - Tries to set take with a non-associated coldkey and verifies the error // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_functionality --exact --nocapture #[test] fn test_childkey_take_functionality() { @@ -861,6 +958,13 @@ fn test_childkey_take_functionality() { }); } +// 25: Test childkey take rate limiting +// This test verifies the rate limiting functionality for setting childkey take: +// - Sets up a network and registers a hotkey +// - Sets a rate limit for childkey take changes +// - Performs multiple attempts to set childkey take +// - Verifies that rate limiting prevents frequent changes +// - Advances blocks to bypass rate limit and confirms successful change // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_childkey_take_rate_limiting --exact --nocapture #[test] fn test_childkey_take_rate_limiting() { @@ -954,6 +1058,13 @@ fn test_childkey_take_rate_limiting() { }); } +// 26: Test childkey take functionality across multiple networks +// This test verifies the childkey take functionality across multiple networks: +// - Creates multiple networks and sets up neurons +// - Sets unique childkey take values for each network +// - Verifies that each network has a different childkey take value +// - Attempts to set childkey take again (should fail due to rate limit) +// - Advances blocks to bypass rate limit and successfully updates take value // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_multiple_networks_childkey_take --exact --nocapture #[test] fn test_multiple_networks_childkey_take() { @@ -1026,6 +1137,12 @@ fn test_multiple_networks_childkey_take() { }); } +// 27: Test setting children with an empty list +// This test verifies the behavior of setting an empty children list: +// - Adds a network and registers a hotkey +// - Sets an empty children list for the hotkey +// - Verifies that the children assignment is empty +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_set_children_multiple_empty_list --exact --nocapture #[test] fn test_do_set_children_multiple_empty_list() { new_test_ext(1).execute_with(|| { @@ -1051,6 +1168,13 @@ fn test_do_set_children_multiple_empty_list() { }); } +// 28: Test revoking multiple children successfully +// This test verifies the successful revocation of multiple children: +// - Adds a network and registers a hotkey +// - Sets multiple children for the hotkey +// - Revokes all children by setting an empty list +// - Verifies that the children list is empty +// - Verifies that the parent-child relationships are removed for both children // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_success --exact --nocapture #[test] fn test_do_revoke_children_multiple_success() { @@ -1096,6 +1220,10 @@ fn test_do_revoke_children_multiple_success() { }); } +// 29: Test revoking children when network does not exist +// This test verifies the behavior when attempting to revoke children on a non-existent network: +// - Attempts to revoke children on a network that doesn't exist +// - Verifies that the operation fails with the correct error // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_network_does_not_exist --exact --nocapture #[test] fn test_do_revoke_children_multiple_network_does_not_exist() { @@ -1118,6 +1246,11 @@ fn test_do_revoke_children_multiple_network_does_not_exist() { }); } +// 30: Test revoking children with non-associated coldkey +// This test verifies the behavior when attempting to revoke children using a non-associated coldkey: +// - Adds a network and registers a hotkey with a different coldkey +// - Attempts to revoke children using an unassociated coldkey +// - Verifies that the operation fails with the correct error // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_non_associated_coldkey --exact --nocapture #[test] fn test_do_revoke_children_multiple_non_associated_coldkey() { @@ -1145,6 +1278,13 @@ fn test_do_revoke_children_multiple_non_associated_coldkey() { }); } +// 31: Test partial revocation of children +// This test verifies the behavior when partially revoking children: +// - Adds a network and registers a hotkey +// - Sets multiple children for the hotkey +// - Revokes one of the children +// - Verifies that the correct children remain and the revoked child is removed +// - Checks the parent-child relationships after partial revocation // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_partial_revocation --exact --nocapture #[test] fn test_do_revoke_children_multiple_partial_revocation() { @@ -1195,8 +1335,14 @@ fn test_do_revoke_children_multiple_partial_revocation() { }); } +// 32: Test revoking non-existent children +// This test verifies the behavior when attempting to revoke non-existent children: +// - Adds a network and registers a hotkey +// - Sets one child for the hotkey +// - Attempts to revoke all children (including non-existent ones) +// - Verifies that all children are removed, including the existing one +// - Checks that the parent-child relationship is properly updated // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_non_existent_children --exact --nocapture - #[test] fn test_do_revoke_children_multiple_non_existent_children() { new_test_ext(1).execute_with(|| { @@ -1236,6 +1382,11 @@ fn test_do_revoke_children_multiple_non_existent_children() { }); } +// 33: Test revoking children with an empty list +// This test verifies the behavior when attempting to revoke children using an empty list: +// - Adds a network and registers a hotkey +// - Attempts to revoke children with an empty list +// - Verifies that no changes occur in the children list // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_empty_list --exact --nocapture #[test] fn test_do_revoke_children_multiple_empty_list() { @@ -1262,6 +1413,13 @@ fn test_do_revoke_children_multiple_empty_list() { }); } +// 34: Test complex scenario for revoking multiple children +// This test verifies a complex scenario involving setting and revoking multiple children: +// - Adds a network and registers a hotkey +// - Sets multiple children with different proportions +// - Revokes one child and verifies the remaining children +// - Revokes all remaining children +// - Verifies that all parent-child relationships are properly updated // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_do_revoke_children_multiple_complex_scenario --exact --nocapture #[test] fn test_do_revoke_children_multiple_complex_scenario() { @@ -1328,6 +1486,11 @@ fn test_do_revoke_children_multiple_complex_scenario() { }); } +// 35: Test getting network max stake +// This test verifies the functionality of getting the network max stake: +// - Checks the default max stake value +// - Sets a new max stake value +// - Verifies that the new value is retrieved correctly // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_network_max_stake --exact --nocapture #[test] fn test_get_network_max_stake() { @@ -1350,6 +1513,12 @@ fn test_get_network_max_stake() { }); } +// 36: Test setting network max stake +// This test verifies the functionality of setting the network max stake: +// - Checks the initial max stake value +// - Sets a new max stake value +// - Verifies that the new value is set correctly +// - Checks that the appropriate event is emitted // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake --exact --nocapture #[test] fn test_set_network_max_stake() { @@ -1376,6 +1545,11 @@ fn test_set_network_max_stake() { }); } +// 37: Test setting network max stake for multiple networks +// This test verifies the functionality of setting different max stake values for multiple networks: +// - Sets different max stake values for two networks +// - Verifies that the values are set correctly for each network +// - Checks that the values are different between networks // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake_multiple_networks --exact --nocapture #[test] fn test_set_network_max_stake_multiple_networks() { @@ -1399,6 +1573,12 @@ fn test_set_network_max_stake_multiple_networks() { }); } +// 38: Test updating network max stake +// This test verifies the functionality of updating an existing network max stake value: +// - Sets an initial max stake value +// - Updates the max stake value +// - Verifies that the value is updated correctly +// - Checks that the appropriate event is emitted for the update // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_set_network_max_stake_update --exact --nocapture #[test] fn test_set_network_max_stake_update() { @@ -1428,6 +1608,13 @@ fn test_set_network_max_stake_update() { }); } +// 39: Test children stake values +// This test verifies the correct distribution of stake among parent and child neurons: +// - Sets up a network with a parent neuron and multiple child neurons +// - Assigns stake to the parent neuron +// - Sets child neurons with specific proportions +// - Verifies that the stake is correctly distributed among parent and child neurons +// - Checks that the total stake remains constant across all neurons // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_children_stake_values --exact --nocapture #[test] fn test_children_stake_values() { @@ -1493,6 +1680,13 @@ fn test_children_stake_values() { }); } +// 40: Test getting parents chain +// This test verifies the correct implementation of parent-child relationships and the get_parents function: +// - Sets up a network with multiple neurons in a chain of parent-child relationships +// - Verifies that each neuron has the correct parent +// - Tests the root neuron has no parents +// - Tests a neuron with multiple parents +// - Verifies correct behavior when adding a new parent to an existing child // SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test children -- test_get_parents_chain --exact --nocapture #[test] fn test_get_parents_chain() { @@ -1626,3 +1820,1420 @@ fn test_get_parents_chain() { ); }); } + +// 41: Test emission distribution between a childkey and a single parent +// This test verifies the correct distribution of emissions between a child and a single parent: +// - Sets up a network with a parent, child, and weight setter +// - Establishes a parent-child relationship +// - Sets weights on the child +// - Runs an epoch with a hardcoded emission value +// - Checks the emission distribution among parent, child, and weight setter +// - Verifies that all parties received emissions and the weight setter received the most +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children test_childkey_single_parent_emission -- --nocapture +#[test] +fn test_childkey_single_parent_emission() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + // Define hotkeys + let parent: U256 = U256::from(1); + let child: U256 = U256::from(2); + let weight_setter: U256 = U256::from(3); + + // Define coldkeys with more readable names + let coldkey_parent: U256 = U256::from(100); + let coldkey_child: U256 = U256::from(101); + let coldkey_weight_setter: U256 = U256::from(102); + + // Register parent with minimal stake and child with high stake + SubtensorModule::add_balance_to_coldkey_account(&coldkey_parent, 1); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_child, 109_999); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_weight_setter, 1_000_000); + + // Add neurons for parent, child and weight_setter + register_ok_neuron(netuid, parent, coldkey_parent, 1); + register_ok_neuron(netuid, child, coldkey_child, 1); + register_ok_neuron(netuid, weight_setter, coldkey_weight_setter, 1); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey_parent, + &parent, + 109_999, + ); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey_weight_setter, + &weight_setter, + 1_000_000, + ); + + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + // Set parent-child relationship + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent), + parent, + netuid, + vec![(u64::MAX, child)] + )); + step_block(7200 + 1); + // Set weights on the child using the weight_setter account + let origin = RuntimeOrigin::signed(weight_setter); + let uids: Vec = vec![1]; // Only set weight for the child (UID 1) + let values: Vec = vec![u16::MAX]; // Use maximum value for u16 + let version_key = SubtensorModule::get_weights_version_key(netuid); + assert_ok!(SubtensorModule::set_weights( + origin, + netuid, + uids, + values, + version_key + )); + + // Run epoch with a hardcoded emission value + let hardcoded_emission: u64 = 1_000_000_000; // 1 TAO + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission); + + // Process the hotkey emission results + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + SubtensorModule::accumulate_hotkey_emission( + &hotkey, + netuid, + validator_emission, + mining_emission, + ); + log::debug!( + "Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", + hotkey, + netuid, + mining_emission, + validator_emission + ); + } + step_block(7200 + 1); + // Check emission distribution + let parent_stake: u64 = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey_parent, &parent); + let parent_stake_on_subnet: u64 = + SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + + log::debug!( + "Parent stake: {:?}, Parent stake on subnet: {:?}", + parent_stake, + parent_stake_on_subnet + ); + + let child_stake: u64 = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey_child, &child); + let child_stake_on_subnet: u64 = + SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); + + log::debug!( + "Child stake: {:?}, Child stake on subnet: {:?}", + child_stake, + child_stake_on_subnet + ); + + let weight_setter_stake: u64 = SubtensorModule::get_stake_for_coldkey_and_hotkey( + &coldkey_weight_setter, + &weight_setter, + ); + let weight_setter_stake_on_subnet: u64 = + SubtensorModule::get_stake_for_hotkey_on_subnet(&weight_setter, netuid); + + log::debug!( + "Weight setter stake: {:?}, Weight setter stake on subnet: {:?}", + weight_setter_stake, + weight_setter_stake_on_subnet + ); + + assert!(parent_stake > 1, "Parent should have received emission"); + assert!(child_stake > 109_999, "Child should have received emission"); + assert!( + weight_setter_stake > 1_000_000, + "Weight setter should have received emission" + ); + + // Additional assertion to verify that the weight setter received the most emission + assert!( + weight_setter_stake > parent_stake && weight_setter_stake > child_stake, + "Weight setter should have received the most emission" + ); + }); +} + +// 43: Test emission distribution between a childkey and multiple parents +// This test verifies the correct distribution of emissions between a child and multiple parents: +// - Sets up a network with two parents, a child, and a weight setter +// - Establishes parent-child relationships with different stake proportions +// - Sets weights on the child and one parent +// - Runs an epoch with a hardcoded emission value +// - Checks the emission distribution among parents, child, and weight setter +// - Verifies that all parties received emissions and the total stake increased correctly +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_childkey_multiple_parents_emission -- --nocapture +#[test] +fn test_childkey_multiple_parents_emission() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + // Set registration parameters and emission tempo + SubtensorModule::set_max_registrations_per_block(netuid, 1000); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000); + SubtensorModule::set_hotkey_emission_tempo(10); + + // Define hotkeys and coldkeys + let parent1: U256 = U256::from(1); + let parent2: U256 = U256::from(2); + let child: U256 = U256::from(3); + let weight_setter: U256 = U256::from(4); + let coldkey_parent1: U256 = U256::from(100); + let coldkey_parent2: U256 = U256::from(101); + let coldkey_child: U256 = U256::from(102); + let coldkey_weight_setter: U256 = U256::from(103); + + // Register neurons and add initial stakes + let initial_stakes: Vec<(U256, U256, u64)> = vec![ + (coldkey_parent1, parent1, 200_000), + (coldkey_parent2, parent2, 150_000), + (coldkey_child, child, 20_000), + (coldkey_weight_setter, weight_setter, 100_000), + ]; + + for (coldkey, hotkey, stake) in initial_stakes.iter() { + SubtensorModule::add_balance_to_coldkey_account(coldkey, *stake); + register_ok_neuron(netuid, *hotkey, *coldkey, 0); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(coldkey, hotkey, *stake); + } + + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + step_block(2); + + // Set parent-child relationships + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent1), + parent1, + netuid, + vec![(100_000, child)] + )); + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent2), + parent2, + netuid, + vec![(75_000, child)] + )); + + // Set weights + let uids: Vec = vec![0, 1, 2]; + let values: Vec = vec![0, 65354, 65354]; + let version_key = SubtensorModule::get_weights_version_key(netuid); + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(weight_setter), + netuid, + uids, + values, + version_key + )); + + // Run epoch with a hardcoded emission value + let hardcoded_emission: u64 = 1_000_000_000; // 1 billion + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission); + + // Process the hotkey emission results + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + SubtensorModule::accumulate_hotkey_emission( + &hotkey, + netuid, + validator_emission, + mining_emission, + ); + log::debug!( + "Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", + hotkey, + netuid, + mining_emission, + validator_emission + ); + } + + step_block(11); + + // Check emission distribution + let stakes: Vec<(U256, U256, &str)> = vec![ + (coldkey_parent1, parent1, "Parent1"), + (coldkey_parent2, parent2, "Parent2"), + (coldkey_child, child, "Child"), + (coldkey_weight_setter, weight_setter, "Weight setter"), + ]; + + for (coldkey, hotkey, name) in stakes.iter() { + let stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(coldkey, hotkey); + let stake_on_subnet = SubtensorModule::get_stake_for_hotkey_on_subnet(hotkey, netuid); + log::debug!( + "{} stake: {:?}, {} stake on subnet: {:?}", + name, + stake, + name, + stake_on_subnet + ); + } + + let parent1_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey_parent1, &parent1); + let parent2_stake = + SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey_parent2, &parent2); + let child_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey(&coldkey_child, &child); + let weight_setter_stake = SubtensorModule::get_stake_for_coldkey_and_hotkey( + &coldkey_weight_setter, + &weight_setter, + ); + + assert!( + parent1_stake > 200_000, + "Parent1 should have received emission" + ); + assert!( + parent2_stake > 150_000, + "Parent2 should have received emission" + ); + assert!(child_stake > 20_000, "Child should have received emission"); + assert!( + weight_setter_stake > 100_000, + "Weight setter should have received emission" + ); + + // Check individual stake increases + let parent1_stake_increase = parent1_stake - 200_000; + let parent2_stake_increase = parent2_stake - 150_000; + let child_stake_increase = child_stake - 20_000; + + log::debug!( + "Stake increases - Parent1: {}, Parent2: {}, Child: {}", + parent1_stake_increase, + parent2_stake_increase, + child_stake_increase + ); + + // Assert that all neurons received some emission + assert!( + parent1_stake_increase > 0, + "Parent1 should have received some emission" + ); + assert!( + parent2_stake_increase > 0, + "Parent2 should have received some emission" + ); + assert!( + child_stake_increase > 0, + "Child should have received some emission" + ); + + // Check that the total stake has increased by the hardcoded emission amount + let total_stake = parent1_stake + parent2_stake + child_stake + weight_setter_stake; + let initial_total_stake: u64 = initial_stakes.iter().map(|(_, _, stake)| stake).sum(); + assert_eq!( + total_stake, + initial_total_stake + hardcoded_emission - 2, // U64::MAX normalization rounding error + "Total stake should have increased by the hardcoded emission amount" + ); + }); +} + +// 44: Test with a chain of parent-child relationships (e.g., A -> B -> C) +// This test verifies the correct distribution of emissions in a chain of parent-child relationships: +// - Sets up a network with three neurons A, B, and C in a chain (A -> B -> C) +// - Establishes parent-child relationships with different stake proportions +// - Sets weights for all neurons +// - Runs an epoch with a hardcoded emission value +// - Checks the emission distribution among A, B, and C +// - Verifies that all parties received emissions and the total stake increased correctly +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test coinbase test_parent_child_chain_emission -- --nocapture +#[test] +fn test_parent_child_chain_emission() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + // Define hotkeys and coldkeys + let hotkey_a: U256 = U256::from(1); + let hotkey_b: U256 = U256::from(2); + let hotkey_c: U256 = U256::from(3); + let coldkey_a: U256 = U256::from(100); + let coldkey_b: U256 = U256::from(101); + let coldkey_c: U256 = U256::from(102); + + // Register neurons with decreasing stakes + register_ok_neuron(netuid, hotkey_a, coldkey_a, 0); + register_ok_neuron(netuid, hotkey_b, coldkey_b, 0); + register_ok_neuron(netuid, hotkey_c, coldkey_c, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_a, 300_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_b, 100_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_c, 50_000); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_a, &hotkey_a, 300_000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_b, &hotkey_b, 100_000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_c, &hotkey_c, 50_000); + + // Set parent-child relationships + // A -> B (50% of A's stake) + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_a), + hotkey_a, + netuid, + vec![(u64::MAX / 2, hotkey_b)] + )); + // B -> C (50% of B's stake) + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_b), + hotkey_b, + netuid, + vec![(u64::MAX / 2, hotkey_c)] + )); + + step_block(2); + + // Set weights + let origin = RuntimeOrigin::signed(hotkey_a); + let uids: Vec = vec![0, 1, 2]; // UIDs for hotkey_a, hotkey_b, hotkey_c + let values: Vec = vec![65535, 65535, 65535]; // Set equal weights for all hotkeys + let version_key = SubtensorModule::get_weights_version_key(netuid); + + // Ensure we can set weights without rate limiting + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + assert_ok!(SubtensorModule::set_weights( + origin, + netuid, + uids, + values, + version_key + )); + + // Run epoch with a hardcoded emission value + let hardcoded_emission: u64 = 1_000_000; // 1 million (adjust as needed) + let hotkey_emission: Vec<(U256, u64, u64)> = + SubtensorModule::epoch(netuid, hardcoded_emission); + + // Process the hotkey emission results + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + SubtensorModule::accumulate_hotkey_emission( + &hotkey, + netuid, + validator_emission, + mining_emission, + ); + } + + // Log PendingEmission Tuple for a, b, c + let pending_emission_a = SubtensorModule::get_pending_hotkey_emission(&hotkey_a); + let pending_emission_b = SubtensorModule::get_pending_hotkey_emission(&hotkey_b); + let pending_emission_c = SubtensorModule::get_pending_hotkey_emission(&hotkey_c); + + println!("Pending Emission for A: {:?}", pending_emission_a); + println!("Pending Emission for B: {:?}", pending_emission_b); + println!("Pending Emission for C: {:?}", pending_emission_c); + + // Assert that pending emissions are non-zero + // A's pending emission: 2/3 of total emission (due to having 2/3 of total stake) + assert!( + pending_emission_a == 666667, + "A should have pending emission of 2/3 of total emission" + ); + // B's pending emission: 2/9 of total emission (1/3 of A's emission + 1/3 of total emission) + assert!( + pending_emission_b == 222222, + "B should have pending emission of 2/9 of total emission" + ); + // C's pending emission: 1/9 of total emission (1/2 of B's emission) + assert!( + pending_emission_c == 111109, + "C should have pending emission of 1/9 of total emission" + ); + + SubtensorModule::set_hotkey_emission_tempo(10); + + step_block(10 + 1); + // Retrieve the current stake for each hotkey on the subnet + let stake_a: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_a, netuid); + let stake_b: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_b, netuid); + let stake_c: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_c, netuid); + + // Log the current stakes for debugging purposes + log::info!("Stake for hotkey A: {:?}", stake_a); + log::info!("Stake for hotkey B: {:?}", stake_b); + log::info!("Stake for hotkey C: {:?}", stake_c); + + // Assert that the stakes have been updated correctly after emission distribution + assert_eq!( + stake_a, 483334, + "A's stake should be 483334 (initial 300_000 + 666667 emission - 483333 given to B)" + ); + assert_eq!( + stake_b, 644445, + "B's stake should be 644445 (initial 100_000 + 222222 emission + 483333 from A - 161110 given to C)" + ); + assert_eq!( + stake_c, 322219, + "C's stake should be 322219 (initial 50_000 + 111109 emission + 161110 from B)" + ); + + // Check that the total stake has increased by the hardcoded emission amount + let total_stake = stake_a + stake_b + stake_c; + let initial_total_stake = 300_000 + 100_000 + 50_000; + let hardcoded_emission = 1_000_000; // Define the hardcoded emission value + assert_eq!( + total_stake, + initial_total_stake + hardcoded_emission - 2, // U64::MAX normalization rounding error + "Total stake should have increased by the hardcoded emission amount" + ); + }); +} + +// 46: Test emission distribution when adding/removing parent-child relationships mid-epoch +// This test verifies the correct distribution of emissions when parent-child relationships change: +// - Sets up a network with three neurons: parent, child1, and child2 +// - Establishes initial parent-child relationship between parent and child1 +// - Runs first epoch and distributes emissions +// - Changes parent-child relationships to include both child1 and child2 +// - Runs second epoch and distributes emissions +// - Checks final emission distribution and stake updates +// - Verifies correct parent-child relationships and stake proportions +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_dynamic_parent_child_relationships --exact --nocapture +#[test] +fn test_dynamic_parent_child_relationships() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + add_network(netuid, 1, 0); + + // Define hotkeys and coldkeys + let parent: U256 = U256::from(1); + let child1: U256 = U256::from(2); + let child2: U256 = U256::from(3); + let coldkey_parent: U256 = U256::from(100); + let coldkey_child1: U256 = U256::from(101); + let coldkey_child2: U256 = U256::from(102); + + // Register neurons with varying stakes + register_ok_neuron(netuid, parent, coldkey_parent, 0); + register_ok_neuron(netuid, child1, coldkey_child1, 0); + register_ok_neuron(netuid, child2, coldkey_child2, 0); + + // Add initial stakes + SubtensorModule::add_balance_to_coldkey_account(&coldkey_parent, 500_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_child1, 50_000); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_child2, 30_000); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_parent, &parent, 500_000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_child1, &child1, 50_000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey_child2, &child2, 30_000); + + // Set initial parent-child relationship + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent), + parent, + netuid, + vec![(u64::MAX / 2, child1)] + )); + + step_block(2); + + // Set weights + let origin = RuntimeOrigin::signed(parent); + let uids: Vec = vec![0, 1, 2]; // UIDs for parent, child1, child2 + let values: Vec = vec![65535, 65535, 65535]; // Set equal weights for all hotkeys + let version_key = SubtensorModule::get_weights_version_key(netuid); + + // Ensure we can set weights without rate limiting + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + assert_ok!(SubtensorModule::set_weights( + origin, + netuid, + uids, + values, + version_key + )); + + // Set hotkey emission tempo + SubtensorModule::set_hotkey_emission_tempo(10); + + // Run first epoch + let hardcoded_emission: u64 = 1_000_000; // 1 million (adjust as needed) + let hotkey_emission: Vec<(U256, u64, u64)> = SubtensorModule::epoch(netuid, hardcoded_emission); + + // Process the hotkey emission results + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + SubtensorModule::accumulate_hotkey_emission(&hotkey, netuid, validator_emission, mining_emission); + } + + // Step blocks to allow for emission distribution + step_block(11); + + // Change parent-child relationships + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent), + parent, + netuid, + vec![(u64::MAX / 4, child1), (u64::MAX / 3, child2)] + )); + + // Run second epoch + let hotkey_emission: Vec<(U256, u64, u64)> = SubtensorModule::epoch(netuid, hardcoded_emission); + + // Process the hotkey emission results + for (hotkey, mining_emission, validator_emission) in hotkey_emission { + SubtensorModule::accumulate_hotkey_emission(&hotkey, netuid, validator_emission, mining_emission); + } + + // Step blocks again to allow for emission distribution + step_block(11); + + // Check final emission distribution + let parent_stake: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child1_stake: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); + let child2_stake: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); + + println!("Final stakes:"); + println!("Parent stake: {}", parent_stake); + println!("Child1 stake: {}", child1_stake); + println!("Child2 stake: {}", child2_stake); + + const TOLERANCE: u64 = 5; // Allow for a small discrepancy due to potential rounding + + // Precise assertions with tolerance + assert!( + (parent_stake as i64 - 926725).abs() <= TOLERANCE as i64, + "Parent stake should be close to 926,725, but was {}", + parent_stake + ); + // Parent stake calculation: + // Initial stake: 500,000 + // First epoch: ~862,500 (500,000 + 725,000 * 1/2) + // Second epoch: ~926,725 (862,500 + 725,000 * 5/12) + + assert!( + (child1_stake as i64 - 778446).abs() <= TOLERANCE as i64, + "Child1 stake should be close to 778,446, but was {}", + child1_stake + ); + // Child1 stake calculation: + // Initial stake: 50,000 + // First epoch: ~412,500 (50,000 + 725,000 * 1/2) + // Second epoch: ~778,446 (412,500 + 725,000 * 1/2 * 1/4 + 137,500) + + assert!( + (child2_stake as i64 - 874826).abs() <= TOLERANCE as i64, + "Child2 stake should be close to 874,826, but was {}", + child2_stake + ); + // Child2 stake calculation: + // Initial stake: 30,000 + // First epoch: ~167,500 (30,000 + 137,500) + // Second epoch: ~874,826 (167,500 + 725,000 * 1/2 * 1/3 + 137,500) + + // Check that the total stake has increased by approximately twice the hardcoded emission amount + let total_stake: u64 = parent_stake + child1_stake + child2_stake; + let initial_total_stake: u64 = 500_000 + 50_000 + 30_000; + let total_emission: u64 = 2 * hardcoded_emission; + assert!( + (total_stake as i64 - (initial_total_stake + total_emission) as i64).abs() <= TOLERANCE as i64, + "Total stake should have increased by approximately twice the hardcoded emission amount" + ); + // Total stake calculation: + // Initial total stake: 500,000 + 50,000 + 30,000 = 580,000 + // Total emission: 2 * 1,000,000 = 2,000,000 + // Expected total stake: 580,000 + 2,000,000 = 2,580,000 + + // Additional checks for parent-child relationships + let parent_children: Vec<(u64, U256)> = SubtensorModule::get_children(&parent, netuid); + assert_eq!( + parent_children, + vec![(u64::MAX / 4, child1), (u64::MAX / 3, child2)], + "Parent should have both children with correct proportions" + ); + // Parent-child relationship: + // child1: 1/4 of parent's stake + // child2: 1/3 of parent's stake + + let child1_parents: Vec<(u64, U256)> = SubtensorModule::get_parents(&child1, netuid); + assert_eq!( + child1_parents, + vec![(u64::MAX / 4, parent)], + "Child1 should have parent as its parent with correct proportion" + ); + // Child1-parent relationship: + // parent: 1/4 of child1's stake + + let child2_parents: Vec<(u64, U256)> = SubtensorModule::get_parents(&child2, netuid); + assert_eq!( + child2_parents, + vec![(u64::MAX / 3, parent)], + "Child2 should have parent as its parent with correct proportion" + ); + // Child2-parent relationship: + // parent: 1/3 of child2's stake + + // Check that child2 has received more stake than child1 + assert!( + child2_stake > child1_stake, + "Child2 should have received more emission than Child1 due to higher proportion" + ); + // Child2 stake (874,826) > Child1 stake (778,446) + + // Check the approximate difference between child2 and child1 stakes + let stake_difference: u64 = child2_stake - child1_stake; + assert!( + (stake_difference as i64 - 96_380).abs() <= TOLERANCE as i64, + "The difference between Child2 and Child1 stakes should be close to 96,380, but was {}", + stake_difference + ); + // Stake difference calculation: + // Child2 stake: 874,826 + // Child1 stake: 778,446 + // Difference: 874,826 - 778,446 = 96,380 + }); +} + +// 47: Test basic stake retrieval for a single hotkey on a subnet +/// This test verifies the basic functionality of retrieving stake for a single hotkey on a subnet: +/// - Sets up a network with one neuron +/// - Increases stake for the neuron +/// - Checks if the retrieved stake matches the increased amount +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_basic --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_basic() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey = U256::from(1); + let coldkey = U256::from(2); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, 1000); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid), + 1000 + ); + }); +} + +// 48: Test stake retrieval for a hotkey with multiple coldkeys on a subnet +/// This test verifies the functionality of retrieving stake for a hotkey with multiple coldkeys on a subnet: +/// - Sets up a network with one neuron and two coldkeys +/// - Increases stake from both coldkeys +/// - Checks if the retrieved stake matches the total increased amount +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_multiple_coldkeys --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_multiple_coldkeys() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let hotkey = U256::from(1); + let coldkey1 = U256::from(2); + let coldkey2 = U256::from(3); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, hotkey, coldkey1, 0); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey1, &hotkey, 1000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey2, &hotkey, 2000); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid), + 3000 + ); + }); +} + +// 49: Test stake retrieval for a single parent-child relationship on a subnet +/// This test verifies the functionality of retrieving stake for a single parent-child relationship on a subnet: +/// - Sets up a network with a parent and child neuron +/// - Increases stake for the parent +/// - Sets the child as the parent's only child with 100% stake allocation +/// - Checks if the retrieved stake for both parent and child is correct +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_single_parent_child --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_single_parent_child() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent = U256::from(1); + let child = U256::from(2); + let coldkey = U256::from(3); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, parent, coldkey, 0); + register_ok_neuron(netuid, child, coldkey, 0); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &parent, 1000); + + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(u64::MAX, child)] + )); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid), + 0 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid), + 1000 + ); + }); +} + +// 50: Test stake retrieval for multiple parents and a single child on a subnet +/// This test verifies the functionality of retrieving stake for multiple parents and a single child on a subnet: +/// - Sets up a network with two parents and one child neuron +/// - Increases stake for both parents +/// - Sets the child as a 50% stake recipient for both parents +/// - Checks if the retrieved stake for parents and child is correct +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_multiple_parents_single_child --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_multiple_parents_single_child() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent1 = U256::from(1); + let parent2 = U256::from(2); + let child = U256::from(3); + let coldkey = U256::from(4); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, parent1, coldkey, 0); + register_ok_neuron(netuid, parent2, coldkey, 0); + register_ok_neuron(netuid, child, coldkey, 0); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &parent1, 1000); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &parent2, 2000); + + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent1, + netuid, + vec![(u64::MAX / 2, child)] + )); + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent2, + netuid, + vec![(u64::MAX / 2, child)] + )); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&parent1, netuid), + 501 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&parent2, netuid), + 1001 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid), + 1498 + ); + }); +} + +// 51: Test stake retrieval for a single parent with multiple children on a subnet +/// This test verifies the functionality of retrieving stake for a single parent with multiple children on a subnet: +/// - Sets up a network with one parent and two child neurons +/// - Increases stake for the parent +/// - Sets both children as 1/3 stake recipients of the parent +/// - Checks if the retrieved stake for parent and children is correct and preserves total stake +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_single_parent_multiple_children --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_single_parent_multiple_children() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent = U256::from(1); + let child1 = U256::from(2); + let child2 = U256::from(3); + let coldkey = U256::from(4); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, parent, coldkey, 0); + register_ok_neuron(netuid, child1, coldkey, 0); + register_ok_neuron(netuid, child2, coldkey, 0); + + let total_stake = 3000; + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &parent, total_stake); + + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(u64::MAX / 3, child1), (u64::MAX / 3, child2)] + )); + + let parent_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child1_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); + let child2_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); + + // Check that the total stake is preserved + assert_eq!(parent_stake + child1_stake + child2_stake, total_stake); + + // Check that the parent stake is slightly higher due to rounding + assert_eq!(parent_stake, 1002); + + // Check that each child gets an equal share of the remaining stake + assert_eq!(child1_stake, 999); + assert_eq!(child2_stake, 999); + + // Log the actual stake values + println!("Parent stake: {}", parent_stake); + println!("Child1 stake: {}", child1_stake); + println!("Child2 stake: {}", child2_stake); + }); +} + +// 52: Test stake retrieval for edge cases on a subnet +/// This test verifies the functionality of retrieving stake for edge cases on a subnet: +/// - Sets up a network with one parent and two child neurons +/// - Increases stake to the network maximum +/// - Sets children with 0% and 100% stake allocation +/// - Checks if the retrieved stake for parent and children is correct and preserves total stake +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_edge_cases --exact --nocapture +#[test] +fn test_get_stake_for_hotkey_on_subnet_edge_cases() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent = U256::from(1); + let child1 = U256::from(2); + let child2 = U256::from(3); + let coldkey = U256::from(4); + + add_network(netuid, 0, 0); + register_ok_neuron(netuid, parent, coldkey, 0); + register_ok_neuron(netuid, child1, coldkey, 0); + register_ok_neuron(netuid, child2, coldkey, 0); + + // Set network max stake + let network_max_stake: u64 = 500_000_000_000_000; // 500_000 TAO + SubtensorModule::set_network_max_stake(netuid, network_max_stake); + + // Increase stake to the network max + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey, + &parent, + network_max_stake, + ); + + // Test with 0% and 100% stake allocation + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey), + parent, + netuid, + vec![(0, child1), (u64::MAX, child2)] + )); + + let parent_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child1_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); + let child2_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); + + println!("Parent stake: {}", parent_stake); + println!("Child1 stake: {}", child1_stake); + println!("Child2 stake: {}", child2_stake); + + assert_eq!(parent_stake, 0, "Parent should have 0 stake"); + assert_eq!(child1_stake, 0, "Child1 should have 0 stake"); + assert_eq!( + child2_stake, network_max_stake, + "Child2 should have all the stake" + ); + + // Check that the total stake is preserved and equal to the network max stake + assert_eq!( + parent_stake + child1_stake + child2_stake, + network_max_stake, + "Total stake should equal the network max stake" + ); + }); +} + +// 53: Test stake distribution in a complex hierarchy of parent-child relationships +// This test verifies the correct distribution of stake in a multi-level parent-child hierarchy: +// - Sets up a network with four neurons: parent, child1, child2, and grandchild +// - Establishes parent-child relationships between parent and its children, and child1 and grandchild +// - Adds initial stake to the parent +// - Checks stake distribution after setting up the first level of relationships +// - Checks stake distribution after setting up the second level of relationships +// - Verifies correct stake calculations, parent-child relationships, and preservation of total stake +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_complex_hierarchy --exact --nocapture + +#[test] +fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let parent = U256::from(1); + let child1 = U256::from(2); + let child2 = U256::from(3); + let grandchild = U256::from(4); + let coldkey_parent = U256::from(5); + let coldkey_child1 = U256::from(6); + let coldkey_child2 = U256::from(7); + let coldkey_grandchild = U256::from(8); + + add_network(netuid, 0, 0); + SubtensorModule::set_max_registrations_per_block(netuid, 1000); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000); + register_ok_neuron(netuid, parent, coldkey_parent, 0); + register_ok_neuron(netuid, child1, coldkey_child1, 0); + register_ok_neuron(netuid, child2, coldkey_child2, 0); + register_ok_neuron(netuid, grandchild, coldkey_grandchild, 0); + + let total_stake = 1000; + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &coldkey_parent, + &parent, + total_stake, + ); + + println!("Initial stakes:"); + println!( + "Parent stake: {}", + SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid) + ); + println!( + "Child1 stake: {}", + SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid) + ); + println!( + "Child2 stake: {}", + SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid) + ); + println!( + "Grandchild stake: {}", + SubtensorModule::get_stake_for_hotkey_on_subnet(&grandchild, netuid) + ); + + // Step 1: Set children for parent + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_parent), + parent, + netuid, + vec![(u64::MAX / 2, child1), (u64::MAX / 2, child2)] + )); + + println!("After setting parent's children:"); + println!( + "Parent's children: {:?}", + SubtensorModule::get_children(&parent, netuid) + ); + println!( + "Child1's parents: {:?}", + SubtensorModule::get_parents(&child1, netuid) + ); + println!( + "Child2's parents: {:?}", + SubtensorModule::get_parents(&child2, netuid) + ); + + let parent_stake_1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child1_stake_1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); + let child2_stake_1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); + + println!("Parent stake: {}", parent_stake_1); + println!("Child1 stake: {}", child1_stake_1); + println!("Child2 stake: {}", child2_stake_1); + + assert_eq!( + parent_stake_1, 2, + "Parent should have 2 stake due to rounding" + ); + assert_eq!(child1_stake_1, 499, "Child1 should have 499 stake"); + assert_eq!(child2_stake_1, 499, "Child2 should have 499 stake"); + + // Step 2: Set children for child1 + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(coldkey_child1), + child1, + netuid, + vec![(u64::MAX, grandchild)] + )); + + println!("After setting child1's children:"); + println!( + "Child1's children: {:?}", + SubtensorModule::get_children(&child1, netuid) + ); + println!( + "Grandchild's parents: {:?}", + SubtensorModule::get_parents(&grandchild, netuid) + ); + + let parent_stake_2 = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); + let child1_stake_2 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); + let child2_stake_2 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); + let grandchild_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&grandchild, netuid); + + println!("Parent stake: {}", parent_stake_2); + println!("Child1 stake: {}", child1_stake_2); + println!("Child2 stake: {}", child2_stake_2); + println!("Grandchild stake: {}", grandchild_stake); + + assert_eq!(parent_stake_2, 2, "Parent stake should remain 2"); + assert_eq!( + child1_stake_2, 499, + "Child1 stake should be be the same , as it doesnt have owned stake" + ); + assert_eq!(child2_stake_2, 499, "Child2 should still have 499 stake"); + assert_eq!( + grandchild_stake, 0, + "Grandchild should have 0 , as child1 doesnt have any owned stake" + ); + + // Check that the total stake is preserved + assert_eq!( + parent_stake_2 + child1_stake_2 + child2_stake_2 + grandchild_stake, + total_stake, + "Total stake should equal the initial stake" + ); + + // Additional checks + println!("Final parent-child relationships:"); + println!( + "Parent's children: {:?}", + SubtensorModule::get_children(&parent, netuid) + ); + println!( + "Child1's parents: {:?}", + SubtensorModule::get_parents(&child1, netuid) + ); + println!( + "Child2's parents: {:?}", + SubtensorModule::get_parents(&child2, netuid) + ); + println!( + "Child1's children: {:?}", + SubtensorModule::get_children(&child1, netuid) + ); + println!( + "Grandchild's parents: {:?}", + SubtensorModule::get_parents(&grandchild, netuid) + ); + + // Check if the parent-child relationships are correct + assert_eq!( + SubtensorModule::get_children(&parent, netuid), + vec![(u64::MAX / 2, child1), (u64::MAX / 2, child2)], + "Parent should have both children" + ); + assert_eq!( + SubtensorModule::get_parents(&child1, netuid), + vec![(u64::MAX / 2, parent)], + "Child1 should have parent as its parent" + ); + assert_eq!( + SubtensorModule::get_parents(&child2, netuid), + vec![(u64::MAX / 2, parent)], + "Child2 should have parent as its parent" + ); + assert_eq!( + SubtensorModule::get_children(&child1, netuid), + vec![(u64::MAX, grandchild)], + "Child1 should have grandchild as its child" + ); + assert_eq!( + SubtensorModule::get_parents(&grandchild, netuid), + vec![(u64::MAX, child1)], + "Grandchild should have child1 as its parent" + ); + }); +} + +// 54: Test stake distribution across multiple networks +// This test verifies the correct distribution of stake for a single neuron across multiple networks: +// - Sets up two networks with a single neuron registered on both +// - Adds initial stake to the neuron +// - Checks that the stake is correctly reflected on both networks +// - Verifies that changes in stake are consistently applied across all networks +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_get_stake_for_hotkey_on_subnet_multiple_networks --exact --nocapture + +#[test] +fn test_get_stake_for_hotkey_on_subnet_multiple_networks() { + new_test_ext(1).execute_with(|| { + let netuid1: u16 = 1; + let netuid2: u16 = 2; + let hotkey = U256::from(1); + let coldkey = U256::from(2); + + add_network(netuid1, 0, 0); + add_network(netuid2, 0, 0); + register_ok_neuron(netuid1, hotkey, coldkey, 0); + register_ok_neuron(netuid2, hotkey, coldkey, 0); + + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, 1000); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid1), + 1000 + ); + assert_eq!( + SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid2), + 1000 + ); + }); +} + +/// 55: Test rank, trust, and incentive calculation with parent-child relationships +/// +/// This test verifies the correct calculation and distribution of rank, trust, incentive, and dividends +/// in a network with parent-child relationships: +/// - Sets up a network with validators (including a parent-child pair) and miners +/// - Establishes initial stakes and weights for all validators +/// - Runs a first epoch to establish baseline metrics +/// - Sets up a parent-child relationship +/// - Runs a second epoch to observe changes in metrics +/// - Verifies that the child's metrics improve relative to its initial state and other validators +/// +/// # Test Steps: +/// 1. Initialize test environment with validators (including parent and child) and miners +/// 2. Set up network parameters and register all neurons +/// 3. Set initial stakes for validators +/// 4. Set initial weights for all validators +/// 5. Run first epoch and process emissions +/// 6. Record initial metrics for the child +/// 7. Establish parent-child relationship +/// 8. Run second epoch and process emissions +/// 9. Record final metrics for the child +/// 10. Compare child's initial and final metrics +/// 11. Compare child's final metrics with other validators +/// +/// # Expected Results: +/// - Child's rank should improve (decrease) +/// - Child's trust should increase or remain the same +/// - Child's dividends should increase +/// - Child's final metrics should be better than or equal to other validators' +/// +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_rank_trust_incentive_calculation_with_parent_child --exact --nocapture +#[test] +fn test_rank_trust_incentive_calculation_with_parent_child() { + new_test_ext(1).execute_with(|| { + // Initialize test environment + let netuid: u16 = 1; + let parent_hotkey: U256 = U256::from(1); + let parent_coldkey: U256 = U256::from(101); + let child_hotkey: U256 = U256::from(2); + let child_coldkey: U256 = U256::from(102); + let other_validators: Vec<(U256, U256)> = (3..6) + .map(|i| (U256::from(i), U256::from(100 + i))) + .collect(); + let miners: Vec<(U256, U256)> = (6..16) + .map(|i| (U256::from(i), U256::from(100 + i))) + .collect(); // 10 miners + + // Setup network and set registration parameters + add_network(netuid, 1, 0); + SubtensorModule::set_max_registrations_per_block(netuid, 1000); + SubtensorModule::set_target_registrations_per_interval(netuid, 1000); + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_hotkey_emission_tempo(10); + + // Register neurons (validators and miners) + register_ok_neuron(netuid, parent_hotkey, parent_coldkey, 0); + register_ok_neuron(netuid, child_hotkey, child_coldkey, 0); + for (hotkey, coldkey) in &other_validators { + register_ok_neuron(netuid, *hotkey, *coldkey, 0); + } + for (hotkey, coldkey) in &miners { + register_ok_neuron(netuid, *hotkey, *coldkey, 0); + } + + step_block(2); + + // Set initial stakes for validators only + let initial_stake: u64 = 1_000_000_000; // 1000 TAO + SubtensorModule::add_balance_to_coldkey_account(&parent_coldkey, initial_stake); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &parent_coldkey, + &parent_hotkey, + initial_stake, + ); + SubtensorModule::add_balance_to_coldkey_account(&child_coldkey, initial_stake); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + &child_coldkey, + &child_hotkey, + initial_stake, + ); + for (hotkey, coldkey) in &other_validators { + SubtensorModule::add_balance_to_coldkey_account(coldkey, initial_stake); + SubtensorModule::increase_stake_on_coldkey_hotkey_account( + coldkey, + hotkey, + initial_stake, + ); + } + + step_block(2); + + // Set initial weights for all validators + let all_uids: Vec = (0..15).collect(); // 0-4 are validators, 5-14 are miners + let validator_weights: Vec = vec![u16::MAX / 5; 5] // Equal weights for validators + .into_iter() + .chain(vec![u16::MAX / 10; 10]) // Equal weights for miners + .collect(); + + for hotkey in std::iter::once(&parent_hotkey) + .chain(other_validators.iter().map(|(h, _)| h)) + .chain(std::iter::once(&child_hotkey)) + { + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(*hotkey), + netuid, + all_uids.clone(), + validator_weights.clone(), + 0 + )); + } + + step_block(10); + + // Run first epoch + let rao_emission: u64 = 1_000_000_000; + let initial_emission = SubtensorModule::epoch(netuid, rao_emission); + + // Process initial emission + for (hotkey, mining_emission, validator_emission) in initial_emission { + SubtensorModule::accumulate_hotkey_emission( + &hotkey, + netuid, + validator_emission, + mining_emission, + ); + } + + step_block(11); + + // Get initial rank, trust, incentive, and dividends for the child + let initial_child_rank: u16 = SubtensorModule::get_rank_for_uid(netuid, 1); + let initial_child_trust: u16 = SubtensorModule::get_trust_for_uid(netuid, 1); + let initial_child_incentive: u16 = SubtensorModule::get_incentive_for_uid(netuid, 1); + let initial_child_dividends: u16 = SubtensorModule::get_dividends_for_uid(netuid, 1); + + log::debug!("Initial child rank: {:?}", initial_child_rank); + log::debug!("Initial child trust: {:?}", initial_child_trust); + log::debug!("Initial child incentive: {:?}", initial_child_incentive); + log::debug!("Initial child dividends: {:?}", initial_child_dividends); + + // Parent sets the child with 100% of its weight + assert_ok!(SubtensorModule::do_set_children( + RuntimeOrigin::signed(parent_coldkey), + parent_hotkey, + netuid, + vec![(u64::MAX, child_hotkey)] + )); + + // Child now sets weights as a validator + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(child_hotkey), + netuid, + all_uids.clone(), + validator_weights.clone(), + 1 + )); + + step_block(10); + + // Run second epoch + let final_emission = SubtensorModule::epoch(netuid, rao_emission); + + // Process final emission + for (hotkey, mining_emission, validator_emission) in final_emission { + SubtensorModule::accumulate_hotkey_emission( + &hotkey, + netuid, + validator_emission, + mining_emission, + ); + } + + step_block(11); + + // Get final rank, trust, incentive, and dividends for the child + let final_child_rank: u16 = SubtensorModule::get_rank_for_uid(netuid, 1); + let final_child_trust: u16 = SubtensorModule::get_trust_for_uid(netuid, 1); + let final_child_incentive: u16 = SubtensorModule::get_incentive_for_uid(netuid, 1); + let final_child_dividends: u16 = SubtensorModule::get_dividends_for_uid(netuid, 1); + + log::debug!("Final child rank: {:?}", final_child_rank); + log::debug!("Final child trust: {:?}", final_child_trust); + log::debug!("Final child incentive: {:?}", final_child_incentive); + log::debug!("Final child dividends: {:?}", final_child_dividends); + + // Print ranks for all validators + for i in 0..5 { + log::debug!( + "Validator {} rank: {:?}", + i, + SubtensorModule::get_rank_for_uid(netuid, i) + ); + } + + // Assert that rank has improved (decreased) for the child + assert!( + final_child_rank < initial_child_rank, + "Child rank should have improved (decreased). Initial: {}, Final: {}", + initial_child_rank, + final_child_rank + ); + + // Assert that trust has increased or remained the same for the child + assert!( + final_child_trust >= initial_child_trust, + "Child trust should have increased or remained the same. Initial: {}, Final: {}", + initial_child_trust, + final_child_trust + ); + + + // Assert that dividends have increased for the child + assert!( + final_child_dividends > initial_child_dividends, + "Child dividends should have increased. Initial: {}, Final: {}", + initial_child_dividends, + final_child_dividends + ); + + // Compare child's final values with other validators + for i in 2..5 { + let other_rank: u16 = SubtensorModule::get_rank_for_uid(netuid, i); + let other_trust: u16 = SubtensorModule::get_trust_for_uid(netuid, i); + let other_incentive: u16 = SubtensorModule::get_incentive_for_uid(netuid, i); + let other_dividends: u16 = SubtensorModule::get_dividends_for_uid(netuid, i); + + log::debug!( + "Validator {} - Rank: {}, Trust: {}, Incentive: {}, Dividends: {}", + i, other_rank, other_trust, other_incentive, other_dividends + ); + + assert!( + final_child_rank <= other_rank, + "Child rank should be better than or equal to other validators. Child: {}, Other: {}", + final_child_rank, + other_rank + ); + + assert!( + final_child_trust >= other_trust, + "Child trust should be greater than or equal to other validators. Child: {}, Other: {}", + final_child_trust, + other_trust + ); + + assert!( + final_child_dividends >= other_dividends, + "Child dividends should be greater than or equal to other validators. Child: {}, Other: {}", + final_child_dividends, + other_dividends + ); + } + + }); +} diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 4039054e0..022849c56 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -507,3 +507,21 @@ pub fn add_network(netuid: u16, tempo: u16, _modality: u16) { SubtensorModule::set_network_registration_allowed(netuid, true); SubtensorModule::set_network_pow_registration_allowed(netuid, true); } + +// Helper function to set up a neuron with stake +#[allow(dead_code)] +pub fn setup_neuron_with_stake(netuid: u16, hotkey: U256, coldkey: U256, stake: u64) { + register_ok_neuron(netuid, hotkey, coldkey, stake); + SubtensorModule::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake); +} + +// Helper function to check if a value is within tolerance of an expected value +#[allow(dead_code)] +pub fn is_within_tolerance(actual: u64, expected: u64, tolerance: u64) -> bool { + let difference = if actual > expected { + actual - expected + } else { + expected - actual + }; + difference <= tolerance +} From 932e7b238bc4968ec0f2724797805f6d5935ab5c Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:32:14 -0400 Subject: [PATCH 151/269] compiling but overflowing stack --- build.rs | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/build.rs b/build.rs index 7e653ebb3..970b1a88c 100644 --- a/build.rs +++ b/build.rs @@ -1,6 +1,8 @@ +use rayon::prelude::*; use std::env; use std::fs; use std::path::{Path, PathBuf}; +use std::sync::Mutex; use syn::Result; use walkdir::WalkDir; @@ -15,39 +17,43 @@ fn main() { // Collect all Rust source files in the workspace let rust_files = collect_rust_files(workspace_root); - let mut found_error = None; + let found_error = Mutex::new(None); - // Parse each Rust file with syn - for file in rust_files { - if found_error.is_some() { - break; + // Parse each rust file with syn and run the linting suite on it in parallel + rust_files.par_iter().for_each(|file| { + if found_error.lock().unwrap().is_some() { + return; } let Ok(content) = fs::read_to_string(&file) else { - continue; + return; }; let Ok(parsed_file) = syn::parse_file(&content) else { - continue; + return; }; let track_lint = |result: Result<()>| { let Err(error) = result else { return; }; - found_error = Some((error, file)); + *found_error.lock().unwrap() = Some((error, file.clone())); }; track_lint(DummyLint::lint(parsed_file)); - } + }); - if let Some((error, file)) = found_error { - let start = error.span().start(); - let end = error.span().end(); - let start_line = start.line; - let start_col = start.column; - let _end_line = end.line; - let _end_col = end.column; - let file_path = file.display(); - panic!("{}:{}:{}: {}", file_path, start_line, start_col, error); + // Use a separate scope to ensure the lock is released before the function exits + { + let guard = found_error.lock().expect("mutex was poisoned"); + if let Some((error, file)) = &*guard { + let start = error.span().start(); + let end = error.span().end(); + let start_line = start.line; + let start_col = start.column; + let _end_line = end.line; + let _end_col = end.column; + let file_path = file.display(); + panic!("{}:{}:{}: {}", file_path, start_line, start_col, error); + } } } From 4a87650b4abda9ee37191615db75d26a8d4cc368 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:37:24 -0400 Subject: [PATCH 152/269] working in parallel :tada: --- build.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build.rs b/build.rs index 970b1a88c..cb66f2894 100644 --- a/build.rs +++ b/build.rs @@ -57,7 +57,7 @@ fn main() { } } -// Recursively collects all Rust files in the given directory +/// Recursively collects all Rust files in the given directory fn collect_rust_files(dir: &Path) -> Vec { let mut rust_files = Vec::new(); @@ -65,7 +65,12 @@ fn collect_rust_files(dir: &Path) -> Vec { let entry = entry.unwrap(); let path = entry.path(); - if path.ends_with("target") || path.ends_with("build.rs") { + // Skip any path that contains "target" directory + if path + .components() + .any(|component| component.as_os_str() == "target") + || path.ends_with("build.rs") + { continue; } From 60f04ad56b2e0f84a4dfb9d3d04e1c02a0c8ed53 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 10:55:22 -0400 Subject: [PATCH 153/269] use channels, warning syntax 100% working --- build.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/build.rs b/build.rs index cb66f2894..32288c306 100644 --- a/build.rs +++ b/build.rs @@ -2,7 +2,7 @@ use rayon::prelude::*; use std::env; use std::fs; use std::path::{Path, PathBuf}; -use std::sync::Mutex; +use std::sync::mpsc::channel; use syn::Result; use walkdir::WalkDir; @@ -17,13 +17,10 @@ fn main() { // Collect all Rust source files in the workspace let rust_files = collect_rust_files(workspace_root); - let found_error = Mutex::new(None); + let (tx, rx) = channel(); // Parse each rust file with syn and run the linting suite on it in parallel - rust_files.par_iter().for_each(|file| { - if found_error.lock().unwrap().is_some() { - return; - } + rust_files.par_iter().for_each_with(tx.clone(), |tx, file| { let Ok(content) = fs::read_to_string(&file) else { return; }; @@ -35,25 +32,28 @@ fn main() { let Err(error) = result else { return; }; - *found_error.lock().unwrap() = Some((error, file.clone())); + tx.send((error, file.clone())).unwrap(); }; track_lint(DummyLint::lint(parsed_file)); }); - // Use a separate scope to ensure the lock is released before the function exits - { - let guard = found_error.lock().expect("mutex was poisoned"); - if let Some((error, file)) = &*guard { - let start = error.span().start(); - let end = error.span().end(); - let start_line = start.line; - let start_col = start.column; - let _end_line = end.line; - let _end_col = end.column; - let file_path = file.display(); - panic!("{}:{}:{}: {}", file_path, start_line, start_col, error); - } + // Collect and print all errors after the parallel processing is done + drop(tx); // Close the sending end of the channel + + for (error, file) in rx { + let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); + let start = error.span().start(); + let end = error.span().end(); + let start_line = start.line; + let start_col = start.column; + let _end_line = end.line; + let _end_col = end.column; + let file_path = relative_path.display(); + println!( + "cargo:warning={}:{}:{}: {}", + file_path, start_line, start_col, error + ); } } From 3de5b038307d3553e7999b47e960deab2bcc9a39 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 7 Aug 2024 07:56:25 -0700 Subject: [PATCH 154/269] Update run_coinbase.rs --- pallets/subtensor/src/coinbase/run_coinbase.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 442a9f085..f7fa3b2a2 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -131,6 +131,11 @@ impl Pallet { log::debug!("Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", hotkey, *netuid, mining_emission, validator_emission); } } else { + // No epoch, increase blocks since last step and continue, + Self::set_blocks_since_last_step( + *netuid, + Self::get_blocks_since_last_step(*netuid).saturating_add(1), + ); log::debug!("Tempo not reached for subnet: {:?}", *netuid); } } From 037b76db2637d2e47970f3909ea1e4ab5256bf19 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 7 Aug 2024 09:13:58 -0700 Subject: [PATCH 155/269] add test_blocks_since_last_step --- .../subtensor/src/coinbase/run_coinbase.rs | 2 +- pallets/subtensor/tests/epoch.rs | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index f7fa3b2a2..723edc423 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -131,7 +131,7 @@ impl Pallet { log::debug!("Accumulated emissions on hotkey {:?} for netuid {:?}: mining {:?}, validator {:?}", hotkey, *netuid, mining_emission, validator_emission); } } else { - // No epoch, increase blocks since last step and continue, + // No epoch, increase blocks since last step and continue Self::set_blocks_since_last_step( *netuid, Self::get_blocks_since_last_step(*netuid).saturating_add(1), diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index b639a4ac4..9c4bf87cc 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -2703,6 +2703,53 @@ fn test_get_set_alpha() { }); } +#[test] +fn test_blocks_since_last_step() { + new_test_ext(1).execute_with(|| { + System::set_block_number(0); + + let netuid: u16 = 1; + let tempo: u16 = 7200; + add_network(netuid, tempo, 0); + + let original_blocks: u64 = SubtensorModule::get_blocks_since_last_step(netuid); + + step_block(5); + + let new_blocks: u64 = SubtensorModule::get_blocks_since_last_step(netuid); + + assert!(new_blocks > original_blocks); + assert_eq!(new_blocks, 5); + + let blocks_to_step: u16 = SubtensorModule::blocks_until_next_epoch( + netuid, + tempo, + SubtensorModule::get_current_block_as_u64(), + ) as u16 + + 10; + step_block(blocks_to_step); + + let post_blocks: u64 = SubtensorModule::get_blocks_since_last_step(netuid); + + assert_eq!(post_blocks, 10); + + let blocks_to_step: u16 = SubtensorModule::blocks_until_next_epoch( + netuid, + tempo, + SubtensorModule::get_current_block_as_u64(), + ) as u16 + + 20; + step_block(blocks_to_step); + + let new_post_blocks: u64 = SubtensorModule::get_blocks_since_last_step(netuid); + + assert_eq!(new_post_blocks, 20); + + step_block(7); + + assert_eq!(SubtensorModule::get_blocks_since_last_step(netuid), 27); + }); +} // // Map the retention graph for consensus guarantees with an single epoch on a graph with 512 nodes, of which the first 64 are validators, the graph is split into a major and minor set, each setting specific weight on itself and the complement on the other. // // // // ```import torch From 75857e94628b0d50899bd036a5a7295713965a68 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 12:33:11 -0400 Subject: [PATCH 156/269] freeze struct lint scaffold --- build.rs | 3 ++- lints/dummy_lint.rs | 2 +- lints/lint.rs | 3 +-- lints/mod.rs | 3 +++ lints/require_freeze_struct.rs | 9 +++++++++ 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 lints/require_freeze_struct.rs diff --git a/build.rs b/build.rs index 32288c306..6b7d52e41 100644 --- a/build.rs +++ b/build.rs @@ -35,7 +35,8 @@ fn main() { tx.send((error, file.clone())).unwrap(); }; - track_lint(DummyLint::lint(parsed_file)); + track_lint(DummyLint::lint(&parsed_file)); + track_lint(RequireFreezeStruct::lint(&parsed_file)); }); // Collect and print all errors after the parallel processing is done diff --git a/lints/dummy_lint.rs b/lints/dummy_lint.rs index 41338b484..5364f2741 100644 --- a/lints/dummy_lint.rs +++ b/lints/dummy_lint.rs @@ -3,7 +3,7 @@ use super::*; pub struct DummyLint; impl Lint for DummyLint { - fn lint(_source: File) -> Result<()> { + fn lint(_source: &File) -> Result<()> { Ok(()) } } diff --git a/lints/lint.rs b/lints/lint.rs index 4a69995d9..16ce15f5d 100644 --- a/lints/lint.rs +++ b/lints/lint.rs @@ -1,4 +1,3 @@ - use super::*; /// A trait that defines custom lints that can be run within our workspace. @@ -8,5 +7,5 @@ use super::*; /// there are no errors. pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. - fn lint(source: File) -> Result<()>; + fn lint(source: &File) -> Result<()>; } diff --git a/lints/mod.rs b/lints/mod.rs index d2f9bb1e7..2a4a7103f 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -4,4 +4,7 @@ pub mod lint; pub use lint::*; mod dummy_lint; +mod require_freeze_struct; + pub use dummy_lint::DummyLint; +pub use require_freeze_struct::RequireFreezeStruct; diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs new file mode 100644 index 000000000..0f2383a41 --- /dev/null +++ b/lints/require_freeze_struct.rs @@ -0,0 +1,9 @@ +use super::*; + +pub struct RequireFreezeStruct; + +impl Lint for RequireFreezeStruct { + fn lint(_source: &File) -> Result<()> { + Ok(()) + } +} From a9200bcfda3490133b0cea46275c6e6811aa4b10 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 14:28:04 -0400 Subject: [PATCH 157/269] working! cleaning up now --- Cargo.lock | 1 + Cargo.toml | 1 + build.rs | 2 ++ lints/require_freeze_struct.rs | 62 +++++++++++++++++++++++++++++++++- pallets/collective/src/lib.rs | 1 + 5 files changed, 66 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c24eca909..43a041c2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9191,6 +9191,7 @@ dependencies = [ "pallet-commitments", "pallet-subtensor", "proc-macro2", + "quote", "rayon", "subtensor-macros", "syn 2.0.71", diff --git a/Cargo.toml b/Cargo.toml index bec2d95e3..12e92e813 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ subtensor-macros = { path = "support/macros", version = "0.1.0" } [build-dependencies] syn.workspace = true +quote.workspace = true proc-macro2.workspace = true walkdir.workspace = true rayon = "1.10" diff --git a/build.rs b/build.rs index 6b7d52e41..387a71d9d 100644 --- a/build.rs +++ b/build.rs @@ -17,6 +17,8 @@ fn main() { // Collect all Rust source files in the workspace let rust_files = collect_rust_files(workspace_root); + // Channel used to communicate errors back to the main thread from the parallel processing + // as we process each Rust file let (tx, rx) = channel(); // Parse each rust file with syn and run the linting suite on it in parallel diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index 0f2383a41..46f370dd1 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -1,9 +1,69 @@ use super::*; +use syn::parse_quote; +use syn::punctuated::Punctuated; +use syn::{visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, Result, Token}; pub struct RequireFreezeStruct; impl Lint for RequireFreezeStruct { - fn lint(_source: &File) -> Result<()> { + fn lint(source: &syn::File) -> Result<()> { + let mut visitor = EncodeDecodeVisitor::default(); + visitor.visit_file(source); + + if !visitor.errors.is_empty() { + for error in visitor.errors { + return Err(error); + } + } + Ok(()) } } + +#[derive(Default)] +struct EncodeDecodeVisitor { + errors: Vec, +} + +impl<'ast> Visit<'ast> for EncodeDecodeVisitor { + fn visit_item_struct(&mut self, node: &'ast ItemStruct) { + let has_encode_decode = node.attrs.iter().any(|attr| { + let result = is_derive_encode_or_decode(attr); + result + }); + let has_freeze_struct = node.attrs.iter().any(|attr| { + let result = is_freeze_struct(attr); + result + }); + + if has_encode_decode && !has_freeze_struct { + self.errors.push(syn::Error::new_spanned( + &node.ident, + "Struct with Encode/Decode derive must also have #[freeze_struct(..)] attribute.", + )); + } + + syn::visit::visit_item_struct(self, node); + } +} + +fn is_freeze_struct(attr: &Attribute) -> bool { + if let Meta::Path(ref path) = attr.meta { + path.is_ident("freeze_struct") + } else { + false + } +} + +fn is_derive_encode_or_decode(attr: &Attribute) -> bool { + if let Meta::List(MetaList { path, tokens, .. }) = &attr.meta { + if path.is_ident("derive") { + let nested: Punctuated = parse_quote!(#tokens); + return nested.iter().any(|nested| { + nested.segments.iter().any(|seg| seg.ident == "Encode") + || nested.segments.iter().any(|seg| seg.ident == "Decode") + }); + } + } + false +} diff --git a/pallets/collective/src/lib.rs b/pallets/collective/src/lib.rs index 6aae3c85e..823e92663 100644 --- a/pallets/collective/src/lib.rs +++ b/pallets/collective/src/lib.rs @@ -165,6 +165,7 @@ pub struct Votes { /// The hard end time of this vote. end: BlockNumber, } + #[deny(missing_docs)] #[frame_support::pallet] pub mod pallet { From 323f5ef432f395d51979052456e9e639349337e5 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 14:38:35 -0400 Subject: [PATCH 158/269] now actually working, before had false positives --- lints/require_freeze_struct.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index 46f370dd1..00f9322f0 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -48,11 +48,12 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { } fn is_freeze_struct(attr: &Attribute) -> bool { - if let Meta::Path(ref path) = attr.meta { - path.is_ident("freeze_struct") - } else { - false + if let Meta::List(meta_list) = &attr.meta { + if meta_list.path.is_ident("freeze_struct") && !meta_list.tokens.is_empty() { + return true; + } } + false } fn is_derive_encode_or_decode(attr: &Attribute) -> bool { From a71a3a08eb9a873fe31a3def8662a6da55a4eefc Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 16:03:53 -0400 Subject: [PATCH 159/269] WIP --- Cargo.toml | 2 +- build.rs | 15 +++------ lints/mod.rs | 60 ++++++++++++++++++++++++++++++++++ lints/require_freeze_struct.rs | 2 +- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12e92e813..dba517984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } quote = "1" -proc-macro2 = "1" +proc-macro2 = { version = "1", features = ["span-locations"] } walkdir = "2" subtensor-macros = { path = "support/macros" } diff --git a/build.rs b/build.rs index 387a71d9d..14684b5eb 100644 --- a/build.rs +++ b/build.rs @@ -34,7 +34,7 @@ fn main() { let Err(error) = result else { return; }; - tx.send((error, file.clone())).unwrap(); + tx.send((error, file.clone(), content.to_string())).unwrap(); }; track_lint(DummyLint::lint(&parsed_file)); @@ -44,18 +44,13 @@ fn main() { // Collect and print all errors after the parallel processing is done drop(tx); // Close the sending end of the channel - for (error, file) in rx { + for (error, file, content) in rx { let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); - let start = error.span().start(); - let end = error.span().end(); - let start_line = start.line; - let start_col = start.column; - let _end_line = end.line; - let _end_col = end.column; + let loc = error.span().location(&content); let file_path = relative_path.display(); println!( - "cargo:warning={}:{}:{}: {}", - file_path, start_line, start_col, error + "cargo:warning={}:{}:{}: {} (ends at {}:{})", + file_path, loc.start_line, loc.start_col, error, loc.end_line, loc.end_col ); } } diff --git a/lints/mod.rs b/lints/mod.rs index 2a4a7103f..91aa89ced 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -8,3 +8,63 @@ mod require_freeze_struct; pub use dummy_lint::DummyLint; pub use require_freeze_struct::RequireFreezeStruct; + +#[derive(Copy, Clone, Debug)] +pub struct SpanLocation { + pub start_line: usize, + pub start_col: usize, + pub end_line: usize, + pub end_col: usize, +} + +impl Default for SpanLocation { + fn default() -> Self { + Self { + start_line: 1, + start_col: 0, + end_line: 1, + end_col: 0, + } + } +} + +pub trait SpanHack { + fn location(&self, source: &str) -> SpanLocation; +} + +impl SpanHack for proc_macro2::Span { + fn location(&self, source: &str) -> SpanLocation { + let range = self.byte_range(); + + let mut start_line = 1; + let mut start_col = 0; + let mut end_line = 1; + let mut end_col = 0; + let mut current_col = 0; + + for (i, c) in source.chars().enumerate() { + if i == range.start { + start_line = end_line; + start_col = current_col; + } + if i == range.end { + end_line = end_line; + end_col = current_col; + break; + } + if c == '\n' { + current_col = 0; + end_line += 1; + } else { + current_col += 1; + } + } + + SpanLocation { + start_line, + start_col, + end_line, + end_col, + } + } +} diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index 00f9322f0..b18519ebc 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -38,7 +38,7 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { if has_encode_decode && !has_freeze_struct { self.errors.push(syn::Error::new_spanned( - &node.ident, + &node, "Struct with Encode/Decode derive must also have #[freeze_struct(..)] attribute.", )); } From dad50ac80d64be0c95dc67facde68da9e4c782c1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 7 Aug 2024 17:43:07 -0400 Subject: [PATCH 160/269] ALMOST --- build.rs | 23 ++++++++++--------- lints/dummy_lint.rs | 4 +++- lints/lint.rs | 4 +++- lints/mod.rs | 41 ++++++++-------------------------- lints/require_freeze_struct.rs | 14 ++++++++---- 5 files changed, 38 insertions(+), 48 deletions(-) diff --git a/build.rs b/build.rs index 14684b5eb..fb8f2f50a 100644 --- a/build.rs +++ b/build.rs @@ -2,6 +2,7 @@ use rayon::prelude::*; use std::env; use std::fs; use std::path::{Path, PathBuf}; +use std::str::FromStr; use std::sync::mpsc::channel; use syn::Result; use walkdir::WalkDir; @@ -26,7 +27,7 @@ fn main() { let Ok(content) = fs::read_to_string(&file) else { return; }; - let Ok(parsed_file) = syn::parse_file(&content) else { + let Ok(parsed_file) = proc_macro2::TokenStream::from_str(&content) else { return; }; @@ -34,7 +35,14 @@ fn main() { let Err(error) = result else { return; }; - tx.send((error, file.clone(), content.to_string())).unwrap(); + let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); + let loc = error.span().location(); + let file_path = relative_path.display(); + tx.send(format!( + "cargo:warning={}:{}:{}: {} (ends at {}:{})", + file_path, loc.start_line, loc.start_col, error, loc.end_line, loc.end_col + )) + .unwrap(); }; track_lint(DummyLint::lint(&parsed_file)); @@ -44,15 +52,10 @@ fn main() { // Collect and print all errors after the parallel processing is done drop(tx); // Close the sending end of the channel - for (error, file, content) in rx { - let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); - let loc = error.span().location(&content); - let file_path = relative_path.display(); - println!( - "cargo:warning={}:{}:{}: {} (ends at {}:{})", - file_path, loc.start_line, loc.start_col, error, loc.end_line, loc.end_col - ); + for (error) in rx { + println!("{error}"); } + panic!("hey"); } /// Recursively collects all Rust files in the given directory diff --git a/lints/dummy_lint.rs b/lints/dummy_lint.rs index 5364f2741..3c046f4a2 100644 --- a/lints/dummy_lint.rs +++ b/lints/dummy_lint.rs @@ -1,9 +1,11 @@ +use proc_macro2::TokenStream; + use super::*; pub struct DummyLint; impl Lint for DummyLint { - fn lint(_source: &File) -> Result<()> { + fn lint(_source: &TokenStream) -> Result<()> { Ok(()) } } diff --git a/lints/lint.rs b/lints/lint.rs index 16ce15f5d..1cd4c9721 100644 --- a/lints/lint.rs +++ b/lints/lint.rs @@ -1,3 +1,5 @@ +use proc_macro2::TokenStream; + use super::*; /// A trait that defines custom lints that can be run within our workspace. @@ -7,5 +9,5 @@ use super::*; /// there are no errors. pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. - fn lint(source: &File) -> Result<()>; + fn lint(source: &TokenStream) -> Result<()>; } diff --git a/lints/mod.rs b/lints/mod.rs index 91aa89ced..e5a77fc3b 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -29,42 +29,19 @@ impl Default for SpanLocation { } pub trait SpanHack { - fn location(&self, source: &str) -> SpanLocation; + fn location(&self) -> SpanLocation; } impl SpanHack for proc_macro2::Span { - fn location(&self, source: &str) -> SpanLocation { - let range = self.byte_range(); - - let mut start_line = 1; - let mut start_col = 0; - let mut end_line = 1; - let mut end_col = 0; - let mut current_col = 0; - - for (i, c) in source.chars().enumerate() { - if i == range.start { - start_line = end_line; - start_col = current_col; - } - if i == range.end { - end_line = end_line; - end_col = current_col; - break; - } - if c == '\n' { - current_col = 0; - end_line += 1; - } else { - current_col += 1; - } - } - + fn location(&self) -> SpanLocation { + //println!("{:#?}", self); + let start = self.start(); + let end = self.end(); SpanLocation { - start_line, - start_col, - end_line, - end_col, + start_line: start.line, + start_col: start.column, + end_line: end.line, + end_col: end.column, } } } diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index b18519ebc..cdc190143 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -1,14 +1,20 @@ use super::*; +use proc_macro2::TokenStream; use syn::parse_quote; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::{visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, Result, Token}; pub struct RequireFreezeStruct; impl Lint for RequireFreezeStruct { - fn lint(source: &syn::File) -> Result<()> { + fn lint(source: &TokenStream) -> Result<()> { let mut visitor = EncodeDecodeVisitor::default(); - visitor.visit_file(source); + + //println!("{:#?}", source.span()); + let file = syn::parse2::(source.clone()).unwrap(); + //println!("{:#?}", file.span()); + visitor.visit_file(&file); if !visitor.errors.is_empty() { for error in visitor.errors { @@ -37,8 +43,8 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { }); if has_encode_decode && !has_freeze_struct { - self.errors.push(syn::Error::new_spanned( - &node, + self.errors.push(syn::Error::new( + node.span(), "Struct with Encode/Decode derive must also have #[freeze_struct(..)] attribute.", )); } From 303db90596284fee336e3665b0d5395889cf5cdb Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 7 Aug 2024 23:28:30 -0400 Subject: [PATCH 161/269] add new proxy types --- runtime/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d047288dd..029a15687 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -620,6 +620,8 @@ pub enum ProxyType { Owner, // Subnet owner Calls NonCritical, NonTransfer, + Transfer, + SmallTransfer, Senate, NonFungibile, // Nothing involving moving TAO Triumvirate, @@ -627,6 +629,8 @@ pub enum ProxyType { Staking, Registration, } +// Transfers below SMALL_TRANSFER_LIMIT are considered small transfers +pub const SMALL_TRANSFER_LIMIT: Balance = 500_000_000; // 0.5 TAO impl Default for ProxyType { fn default() -> Self { Self::Any @@ -645,6 +649,22 @@ impl InstanceFilter for ProxyType { | RuntimeCall::SubtensorModule(pallet_subtensor::Call::burned_register { .. }) | RuntimeCall::SubtensorModule(pallet_subtensor::Call::root_register { .. }) ), + ProxyType::Transfer => matches!( + c, + RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { .. }) + | RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) + | RuntimeCall::Balances(pallet_balances::Call::transfer_all { .. }) + ), + ProxyType::SmallTransfer => match c { + RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { + value, .. + }) => *value < SMALL_TRANSFER_LIMIT, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + value, + .. + }) => *value < SMALL_TRANSFER_LIMIT, + _ => false, + }, ProxyType::Owner => matches!(c, RuntimeCall::AdminUtils(..)), ProxyType::NonCritical => !matches!( c, From 8a86e360348c0c483d2a9c49794eeea76f714760 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 7 Aug 2024 23:42:27 -0400 Subject: [PATCH 162/269] add supersets --- runtime/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 029a15687..5ed1131af 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -701,8 +701,12 @@ impl InstanceFilter for ProxyType { (x, y) if x == y => true, (ProxyType::Any, _) => true, (_, ProxyType::Any) => false, - (ProxyType::NonTransfer, _) => true, + (ProxyType::NonTransfer, _) => { + // NonTransfer is NOT a superset of Transfer or SmallTransfer + !matches!(o, ProxyType::Transfer | ProxyType::SmallTransfer) + } (ProxyType::Governance, ProxyType::Triumvirate | ProxyType::Senate) => true, + (ProxyType::Transfer, ProxyType::SmallTransfer) => true, _ => false, } } From 43dbb6c9e8affb6aa6c33bcbed6560beee5f6794 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 7 Aug 2024 23:42:31 -0400 Subject: [PATCH 163/269] add test --- runtime/tests/pallet_proxy.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs index 796dfc471..eea250938 100644 --- a/runtime/tests/pallet_proxy.rs +++ b/runtime/tests/pallet_proxy.rs @@ -200,3 +200,30 @@ fn test_proxy_pallet() { } } } + +#[test] +fn test_non_transfer_cannot_transfer() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(AccountId::from(ACCOUNT)), + AccountId::from(DELEGATE).into(), + ProxyType::NonTransfer, + 0 + )); + + let call = call_transfer(); + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(AccountId::from(DELEGATE)), + AccountId::from(ACCOUNT).into(), + None, + Box::new(call.clone()), + )); + + System::assert_last_event( + pallet_proxy::Event::ProxyExecuted { + result: Err(SystemError::CallFiltered.into()), + } + .into(), + ); + }); +} From e1ba277e4e004942f1412581340fb996196ae29b Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 00:23:10 -0400 Subject: [PATCH 164/269] clean up --- build.rs | 11 +++++----- lints/mod.rs | 39 +--------------------------------- lints/require_freeze_struct.rs | 9 ++------ 3 files changed, 9 insertions(+), 50 deletions(-) diff --git a/build.rs b/build.rs index fb8f2f50a..587817090 100644 --- a/build.rs +++ b/build.rs @@ -36,11 +36,13 @@ fn main() { return; }; let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); - let loc = error.span().location(); + let loc = error.span().start(); let file_path = relative_path.display(); + // note that spans can't go across thread boundaries without losing their location + // info so we we serialize here and send a String tx.send(format!( - "cargo:warning={}:{}:{}: {} (ends at {}:{})", - file_path, loc.start_line, loc.start_col, error, loc.end_line, loc.end_col + "cargo:warning={}:{}:{}: {}", + file_path, loc.line, loc.column, error, )) .unwrap(); }; @@ -52,10 +54,9 @@ fn main() { // Collect and print all errors after the parallel processing is done drop(tx); // Close the sending end of the channel - for (error) in rx { + for error in rx { println!("{error}"); } - panic!("hey"); } /// Recursively collects all Rust files in the given directory diff --git a/lints/mod.rs b/lints/mod.rs index e5a77fc3b..741278ab3 100644 --- a/lints/mod.rs +++ b/lints/mod.rs @@ -1,4 +1,4 @@ -use syn::{File, Result}; +use syn::Result; pub mod lint; pub use lint::*; @@ -8,40 +8,3 @@ mod require_freeze_struct; pub use dummy_lint::DummyLint; pub use require_freeze_struct::RequireFreezeStruct; - -#[derive(Copy, Clone, Debug)] -pub struct SpanLocation { - pub start_line: usize, - pub start_col: usize, - pub end_line: usize, - pub end_col: usize, -} - -impl Default for SpanLocation { - fn default() -> Self { - Self { - start_line: 1, - start_col: 0, - end_line: 1, - end_col: 0, - } - } -} - -pub trait SpanHack { - fn location(&self) -> SpanLocation; -} - -impl SpanHack for proc_macro2::Span { - fn location(&self) -> SpanLocation { - //println!("{:#?}", self); - let start = self.start(); - let end = self.end(); - SpanLocation { - start_line: start.line, - start_col: start.column, - end_line: end.line, - end_col: end.column, - } - } -} diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index cdc190143..825ce34e8 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -1,9 +1,6 @@ use super::*; use proc_macro2::TokenStream; -use syn::parse_quote; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, Result, Token}; +use syn::{punctuated::Punctuated, parse_quote, visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, Result, Token}; pub struct RequireFreezeStruct; @@ -11,9 +8,7 @@ impl Lint for RequireFreezeStruct { fn lint(source: &TokenStream) -> Result<()> { let mut visitor = EncodeDecodeVisitor::default(); - //println!("{:#?}", source.span()); let file = syn::parse2::(source.clone()).unwrap(); - //println!("{:#?}", file.span()); visitor.visit_file(&file); if !visitor.errors.is_empty() { @@ -44,7 +39,7 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { if has_encode_decode && !has_freeze_struct { self.errors.push(syn::Error::new( - node.span(), + node.ident.span(), "Struct with Encode/Decode derive must also have #[freeze_struct(..)] attribute.", )); } From 0d486ba8f00dccdeb5064973eb15b6d51821ce2a Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 8 Aug 2024 00:28:34 -0400 Subject: [PATCH 165/269] reorder so enum doesnt change --- runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5ed1131af..75471d164 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -620,14 +620,14 @@ pub enum ProxyType { Owner, // Subnet owner Calls NonCritical, NonTransfer, - Transfer, - SmallTransfer, Senate, NonFungibile, // Nothing involving moving TAO Triumvirate, Governance, // Both above governance Staking, Registration, + Transfer, + SmallTransfer, } // Transfers below SMALL_TRANSFER_LIMIT are considered small transfers pub const SMALL_TRANSFER_LIMIT: Balance = 500_000_000; // 0.5 TAO From 13db9e6b2b65a5a8419ef49086d450e1680593fd Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 01:01:46 -0400 Subject: [PATCH 166/269] add missing freeze_struct to Registration --- pallets/commitments/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 912a474c0..06bafcaac 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -299,6 +299,7 @@ pub struct CommitmentInfo> { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. +#[freeze_struct("632f12850e51c420")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] From 03a85f191dcebe6c107a4016e570fa5db8ea471c Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 7 Aug 2024 03:00:46 +0900 Subject: [PATCH 167/269] Create publish script to publish crates in the correct order --- publish.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 publish.sh diff --git a/publish.sh b/publish.sh new file mode 100644 index 000000000..0e8b75520 --- /dev/null +++ b/publish.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -ex +cargo doc --all-features +cargo test --all-features --workspace +cd support/macros +cargo publish +cd ../.. +cd pallets/commitments +cargo publish +cd .. +cd collective +cargo publish +cd .. +cd registry +cargo publish +cd .. +cd subtensor +cargo publish +cd runtime-api +cargo publish +cd ../.. +cd admin-utils +cargo publish +cd ../.. +cd runtime +cargo publish +cd .. +cd node +cargo publish +echo "published successfully." From c4eeca1fabe75d8a31e16335f6512ec9614a6f60 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 7 Aug 2024 13:32:46 +0900 Subject: [PATCH 168/269] Put publish.sh inside scripts/ --- publish.sh => scripts/publish.sh | 2 -- 1 file changed, 2 deletions(-) rename publish.sh => scripts/publish.sh (85%) diff --git a/publish.sh b/scripts/publish.sh similarity index 85% rename from publish.sh rename to scripts/publish.sh index 0e8b75520..3eb0fc6a5 100644 --- a/publish.sh +++ b/scripts/publish.sh @@ -1,7 +1,5 @@ #!/bin/bash set -ex -cargo doc --all-features -cargo test --all-features --workspace cd support/macros cargo publish cd ../.. From 63666075ee7c71aa0d2045ec9164aa31e5d02291 Mon Sep 17 00:00:00 2001 From: Keith Date: Thu, 8 Aug 2024 17:30:36 +0900 Subject: [PATCH 169/269] Add bump-version binary to help with bumping all crate versions --- Cargo.lock | 9 ++++++ Cargo.toml | 1 + VERSION | 1 + pallets/subtensor/rpc/Cargo.toml | 4 +-- runtime/Cargo.toml | 2 +- support/tools/Cargo.toml | 18 ++++++++++++ support/tools/src/bump_version.rs | 49 +++++++++++++++++++++++++++++++ 7 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 VERSION create mode 100644 support/tools/Cargo.toml create mode 100644 support/tools/src/bump_version.rs diff --git a/Cargo.lock b/Cargo.lock index 4a50f8a12..28f3f212c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9216,6 +9216,15 @@ dependencies = [ "syn 2.0.67", ] +[[package]] +name = "subtensor-tools" +version = "0.1.0" +dependencies = [ + "anyhow", + "semver 1.0.23", + "toml_edit 0.22.14", +] + [[package]] name = "subtle" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 4a7565a01..e8d94e157 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "pallets/commitments", "pallets/subtensor", "runtime", + "support/tools", "support/macros", ] resolver = "2" diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..0c89fc927 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +4.0.0 \ No newline at end of file diff --git a/pallets/subtensor/rpc/Cargo.toml b/pallets/subtensor/rpc/Cargo.toml index db2f5f147..861c313d8 100644 --- a/pallets/subtensor/rpc/Cargo.toml +++ b/pallets/subtensor/rpc/Cargo.toml @@ -26,8 +26,8 @@ sp-runtime = { workspace = true } # local packages -subtensor-custom-rpc-runtime-api = { version = "0.0.2", path = "../runtime-api", default-features = false } -pallet-subtensor = { version = "4.0.0-dev", path = "../../subtensor", default-features = false } +subtensor-custom-rpc-runtime-api = { path = "../runtime-api", default-features = false } +pallet-subtensor = { path = "../../subtensor", default-features = false } [features] default = ["std"] diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 042d0337c..60c8fa22e 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -21,7 +21,7 @@ path = "src/spec_version.rs" [dependencies] subtensor-macros.workspace = true -subtensor-custom-rpc-runtime-api = { version = "0.0.2", path = "../pallets/subtensor/runtime-api", default-features = false } +subtensor-custom-rpc-runtime-api = { path = "../pallets/subtensor/runtime-api", default-features = false } smallvec = { workspace = true } log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ diff --git a/support/tools/Cargo.toml b/support/tools/Cargo.toml new file mode 100644 index 000000000..a640fde54 --- /dev/null +++ b/support/tools/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "subtensor-tools" +version = "0.1.0" +edition = "2021" +license = "MIT" + +description = "support tools for Subtensor" +repository = "https://github.com/opentensor/subtensor" +homepage = "https://bittensor.com" + +[[bin]] +name = "bump-version" +path = "src/bump_version.rs" + +[dependencies] +anyhow = "1.0" +semver = "1.0" +toml_edit = "0.22" diff --git a/support/tools/src/bump_version.rs b/support/tools/src/bump_version.rs new file mode 100644 index 000000000..a806e7f36 --- /dev/null +++ b/support/tools/src/bump_version.rs @@ -0,0 +1,49 @@ +use semver::Version; +use std::{ + fs, + io::{Read, Seek, Write}, + str::FromStr, +}; +use toml_edit::{DocumentMut, Item, Value}; + +const TOML_PATHS: [&str; 9] = [ + "support/macros", + "pallets/commitments", + "pallets/collective", + "pallets/registry", + "pallets/subtensor", + "pallets/subtensor/runtime-api", + "pallets/admin-utils", + "runtime", + "node", +]; + +fn main() -> anyhow::Result<()> { + let mut version_file = fs::File::options().read(true).write(true).open("VERSION")?; + let mut version_str = String::new(); + version_file.read_to_string(&mut version_str)?; + let mut version = Version::parse(&version_str)?; + version.minor = version.minor.saturating_add(1); + + for path in TOML_PATHS { + let cargo_toml_path = format!("{path}/Cargo.toml"); + let mut toml_file = fs::File::options() + .read(true) + .write(true) + .open(&cargo_toml_path)?; + let mut toml_str = String::new(); + toml_file.read_to_string(&mut toml_str)?; + let mut modified_toml_doc = DocumentMut::from_str(&toml_str)?; + + modified_toml_doc["package"]["version"] = Item::Value(Value::from(version.to_string())); + toml_file.set_len(0)?; + toml_file.rewind()?; + toml_file.write_all(modified_toml_doc.to_string().as_bytes())?; + } + + version_file.set_len(0)?; + version_file.rewind()?; + version_file.write_all(version.to_string().as_bytes())?; + + Ok(()) +} From a6d13f62adec87aff6b7598a65103c7a19f19f1d Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 8 Aug 2024 13:55:28 -0400 Subject: [PATCH 170/269] Add short default tempo to fast-blocks feature --- runtime/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index dec65f60f..f5db5d1ed 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -848,6 +848,12 @@ impl pallet_commitments::Config for Runtime { type RateLimit = CommitmentRateLimit; } +#[cfg(not(feature = "fast-blocks"))] +pub const INITIAL_SUBNET_TEMPO: u16 = 99; + +#[cfg(feature = "fast-blocks")] +pub const INITIAL_SUBNET_TEMPO: u16 = 10; + // Configure the pallet subtensor. parameter_types! { pub const SubtensorInitialRho: u16 = 10; @@ -860,7 +866,7 @@ parameter_types! { pub const SubtensorInitialValidatorPruneLen: u64 = 1; pub const SubtensorInitialScalingLawPower: u16 = 50; // 0.5 pub const SubtensorInitialMaxAllowedValidators: u16 = 128; - pub const SubtensorInitialTempo: u16 = 99; + pub const SubtensorInitialTempo: u16 = INITIAL_SUBNET_TEMPO; pub const SubtensorInitialDifficulty: u64 = 10_000_000; pub const SubtensorInitialAdjustmentInterval: u16 = 100; pub const SubtensorInitialAdjustmentAlpha: u64 = 0; // no weight to previous value. From 6a792ce655e2aff22550319c3b2ddd8a4e1bce69 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 15:33:59 -0400 Subject: [PATCH 171/269] add missing freeze_structs + confirm detection issue with crate::freeze_struct --- pallets/registry/src/types.rs | 1 + pallets/subtensor/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs index 0badd5669..58cc5ed19 100644 --- a/pallets/registry/src/types.rs +++ b/pallets/registry/src/types.rs @@ -367,6 +367,7 @@ impl> IdentityInfo { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. +#[freeze_struct("797b69e82710bb21")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b305661f7..e2d194401 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -59,7 +59,6 @@ extern crate alloc; #[import_section(config::config)] #[frame_support::pallet] pub mod pallet { - use crate::migrations; use frame_support::{ dispatch::GetDispatchInfo, @@ -96,6 +95,7 @@ pub mod pallet { pub type AxonInfoOf = AxonInfo; /// Data structure for Axon information. + #[crate::freeze_struct("3545cfb0cac4c1f5")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct AxonInfo { /// Axon serving block. From b3485c94ac57e689f472ad9a604409f1aa740b82 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 15:37:02 -0400 Subject: [PATCH 172/269] fix detection issue --- lints/require_freeze_struct.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lints/require_freeze_struct.rs b/lints/require_freeze_struct.rs index 825ce34e8..0dae59262 100644 --- a/lints/require_freeze_struct.rs +++ b/lints/require_freeze_struct.rs @@ -1,6 +1,9 @@ use super::*; use proc_macro2::TokenStream; -use syn::{punctuated::Punctuated, parse_quote, visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, Result, Token}; +use syn::{ + parse_quote, punctuated::Punctuated, visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, + Result, Token, +}; pub struct RequireFreezeStruct; @@ -50,7 +53,10 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { fn is_freeze_struct(attr: &Attribute) -> bool { if let Meta::List(meta_list) = &attr.meta { - if meta_list.path.is_ident("freeze_struct") && !meta_list.tokens.is_empty() { + let Some(seg) = meta_list.path.segments.last() else { + return false; + }; + if seg.ident == "freeze_struct" && !meta_list.tokens.is_empty() { return true; } } From a76484c0096b32a8de7442ef140c8aaf8271370a Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 16:06:58 -0400 Subject: [PATCH 173/269] fix rerun-if logic --- build.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/build.rs b/build.rs index 587817090..a1f1a0ddb 100644 --- a/build.rs +++ b/build.rs @@ -11,6 +11,14 @@ mod lints; use lints::*; fn main() { + // need to list all rust directories here + println!("cargo:rerun-if-changed=pallets"); + println!("cargo:rerun-if-changed=node"); + println!("cargo:rerun-if-changed=runtime"); + println!("cargo:rerun-if-changed=lints"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src"); + println!("cargo:rerun-if-changed=support"); // Get the root directory of the workspace let workspace_root = env::var("CARGO_MANIFEST_DIR").unwrap(); let workspace_root = Path::new(&workspace_root); From f704724df5b61a0e9c16971f737a2beb47579c6a Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 16:07:11 -0400 Subject: [PATCH 174/269] add missing freeze_struct to PrometheusInfo --- pallets/subtensor/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index e2d194401..466ecf966 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -118,7 +118,9 @@ pub mod pallet { /// Struct for Prometheus. pub type PrometheusInfoOf = PrometheusInfo; + /// Data structure for Prometheus information. + #[crate::freeze_struct("5dde687e63baf0cd")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct PrometheusInfo { /// Prometheus serving block. @@ -135,7 +137,9 @@ pub mod pallet { /// Struct for Prometheus. pub type ChainIdentityOf = ChainIdentity; + /// Data structure for Prometheus information. + #[crate::freeze_struct("bbfd00438dbe2b58")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct ChainIdentity { /// The name of the chain identity From 955ee84f95a06373df97834b0995eb53eff2365e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 16:42:51 -0400 Subject: [PATCH 175/269] hack: include linting framework in main crate so we can test it --- build.rs | 5 +++-- {lints => src/lints}/dummy_lint.rs | 0 {lints => src/lints}/lint.rs | 0 {lints => src/lints}/mod.rs | 0 {lints => src/lints}/require_freeze_struct.rs | 0 src/mod.rs | 1 + tests/lint_tests.rs | 1 + 7 files changed, 5 insertions(+), 2 deletions(-) rename {lints => src/lints}/dummy_lint.rs (100%) rename {lints => src/lints}/lint.rs (100%) rename {lints => src/lints}/mod.rs (100%) rename {lints => src/lints}/require_freeze_struct.rs (100%) create mode 100644 src/mod.rs create mode 100644 tests/lint_tests.rs diff --git a/build.rs b/build.rs index a1f1a0ddb..74cc1a9fc 100644 --- a/build.rs +++ b/build.rs @@ -7,8 +7,9 @@ use std::sync::mpsc::channel; use syn::Result; use walkdir::WalkDir; -mod lints; -use lints::*; +// HACK: let's us have tests for our linting framework but still be part of the build script +mod src; +use src::lints::*; fn main() { // need to list all rust directories here diff --git a/lints/dummy_lint.rs b/src/lints/dummy_lint.rs similarity index 100% rename from lints/dummy_lint.rs rename to src/lints/dummy_lint.rs diff --git a/lints/lint.rs b/src/lints/lint.rs similarity index 100% rename from lints/lint.rs rename to src/lints/lint.rs diff --git a/lints/mod.rs b/src/lints/mod.rs similarity index 100% rename from lints/mod.rs rename to src/lints/mod.rs diff --git a/lints/require_freeze_struct.rs b/src/lints/require_freeze_struct.rs similarity index 100% rename from lints/require_freeze_struct.rs rename to src/lints/require_freeze_struct.rs diff --git a/src/mod.rs b/src/mod.rs new file mode 100644 index 000000000..2d9270d07 --- /dev/null +++ b/src/mod.rs @@ -0,0 +1 @@ +pub mod lints; diff --git a/tests/lint_tests.rs b/tests/lint_tests.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/tests/lint_tests.rs @@ -0,0 +1 @@ + From 19aa49fb1b19a446c8819301a71175ad482a27b7 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 17:04:54 -0400 Subject: [PATCH 176/269] refactor to enable testing, separate linting crate --- Cargo.lock | 10 ++++++++++ Cargo.toml | 7 ++++++- build.rs | 4 +--- src/mod.rs | 1 - support/linting/Cargo.toml | 12 ++++++++++++ {src/lints => support/linting/src}/dummy_lint.rs | 0 src/lints/mod.rs => support/linting/src/lib.rs | 0 {src/lints => support/linting/src}/lint.rs | 0 .../linting/src}/require_freeze_struct.rs | 0 tests/lint_tests.rs | 1 - 10 files changed, 29 insertions(+), 6 deletions(-) delete mode 100644 src/mod.rs create mode 100644 support/linting/Cargo.toml rename {src/lints => support/linting/src}/dummy_lint.rs (100%) rename src/lints/mod.rs => support/linting/src/lib.rs (100%) rename {src/lints => support/linting/src}/lint.rs (100%) rename {src/lints => support/linting/src}/require_freeze_struct.rs (100%) delete mode 100644 tests/lint_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 43a041c2f..417aeeca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4114,6 +4114,15 @@ dependencies = [ "nalgebra", ] +[[package]] +name = "linting" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -9186,6 +9195,7 @@ dependencies = [ name = "subtensor" version = "0.1.0" dependencies = [ + "linting", "node-subtensor", "node-subtensor-runtime", "pallet-commitments", diff --git a/Cargo.toml b/Cargo.toml index dba517984..d93e99505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } subtensor-macros = { path = "support/macros", version = "0.1.0" } [build-dependencies] +linting = { path = "support/linting", version = "0.1.0" } syn.workspace = true quote.workspace = true proc-macro2.workspace = true @@ -28,8 +29,12 @@ members = [ "node", "pallets/commitments", "pallets/subtensor", + "pallets/admin-utils", + "pallets/collective", + "pallets/registry", "runtime", "support/macros", + "support/linting", ] resolver = "2" @@ -61,7 +66,7 @@ serde_json = { version = "1.0.116", default-features = false } serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } -syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } +syn = { version = "2", features = ["full", "visit-mut", "visit", "extra-traits", "parsing"] } quote = "1" proc-macro2 = { version = "1", features = ["span-locations"] } walkdir = "2" diff --git a/build.rs b/build.rs index 74cc1a9fc..b13b5bb6b 100644 --- a/build.rs +++ b/build.rs @@ -7,9 +7,7 @@ use std::sync::mpsc::channel; use syn::Result; use walkdir::WalkDir; -// HACK: let's us have tests for our linting framework but still be part of the build script -mod src; -use src::lints::*; +use linting::*; fn main() { // need to list all rust directories here diff --git a/src/mod.rs b/src/mod.rs deleted file mode 100644 index 2d9270d07..000000000 --- a/src/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod lints; diff --git a/support/linting/Cargo.toml b/support/linting/Cargo.toml new file mode 100644 index 000000000..f76b638e0 --- /dev/null +++ b/support/linting/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "linting" +version = "0.1.0" +edition = "2021" + +[dependencies] +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true + +[lints] +workspace = true diff --git a/src/lints/dummy_lint.rs b/support/linting/src/dummy_lint.rs similarity index 100% rename from src/lints/dummy_lint.rs rename to support/linting/src/dummy_lint.rs diff --git a/src/lints/mod.rs b/support/linting/src/lib.rs similarity index 100% rename from src/lints/mod.rs rename to support/linting/src/lib.rs diff --git a/src/lints/lint.rs b/support/linting/src/lint.rs similarity index 100% rename from src/lints/lint.rs rename to support/linting/src/lint.rs diff --git a/src/lints/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs similarity index 100% rename from src/lints/require_freeze_struct.rs rename to support/linting/src/require_freeze_struct.rs diff --git a/tests/lint_tests.rs b/tests/lint_tests.rs deleted file mode 100644 index 8b1378917..000000000 --- a/tests/lint_tests.rs +++ /dev/null @@ -1 +0,0 @@ - From 2abb31936ea035d0a0a62b5288ab28572dd9a01b Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 17:30:40 -0400 Subject: [PATCH 177/269] tests for freeze struct lint --- Cargo.lock | 20 ++-- Cargo.toml | 2 +- build.rs | 2 +- support/linting/Cargo.toml | 2 +- support/linting/src/require_freeze_struct.rs | 118 +++++++++++++++++++ 5 files changed, 131 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 417aeeca9..350831723 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4114,15 +4114,6 @@ dependencies = [ "nalgebra", ] -[[package]] -name = "linting" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.71", -] - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -9195,7 +9186,6 @@ dependencies = [ name = "subtensor" version = "0.1.0" dependencies = [ - "linting", "node-subtensor", "node-subtensor-runtime", "pallet-commitments", @@ -9203,6 +9193,7 @@ dependencies = [ "proc-macro2", "quote", "rayon", + "subtensor-linting", "subtensor-macros", "syn 2.0.71", "walkdir", @@ -9233,6 +9224,15 @@ dependencies = [ "sp-api", ] +[[package]] +name = "subtensor-linting" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "subtensor-macros" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d93e99505..36a87755b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } subtensor-macros = { path = "support/macros", version = "0.1.0" } [build-dependencies] -linting = { path = "support/linting", version = "0.1.0" } +subtensor-linting = { path = "support/linting", version = "0.1.0" } syn.workspace = true quote.workspace = true proc-macro2.workspace = true diff --git a/build.rs b/build.rs index b13b5bb6b..780b43cc1 100644 --- a/build.rs +++ b/build.rs @@ -7,7 +7,7 @@ use std::sync::mpsc::channel; use syn::Result; use walkdir::WalkDir; -use linting::*; +use subtensor_linting::*; fn main() { // need to list all rust directories here diff --git a/support/linting/Cargo.toml b/support/linting/Cargo.toml index f76b638e0..1e37d8163 100644 --- a/support/linting/Cargo.toml +++ b/support/linting/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "linting" +name = "subtensor-linting" version = "0.1.0" edition = "2021" diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 0dae59262..deafc93cb 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -75,3 +75,121 @@ fn is_derive_encode_or_decode(attr: &Attribute) -> bool { } false } + +#[cfg(test)] +mod tests { + use super::*; + + fn lint_struct(input: &str) -> Result<()> { + let item_struct: ItemStruct = syn::parse_str(input).unwrap(); + let mut visitor = EncodeDecodeVisitor::default(); + visitor.visit_item_struct(&item_struct); + if visitor.errors.is_empty() { + Ok(()) + } else { + Err(visitor.errors[0].clone()) + } + } + + #[test] + fn test_no_attributes() { + let input = r#" + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_freeze_struct_only() { + let input = r#" + #[freeze_struct("12345")] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_encode_only() { + let input = r#" + #[derive(Encode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_decode_only() { + let input = r#" + #[derive(Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_encode_and_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Encode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_decode_and_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_encode_decode_without_freeze_struct() { + let input = r#" + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } + + #[test] + fn test_encode_decode_with_freeze_struct() { + let input = r#" + #[freeze_struct("12345")] + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_ok()); + } + + #[test] + fn test_temporary_freeze_struct() { + let input = r#" + #[freeze_struct] + #[derive(Encode, Decode)] + pub struct Test { + field: u32, + } + "#; + assert!(lint_struct(input).is_err()); + } +} From c34d72b23579c611a3368ae2932604353794f9b6 Mon Sep 17 00:00:00 2001 From: VectorChat Date: Thu, 8 Aug 2024 16:41:13 -0500 Subject: [PATCH 178/269] neuron pruning changes, initial tests --- pallets/subtensor/src/registration.rs | 80 ++++++++--------- pallets/subtensor/src/utils.rs | 7 ++ pallets/subtensor/tests/registration.rs | 115 ++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 44 deletions(-) diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 6b73f2fc3..1ece0e9ca 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -423,65 +423,57 @@ impl Pallet { } /// Determine which peer to prune from the network by finding the element with the lowest pruning score out of - /// immunity period. If all neurons are in immunity period, return node with lowest prunning score. - /// This function will always return an element to prune. + /// immunity period. If there is a tie for lowest pruning score, the neuron registered earliest is pruned. + /// If all neurons are in immunity period, the neuron with the lowest pruning score is pruned. If there is a tie for + /// the lowest pruning score, the immune neuron registered earliest is pruned. + /// Ties for earliest registration are broken by the neuron with the lowest uid. pub fn get_neuron_to_prune(netuid: u16) -> u16 { let mut min_score: u16 = u16::MAX; - let mut min_score_in_immunity_period = u16::MAX; - let mut uid_with_min_score = 0; - let mut uid_with_min_score_in_immunity_period: u16 = 0; + let mut min_score_in_immunity: u16 = u16::MAX; + let mut earliest_registration: u64 = u64::MAX; + let mut earliest_registration_in_immunity: u64 = u64::MAX; + let mut uid_to_prune: u16 = 0; + let mut uid_to_prune_in_immunity: u16 = 0; + let mut found_non_immune = false; let neurons_n = Self::get_subnetwork_n(netuid); if neurons_n == 0 { return 0; // If there are no neurons in this network. } - let current_block: u64 = Self::get_current_block_as_u64(); - let immunity_period: u64 = Self::get_immunity_period(netuid) as u64; - for neuron_uid_i in 0..neurons_n { - let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid_i); + for neuron_uid in 0..neurons_n { + let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid); let block_at_registration: u64 = - Self::get_neuron_block_at_registration(netuid, neuron_uid_i); - #[allow(clippy::comparison_chain)] - if min_score == pruning_score { - if current_block.saturating_sub(block_at_registration) < immunity_period { - //neuron is in immunity period - if min_score_in_immunity_period > pruning_score { - min_score_in_immunity_period = pruning_score; - uid_with_min_score_in_immunity_period = neuron_uid_i; - } - } else { - uid_with_min_score = neuron_uid_i; + Self::get_neuron_block_at_registration(netuid, neuron_uid); + let is_immune = Self::get_neuron_is_immune(netuid, neuron_uid); + + if is_immune { + if pruning_score < min_score_in_immunity + || (pruning_score == min_score_in_immunity + && block_at_registration < earliest_registration_in_immunity) + { + min_score_in_immunity = pruning_score; + earliest_registration_in_immunity = block_at_registration; + uid_to_prune_in_immunity = neuron_uid; } - } - // Find min pruning score. - else if min_score > pruning_score { - if current_block.saturating_sub(block_at_registration) < immunity_period { - //neuron is in immunity period - if min_score_in_immunity_period > pruning_score { - min_score_in_immunity_period = pruning_score; - uid_with_min_score_in_immunity_period = neuron_uid_i; - } - } else { + } else { + found_non_immune = true; + if pruning_score < min_score + || (pruning_score == min_score && block_at_registration < earliest_registration) + { min_score = pruning_score; - uid_with_min_score = neuron_uid_i; + earliest_registration = block_at_registration; + uid_to_prune = neuron_uid; } } } - if min_score == u16::MAX { - //all neuorns are in immunity period - Self::set_pruning_score_for_uid( - netuid, - uid_with_min_score_in_immunity_period, - u16::MAX, - ); - uid_with_min_score_in_immunity_period + + if found_non_immune { + Self::set_pruning_score_for_uid(netuid, uid_to_prune, u16::MAX); + uid_to_prune } else { - // We replace the pruning score here with u16 max to ensure that all peers always have a - // pruning score. In the event that every peer has been pruned this function will prune - // the last element in the network continually. - Self::set_pruning_score_for_uid(netuid, uid_with_min_score, u16::MAX); - uid_with_min_score + Self::set_pruning_score_for_uid(netuid, uid_to_prune_in_immunity, u16::MAX); + uid_to_prune_in_immunity } } diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index c61133e94..d12a8a01a 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -461,6 +461,13 @@ impl Pallet { Self::deposit_event(Event::ImmunityPeriodSet(netuid, immunity_period)); } + pub fn get_neuron_is_immune(netuid: u16, uid: u16) -> bool { + let registered_at = Self::get_neuron_block_at_registration(netuid, uid); + let current_block = Self::get_current_block_as_u64(); + let immunity_period = Self::get_immunity_period(netuid); + current_block.saturating_sub(registered_at) < u64::from(immunity_period) + } + pub fn get_min_allowed_weights(netuid: u16) -> u16 { MinAllowedWeights::::get(netuid) } diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 7d6e8ea65..98963f61c 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -1,6 +1,9 @@ #![allow(clippy::unwrap_used)] +use std::u16; + use frame_support::traits::Currency; +use substrate_fixed::types::extra::True; use crate::mock::*; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; @@ -538,6 +541,118 @@ fn test_burn_adjustment() { }); } +#[test] +fn test_burn_registration_pruning_scenarios() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let tempo: u16 = 13; + let burn_cost = 1000; + let coldkey_account_id = U256::from(667); + let max_allowed_uids = 6; + let immunity_period = 5000; + + SubtensorModule::set_burn(netuid, burn_cost); + SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids); + SubtensorModule::set_target_registrations_per_interval(netuid, max_allowed_uids); + SubtensorModule::set_immunity_period(netuid, immunity_period); + + // SubtensorModule::set_immunity_period(netuid, immunity_period); + + add_network(netuid, tempo, 0); + + let mint_balance = burn_cost * u64::from(max_allowed_uids) + 1_000_000_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, mint_balance); + + // Register first half of neurons + for i in 0..3 { + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), + netuid, + U256::from(i) + )); + step_block(1); + } + + // Note: pruning score is set to u16::MAX after getting neuron to prune + + // 1. Test all immune neurons + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), true); + + SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); + SubtensorModule::set_pruning_score_for_uid(netuid, 1, 75); + SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); + + // The immune neuron with the lowest score should be pruned + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2); + + // 2. Test tie-breaking for immune neurons + SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); + SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); + + // Should get the oldest neuron + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + + // 3. Test no immune neurons + step_block(immunity_period); + + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), false); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), false); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), false); + + SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); + SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); + SubtensorModule::set_pruning_score_for_uid(netuid, 2, 75); + + // The non-immune neuron with the lowest score should be pruned + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + + // 4. Test tie-breaking for non-immune neurons + SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); + SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); + + // Should get the oldest non-immune neuron + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + + // 5. Test mixed immunity + // Register second batch of neurons (these will be non-immune) + for i in 3..6 { + assert_ok!(SubtensorModule::burned_register( + <::RuntimeOrigin>::signed(coldkey_account_id), + netuid, + U256::from(i) + )); + step_block(1); + } + + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), true); + + // Set pruning scores for all neurons + SubtensorModule::set_pruning_score_for_uid(netuid, 0, 75); // non-immune + SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); // non-immune + SubtensorModule::set_pruning_score_for_uid(netuid, 2, 60); // non-immune + SubtensorModule::set_pruning_score_for_uid(netuid, 3, 40); // immune + SubtensorModule::set_pruning_score_for_uid(netuid, 4, 55); // immune + SubtensorModule::set_pruning_score_for_uid(netuid, 5, 45); // immune + + // The non-immune neuron with the lowest score should be pruned + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + + // If we remove the lowest non-immune neuron, it should choose the next lowest non-immune + SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2); + + // If we make all non-immune neurons have high scores, it should choose the oldest non-immune neuron + SubtensorModule::set_pruning_score_for_uid(netuid, 0, u16::MAX); + SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX); + SubtensorModule::set_pruning_score_for_uid(netuid, 2, u16::MAX); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 0); + }); +} + #[test] fn test_registration_too_many_registrations_per_block() { new_test_ext(1).execute_with(|| { From 069a33d064e5296b6a1f667879c384bb0d93a57e Mon Sep 17 00:00:00 2001 From: VectorChat Date: Thu, 8 Aug 2024 17:11:51 -0500 Subject: [PATCH 179/269] adding comments --- pallets/subtensor/src/registration.rs | 10 ++++++++++ pallets/subtensor/src/utils.rs | 2 +- pallets/subtensor/tests/registration.rs | 11 ++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pallets/subtensor/src/registration.rs b/pallets/subtensor/src/registration.rs index 1ece0e9ca..8c28c176e 100644 --- a/pallets/subtensor/src/registration.rs +++ b/pallets/subtensor/src/registration.rs @@ -434,6 +434,10 @@ impl Pallet { let mut earliest_registration_in_immunity: u64 = u64::MAX; let mut uid_to_prune: u16 = 0; let mut uid_to_prune_in_immunity: u16 = 0; + + // This boolean is used instead of checking if min_score == u16::MAX, to avoid the case + // where all non-immune neurons have pruning score u16::MAX + // This may be unlikely in practice. let mut found_non_immune = false; let neurons_n = Self::get_subnetwork_n(netuid); @@ -448,6 +452,9 @@ impl Pallet { let is_immune = Self::get_neuron_is_immune(netuid, neuron_uid); if is_immune { + // if the immune neuron has a lower pruning score than the minimum for immune neurons, + // or, if the pruning scores are equal and the immune neuron was registered earlier than the current minimum for immune neurons, + // then update the minimum pruning score and the uid to prune for immune neurons if pruning_score < min_score_in_immunity || (pruning_score == min_score_in_immunity && block_at_registration < earliest_registration_in_immunity) @@ -458,6 +465,9 @@ impl Pallet { } } else { found_non_immune = true; + // if the non-immune neuron has a lower pruning score than the minimum for non-immune neurons, + // or, if the pruning scores are equal and the non-immune neuron was registered earlier than the current minimum for non-immune neurons, + // then update the minimum pruning score and the uid to prune for non-immune neurons if pruning_score < min_score || (pruning_score == min_score && block_at_registration < earliest_registration) { diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index d12a8a01a..f88ef6865 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -460,7 +460,7 @@ impl Pallet { ImmunityPeriod::::insert(netuid, immunity_period); Self::deposit_event(Event::ImmunityPeriodSet(netuid, immunity_period)); } - + /// Check if a neuron is in immunity based on the current block pub fn get_neuron_is_immune(netuid: u16, uid: u16) -> bool { let registered_at = Self::get_neuron_block_at_registration(netuid, uid); let current_block = Self::get_current_block_as_u64(); diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 98963f61c..f644680c3 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -551,13 +551,12 @@ fn test_burn_registration_pruning_scenarios() { let max_allowed_uids = 6; let immunity_period = 5000; + // Initial setup SubtensorModule::set_burn(netuid, burn_cost); SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids); SubtensorModule::set_target_registrations_per_interval(netuid, max_allowed_uids); SubtensorModule::set_immunity_period(netuid, immunity_period); - // SubtensorModule::set_immunity_period(netuid, immunity_period); - add_network(netuid, tempo, 0); let mint_balance = burn_cost * u64::from(max_allowed_uids) + 1_000_000_000; @@ -575,7 +574,7 @@ fn test_burn_registration_pruning_scenarios() { // Note: pruning score is set to u16::MAX after getting neuron to prune - // 1. Test all immune neurons + // 1. Test if all immune neurons assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), true); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), true); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), true); @@ -591,12 +590,13 @@ fn test_burn_registration_pruning_scenarios() { SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); - // Should get the oldest neuron + // Should get the oldest neuron (i.e., neuron that was registered first) assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); - // 3. Test no immune neurons + // 3. Test if no immune neurons step_block(immunity_period); + // ensure all neurons are non-immune assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), false); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), false); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), false); @@ -626,6 +626,7 @@ fn test_burn_registration_pruning_scenarios() { step_block(1); } + // Ensure all new neurons are immune assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), true); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), true); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), true); From 10114250d90bd385d9b5ee480a3524169b00490b Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Thu, 8 Aug 2024 18:15:33 -0400 Subject: [PATCH 180/269] Add short InitialTxChildkeyTakeRateLimit to fast-blocks feature --- runtime/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f5db5d1ed..45ebee849 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -854,6 +854,12 @@ pub const INITIAL_SUBNET_TEMPO: u16 = 99; #[cfg(feature = "fast-blocks")] pub const INITIAL_SUBNET_TEMPO: u16 = 10; +#[cfg(not(feature = "fast-blocks"))] +pub const INITIAL_CHILDKEY_TAKE_RATELIMIT: u64 = 216000; // 30 days at 12 seconds per block + +#[cfg(feature = "fast-blocks")] +pub const INITIAL_CHILDKEY_TAKE_RATELIMIT: u64 = 5; + // Configure the pallet subtensor. parameter_types! { pub const SubtensorInitialRho: u16 = 10; @@ -889,7 +895,7 @@ parameter_types! { pub const SubtensorInitialMaxBurn: u64 = 100_000_000_000; // 100 tao pub const SubtensorInitialTxRateLimit: u64 = 1000; pub const SubtensorInitialTxDelegateTakeRateLimit: u64 = 216000; // 30 days at 12 seconds per block - pub const SubtensorInitialTxChildKeyTakeRateLimit: u64 = 216000; // 30 days at 12 seconds per block + pub const SubtensorInitialTxChildKeyTakeRateLimit: u64 = INITIAL_CHILDKEY_TAKE_RATELIMIT; pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; From 4e976a1f473a628662407468478b75c73dd4e1d0 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 18:24:32 -0400 Subject: [PATCH 181/269] ensure no warnings allowed / re-enable cargo check --workspace job --- .github/workflows/check-rust.yml | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index d36718ef9..cdd9b59a0 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -113,6 +113,54 @@ jobs: - name: cargo clippy --workspace --all-targets -- -D warnings run: cargo clippy --workspace --all-targets -- -D warnings + cargo-check-lints: + name: cargo check + lints + runs-on: SubtensorCI + strategy: + matrix: + rust-branch: + - stable + rust-target: + - x86_64-unknown-linux-gnu + # - x86_64-apple-darwin + os: + - ubuntu-latest + # - macos-latest + include: + - os: ubuntu-latest + # - os: macos-latest + env: + RELEASE_NAME: development + RUSTV: ${{ matrix.rust-branch }} + RUSTFLAGS: -D warnings + RUST_BACKTRACE: full + RUST_BIN_DIR: target/${{ matrix.rust-target }} + SKIP_WASM_BUILD: 1 + TARGET: ${{ matrix.rust-target }} + steps: + - name: Check-out repository under $GITHUB_WORKSPACE + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update && + sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler + + - name: Install Rust ${{ matrix.rust-branch }} + uses: actions-rs/toolchain@v1.0.6 + with: + toolchain: ${{ matrix.rust-branch }} + components: rustfmt, clippy + profile: minimal + + - name: Utilize Shared Rust Cache + uses: Swatinem/rust-cache@v2.2.1 + with: + key: ${{ matrix.os }}-${{ env.RUST_BIN_DIR }} + + - name: cargo check --workspace (no warnings allowed) + run: cargo check --workspace + cargo-clippy-all-features: name: cargo clippy --all-features runs-on: SubtensorCI From 583c6a7f7d9f7b3f28a397692c9ac54158eefff9 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 19:52:53 -0400 Subject: [PATCH 182/269] cargo clippy --fix --workspace --- build.rs | 2 +- support/linting/src/require_freeze_struct.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.rs b/build.rs index 780b43cc1..9c7ce59f8 100644 --- a/build.rs +++ b/build.rs @@ -31,7 +31,7 @@ fn main() { // Parse each rust file with syn and run the linting suite on it in parallel rust_files.par_iter().for_each_with(tx.clone(), |tx, file| { - let Ok(content) = fs::read_to_string(&file) else { + let Ok(content) = fs::read_to_string(file) else { return; }; let Ok(parsed_file) = proc_macro2::TokenStream::from_str(&content) else { diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index deafc93cb..6cf8412ed 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -32,12 +32,12 @@ struct EncodeDecodeVisitor { impl<'ast> Visit<'ast> for EncodeDecodeVisitor { fn visit_item_struct(&mut self, node: &'ast ItemStruct) { let has_encode_decode = node.attrs.iter().any(|attr| { - let result = is_derive_encode_or_decode(attr); - result + + is_derive_encode_or_decode(attr) }); let has_freeze_struct = node.attrs.iter().any(|attr| { - let result = is_freeze_struct(attr); - result + + is_freeze_struct(attr) }); if has_encode_decode && !has_freeze_struct { From 34ca8a9ba28f23630b83a4dcbccffc23be40b57e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 19:53:30 -0400 Subject: [PATCH 183/269] cargo +nightly fmt --- support/linting/src/require_freeze_struct.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 6cf8412ed..52df95854 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -31,14 +31,11 @@ struct EncodeDecodeVisitor { impl<'ast> Visit<'ast> for EncodeDecodeVisitor { fn visit_item_struct(&mut self, node: &'ast ItemStruct) { - let has_encode_decode = node.attrs.iter().any(|attr| { - - is_derive_encode_or_decode(attr) - }); - let has_freeze_struct = node.attrs.iter().any(|attr| { - - is_freeze_struct(attr) - }); + let has_encode_decode = node + .attrs + .iter() + .any(|attr| is_derive_encode_or_decode(attr)); + let has_freeze_struct = node.attrs.iter().any(|attr| is_freeze_struct(attr)); if has_encode_decode && !has_freeze_struct { self.errors.push(syn::Error::new( From 3c4903e3e2ab76a0024f4b0881845bdfb76bd084 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 8 Aug 2024 19:54:32 -0400 Subject: [PATCH 184/269] check that CI fails successfully --- pallets/commitments/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 06bafcaac..334c12565 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -299,7 +299,7 @@ pub struct CommitmentInfo> { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. -#[freeze_struct("632f12850e51c420")] +// #[freeze_struct("632f12850e51c420")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] From b98133d6429be7c31f4f926f8b2a81b6869351f8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 06:05:14 -0400 Subject: [PATCH 185/269] test warning detection in CI --- .github/workflows/check-rust.yml | 2 +- support/linting/src/require_freeze_struct.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index cdd9b59a0..76f03afbd 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -114,7 +114,7 @@ jobs: run: cargo clippy --workspace --all-targets -- -D warnings cargo-check-lints: - name: cargo check + lints + name: no warnings or custom lint failures runs-on: SubtensorCI strategy: matrix: diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 52df95854..861388b56 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -7,6 +7,8 @@ use syn::{ pub struct RequireFreezeStruct; +fn meh() {} + impl Lint for RequireFreezeStruct { fn lint(source: &TokenStream) -> Result<()> { let mut visitor = EncodeDecodeVisitor::default(); From af6435a911f8e22307025c3e206d9aad90ea9b65 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 06:17:28 -0400 Subject: [PATCH 186/269] proper check for custom lint failures --- .github/workflows/check-rust.yml | 18 +++++++++++++++--- support/linting/src/require_freeze_struct.rs | 2 -- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 76f03afbd..803e07ba8 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -114,7 +114,7 @@ jobs: run: cargo clippy --workspace --all-targets -- -D warnings cargo-check-lints: - name: no warnings or custom lint failures + name: check custom lints runs-on: SubtensorCI strategy: matrix: @@ -158,8 +158,20 @@ jobs: with: key: ${{ matrix.os }}-${{ env.RUST_BIN_DIR }} - - name: cargo check --workspace (no warnings allowed) - run: cargo check --workspace + - name: check lints + # regular cargo check is sufficient here as we only need to trigger the build script + # which will nevertheless check the whole workspace + run: | + set -e # Fail the script if any command fails + cargo check 2>&1 | tee build_output.log + warnings=$(grep "cargo:warning=" build_output.log || true) + if [ -n "$warnings" ]; then + echo "The following custom lints have failed ❌:" + echo "$warnings" + exit 1 + else + echo "All custom lints passed ✅" + fi cargo-clippy-all-features: name: cargo clippy --all-features diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 861388b56..52df95854 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -7,8 +7,6 @@ use syn::{ pub struct RequireFreezeStruct; -fn meh() {} - impl Lint for RequireFreezeStruct { fn lint(source: &TokenStream) -> Result<()> { let mut visitor = EncodeDecodeVisitor::default(); From e47f2def1b7885d0b14ceee4dfda8b25de8c82e4 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 06:21:57 -0400 Subject: [PATCH 187/269] clippy fixes --- support/linting/src/require_freeze_struct.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 52df95854..3e9c4e539 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -34,8 +34,8 @@ impl<'ast> Visit<'ast> for EncodeDecodeVisitor { let has_encode_decode = node .attrs .iter() - .any(|attr| is_derive_encode_or_decode(attr)); - let has_freeze_struct = node.attrs.iter().any(|attr| is_freeze_struct(attr)); + .any(is_derive_encode_or_decode); + let has_freeze_struct = node.attrs.iter().any(is_freeze_struct); if has_encode_decode && !has_freeze_struct { self.errors.push(syn::Error::new( From 3398adcaf3f39caddfa2dbc761c2e14527b31c1e Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 06:45:03 -0400 Subject: [PATCH 188/269] tweak --- .github/workflows/check-rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 803e07ba8..5f102d646 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -164,7 +164,7 @@ jobs: run: | set -e # Fail the script if any command fails cargo check 2>&1 | tee build_output.log - warnings=$(grep "cargo:warning=" build_output.log || true) + warnings=$(grep "warning: " build_output.log || true) if [ -n "$warnings" ]; then echo "The following custom lints have failed ❌:" echo "$warnings" From 63da5afa7b3a7560ba2c48a3eeab8e3ec79b1318 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 06:53:06 -0400 Subject: [PATCH 189/269] try again --- .github/workflows/check-rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 5f102d646..bb382a861 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -163,7 +163,7 @@ jobs: # which will nevertheless check the whole workspace run: | set -e # Fail the script if any command fails - cargo check 2>&1 | tee build_output.log + cargo check | tee build_output.log warnings=$(grep "warning: " build_output.log || true) if [ -n "$warnings" ]; then echo "The following custom lints have failed ❌:" From fad9f691bb1b1c92510da32f4447cd7cde4aea08 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:06:09 -0400 Subject: [PATCH 190/269] tweak again --- .github/workflows/check-rust.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index bb382a861..9925bb863 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -163,14 +163,14 @@ jobs: # which will nevertheless check the whole workspace run: | set -e # Fail the script if any command fails - cargo check | tee build_output.log - warnings=$(grep "warning: " build_output.log || true) + cargo check 2>&1 | tee build_output.log + warnings=$(grep "^warning:" build_output.log || true) if [ -n "$warnings" ]; then - echo "The following custom lints have failed ❌:" + echo "Build emitted the following warnings:" echo "$warnings" exit 1 else - echo "All custom lints passed ✅" + echo "No warnings found." fi cargo-clippy-all-features: From 4b79e6141b57df01e18a9cd229b517e54d5d0b3a Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:13:49 -0400 Subject: [PATCH 191/269] try again --- .github/workflows/check-rust.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 9925bb863..b3e656c33 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -162,12 +162,11 @@ jobs: # regular cargo check is sufficient here as we only need to trigger the build script # which will nevertheless check the whole workspace run: | - set -e # Fail the script if any command fails + set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails cargo check 2>&1 | tee build_output.log - warnings=$(grep "^warning:" build_output.log || true) - if [ -n "$warnings" ]; then + if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" - echo "$warnings" + grep "^warning:" build_output.log exit 1 else echo "No warnings found." From a4ac8b64004e17d1cbd8d7141672ee09773da5c6 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:18:44 -0400 Subject: [PATCH 192/269] try echoing the file --- .github/workflows/check-rust.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index b3e656c33..c724ecd1a 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -163,7 +163,8 @@ jobs: # which will nevertheless check the whole workspace run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails - cargo check 2>&1 | tee build_output.log + cargo check 2>&1 | build_output.log + cat build_output.log if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" grep "^warning:" build_output.log From 4021d60cb1f9da283851dcfb8b97d324a3d201d1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:33:38 -0400 Subject: [PATCH 193/269] warning detection should be working in CI now --- .github/workflows/check-rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index c724ecd1a..ce1aa4785 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -163,7 +163,7 @@ jobs: # which will nevertheless check the whole workspace run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails - cargo check 2>&1 | build_output.log + cargo check 2>&1 | tee build_output.log cat build_output.log if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" From dbe2a894643dc6eb298e9e4e3a4c0e099f1512b3 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:45:02 -0400 Subject: [PATCH 194/269] but actually now --- .github/workflows/check-rust.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index ce1aa4785..ffef13274 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -164,11 +164,10 @@ jobs: run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails cargo check 2>&1 | tee build_output.log - cat build_output.log if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" - grep "^warning:" build_output.log - exit 1 + >&2 echo `grep "^warning:" build_output.log` + else echo "No warnings found." fi From fe0d6ad188bf3122fe3674f45825d55ba5e33a97 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 07:52:44 -0400 Subject: [PATCH 195/269] try catting --- .github/workflows/check-rust.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index ffef13274..faba74f8d 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -164,10 +164,10 @@ jobs: run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails cargo check 2>&1 | tee build_output.log + cat build_output.log if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" >&2 echo `grep "^warning:" build_output.log` - else echo "No warnings found." fi From 68ec4c66ae263f1ee343b6b8f752ada467bff9fb Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 9 Aug 2024 16:13:14 +0400 Subject: [PATCH 196/269] feat: bump network max stake --- pallets/admin-utils/tests/mock.rs | 2 +- runtime/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index acb9c8a4a..5575bd560 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -118,7 +118,7 @@ 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 InitialHotkeyEmissionTempo: u64 = 1; - pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO + pub const InitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64, this make the make stake infinity } impl pallet_subtensor::Config for Test { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index dec65f60f..80818f7e4 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -899,7 +899,8 @@ 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 SubtensorInitialHotkeyEmissionTempo: u64 = 7200; // Drain every day. - pub const SubtensorInitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500_000 TAO + pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64, this make the make stake infinity + } impl pallet_subtensor::Config for Runtime { From 9c3690bd94d6eb34382afe4b585e3654084c7846 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 08:23:52 -0400 Subject: [PATCH 197/269] strip color codes --- .github/workflows/check-rust.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index faba74f8d..52032b21e 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -164,7 +164,8 @@ jobs: run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails cargo check 2>&1 | tee build_output.log - cat build_output.log + # Strip ANSI color codes + sed -r "s/\x1B\[[0-9;]*[mK]//g" build_output.log > clean_output.log if grep -q "^warning:" build_output.log; then echo "Build emitted the following warnings:" >&2 echo `grep "^warning:" build_output.log` From 370ca7f6aae429fef45062a850702dc4e655b4dc Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 10:22:29 -0400 Subject: [PATCH 198/269] whoops --- .github/workflows/check-rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 52032b21e..996fd9454 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -166,9 +166,9 @@ jobs: cargo check 2>&1 | tee build_output.log # Strip ANSI color codes sed -r "s/\x1B\[[0-9;]*[mK]//g" build_output.log > clean_output.log - if grep -q "^warning:" build_output.log; then + if grep -q "^warning:" clean_output.log; then echo "Build emitted the following warnings:" - >&2 echo `grep "^warning:" build_output.log` + >&2 echo `grep "^warning:" clean_output.log` else echo "No warnings found." fi From 75df9970cf6b8537e1091ae7c3c8eb587c9be3c8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 10:37:00 -0400 Subject: [PATCH 199/269] working, now proper error message at the end --- .github/workflows/check-rust.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 996fd9454..1d2f686c1 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -169,6 +169,7 @@ jobs: if grep -q "^warning:" clean_output.log; then echo "Build emitted the following warnings:" >&2 echo `grep "^warning:" clean_output.log` + exit "Some custom lints failed, see above for details." else echo "No warnings found." fi From 567696f127963b8c740d5cf7f7dcd17b6e03e19d Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 10:51:38 -0400 Subject: [PATCH 200/269] fix exit status --- .github/workflows/check-rust.yml | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/.github/workflows/check-rust.yml b/.github/workflows/check-rust.yml index 1d2f686c1..797ad4df4 100644 --- a/.github/workflows/check-rust.yml +++ b/.github/workflows/check-rust.yml @@ -122,13 +122,10 @@ jobs: - stable rust-target: - x86_64-unknown-linux-gnu - # - x86_64-apple-darwin + # - x86_64-apple-darwin os: - ubuntu-latest # - macos-latest - include: - - os: ubuntu-latest - # - os: macos-latest env: RELEASE_NAME: development RUSTV: ${{ matrix.rust-branch }} @@ -159,20 +156,10 @@ jobs: key: ${{ matrix.os }}-${{ env.RUST_BIN_DIR }} - name: check lints - # regular cargo check is sufficient here as we only need to trigger the build script - # which will nevertheless check the whole workspace run: | set -o pipefail # Ensure the pipeline fails if any command in the pipeline fails - cargo check 2>&1 | tee build_output.log - # Strip ANSI color codes - sed -r "s/\x1B\[[0-9;]*[mK]//g" build_output.log > clean_output.log - if grep -q "^warning:" clean_output.log; then - echo "Build emitted the following warnings:" - >&2 echo `grep "^warning:" clean_output.log` - exit "Some custom lints failed, see above for details." - else - echo "No warnings found." - fi + cargo check 2>&1 | sed -r "s/\x1B\[[0-9;]*[mK]//g" | tee /dev/tty | grep -q "^warning:" && \ + (echo "Build emitted the following warnings:" >&2 && exit 1) || echo "No warnings found." cargo-clippy-all-features: name: cargo clippy --all-features From 7aa4dc8d83e4cd23265f5971fac1cab2b568a187 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 10:53:47 -0400 Subject: [PATCH 201/269] fix unwrap --- support/linting/src/require_freeze_struct.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 3e9c4e539..aa84b6d5c 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -31,10 +31,7 @@ struct EncodeDecodeVisitor { impl<'ast> Visit<'ast> for EncodeDecodeVisitor { fn visit_item_struct(&mut self, node: &'ast ItemStruct) { - let has_encode_decode = node - .attrs - .iter() - .any(is_derive_encode_or_decode); + let has_encode_decode = node.attrs.iter().any(is_derive_encode_or_decode); let has_freeze_struct = node.attrs.iter().any(is_freeze_struct); if has_encode_decode && !has_freeze_struct { @@ -78,7 +75,7 @@ mod tests { use super::*; fn lint_struct(input: &str) -> Result<()> { - let item_struct: ItemStruct = syn::parse_str(input).unwrap(); + let item_struct: ItemStruct = syn::parse_str(input).expect("should only use on a struct"); let mut visitor = EncodeDecodeVisitor::default(); visitor.visit_item_struct(&item_struct); if visitor.errors.is_empty() { From ae40d0f64e8773f1c1c750e1ff2bdaff3ba38df6 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 9 Aug 2024 18:57:47 +0400 Subject: [PATCH 202/269] chore: add sudo calls for setting min/max childkey takes --- pallets/subtensor/src/macros/dispatches.rs | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index a776cfc4f..2d6c5bde0 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -786,6 +786,53 @@ mod dispatches { Ok(()) } + /// Sets the minimum allowed childkey take. + /// + /// This function can only be called by the root origin. + /// + /// # Arguments: + /// * `origin` - The origin of the call, must be root. + /// * `take` - The new minimum childkey take value. + /// + /// # Errors: + /// * `BadOrigin` - If the origin is not root. + /// + #[pallet::call_index(76)] + #[pallet::weight(( + Weight::from_parts(6_000, 0) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No + ))] + pub fn sudo_set_min_childkey_take(origin: OriginFor, take: u16) -> DispatchResult { + ensure_root(origin)?; + Self::set_min_childkey_take(take); + Ok(()) + } + + /// Sets the maximum allowed childkey take. + /// + /// This function can only be called by the root origin. + /// + /// # Arguments: + /// * `origin` - The origin of the call, must be root. + /// * `take` - The new maximum childkey take value. + /// + /// # Errors: + /// * `BadOrigin` - If the origin is not root. + /// + #[pallet::call_index(77)] + #[pallet::weight(( + Weight::from_parts(6_000, 0) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No + ))] + pub fn sudo_set_max_childkey_take(origin: OriginFor, take: u16) -> DispatchResult { + ensure_root(origin)?; + Self::set_max_childkey_take(take); + Ok(()) + } // ================================== // ==== Parameter Sudo calls ======== // ================================== From 320aecfe83487bafbb64e513ddc59af6dc0e80fc Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 10:58:23 -0400 Subject: [PATCH 203/269] fix clippy warning --- support/linting/src/require_freeze_struct.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index aa84b6d5c..ca6232297 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -78,11 +78,10 @@ mod tests { let item_struct: ItemStruct = syn::parse_str(input).expect("should only use on a struct"); let mut visitor = EncodeDecodeVisitor::default(); visitor.visit_item_struct(&item_struct); - if visitor.errors.is_empty() { - Ok(()) - } else { - Err(visitor.errors[0].clone()) + if let Some(error) = visitor.errors.first() { + return Err(error.clone()); } + Ok(()) } #[test] From 70729713cb059b682d889ee345d3c6c465272422 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 9 Aug 2024 13:02:58 -0400 Subject: [PATCH 204/269] last remaining warning --- pallets/commitments/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/commitments/src/types.rs b/pallets/commitments/src/types.rs index 334c12565..06bafcaac 100644 --- a/pallets/commitments/src/types.rs +++ b/pallets/commitments/src/types.rs @@ -299,7 +299,7 @@ pub struct CommitmentInfo> { /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a /// backwards compatible way through a specialized `Decode` impl. -// #[freeze_struct("632f12850e51c420")] +#[freeze_struct("632f12850e51c420")] #[derive( CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, )] From 15366cf21150064b4bb69d5d76d2f9cbecdfd53c Mon Sep 17 00:00:00 2001 From: VectorChat Date: Fri, 9 Aug 2024 15:41:33 -0500 Subject: [PATCH 205/269] clippy --- pallets/subtensor/tests/registration.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index f644680c3..6a2b9be0b 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -3,7 +3,6 @@ use std::u16; use frame_support::traits::Currency; -use substrate_fixed::types::extra::True; use crate::mock::*; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; @@ -551,6 +550,9 @@ fn test_burn_registration_pruning_scenarios() { let max_allowed_uids = 6; let immunity_period = 5000; + const IS_IMMUNE: bool = true; + const NOT_IMMUNE: bool = false; + // Initial setup SubtensorModule::set_burn(netuid, burn_cost); SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids); @@ -575,9 +577,9 @@ fn test_burn_registration_pruning_scenarios() { // Note: pruning score is set to u16::MAX after getting neuron to prune // 1. Test if all immune neurons - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), true); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), true); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), IS_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), IS_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), IS_IMMUNE); SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); SubtensorModule::set_pruning_score_for_uid(netuid, 1, 75); @@ -597,9 +599,9 @@ fn test_burn_registration_pruning_scenarios() { step_block(immunity_period); // ensure all neurons are non-immune - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), false); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), false); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), false); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), NOT_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), NOT_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), NOT_IMMUNE); SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); @@ -627,9 +629,9 @@ fn test_burn_registration_pruning_scenarios() { } // Ensure all new neurons are immune - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), true); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), true); - assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), true); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), IS_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), IS_IMMUNE); + assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), IS_IMMUNE); // Set pruning scores for all neurons SubtensorModule::set_pruning_score_for_uid(netuid, 0, 75); // non-immune From 681097b529dda69acd91cdbcb3753114fef57be8 Mon Sep 17 00:00:00 2001 From: VectorChat Date: Fri, 9 Aug 2024 21:41:54 -0500 Subject: [PATCH 206/269] more clippy --- pallets/subtensor/tests/registration.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/pallets/subtensor/tests/registration.rs b/pallets/subtensor/tests/registration.rs index 6a2b9be0b..536aa2688 100644 --- a/pallets/subtensor/tests/registration.rs +++ b/pallets/subtensor/tests/registration.rs @@ -1,7 +1,5 @@ #![allow(clippy::unwrap_used)] -use std::u16; - use frame_support::traits::Currency; use crate::mock::*; From 2cb7ef4d8cfdd0e7665296f342b8b87179293159 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 10 Aug 2024 20:24:26 -0400 Subject: [PATCH 207/269] allow multiple linting errors per file --- build.rs | 36 +++++++++++--------- support/linting/src/dummy_lint.rs | 2 +- support/linting/src/lib.rs | 2 -- support/linting/src/lint.rs | 4 +-- support/linting/src/require_freeze_struct.rs | 14 ++++---- 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/build.rs b/build.rs index 9c7ce59f8..cc98ea32d 100644 --- a/build.rs +++ b/build.rs @@ -1,10 +1,10 @@ use rayon::prelude::*; -use std::env; -use std::fs; -use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::mpsc::channel; -use syn::Result; +use std::{ + env, fs, + path::{Path, PathBuf}, + str::FromStr, + sync::mpsc::channel, +}; use walkdir::WalkDir; use subtensor_linting::*; @@ -38,20 +38,22 @@ fn main() { return; }; - let track_lint = |result: Result<()>| { - let Err(error) = result else { + let track_lint = |result: Result| { + let Err(errors) = result else { return; }; let relative_path = file.strip_prefix(workspace_root).unwrap_or(file.as_path()); - let loc = error.span().start(); - let file_path = relative_path.display(); - // note that spans can't go across thread boundaries without losing their location - // info so we we serialize here and send a String - tx.send(format!( - "cargo:warning={}:{}:{}: {}", - file_path, loc.line, loc.column, error, - )) - .unwrap(); + for error in errors { + let loc = error.span().start(); + let file_path = relative_path.display(); + // note that spans can't go across thread boundaries without losing their location + // info so we we serialize here and send a String + tx.send(format!( + "cargo:warning={}:{}:{}: {}", + file_path, loc.line, loc.column, error, + )) + .unwrap(); + } }; track_lint(DummyLint::lint(&parsed_file)); diff --git a/support/linting/src/dummy_lint.rs b/support/linting/src/dummy_lint.rs index 3c046f4a2..2fb0a5c2c 100644 --- a/support/linting/src/dummy_lint.rs +++ b/support/linting/src/dummy_lint.rs @@ -5,7 +5,7 @@ use super::*; pub struct DummyLint; impl Lint for DummyLint { - fn lint(_source: &TokenStream) -> Result<()> { + fn lint(_source: &TokenStream) -> Result { Ok(()) } } diff --git a/support/linting/src/lib.rs b/support/linting/src/lib.rs index 741278ab3..0d0df8a44 100644 --- a/support/linting/src/lib.rs +++ b/support/linting/src/lib.rs @@ -1,5 +1,3 @@ -use syn::Result; - pub mod lint; pub use lint::*; diff --git a/support/linting/src/lint.rs b/support/linting/src/lint.rs index 1cd4c9721..985e2ecc0 100644 --- a/support/linting/src/lint.rs +++ b/support/linting/src/lint.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream; -use super::*; +pub type Result = core::result::Result<(), Vec>; /// A trait that defines custom lints that can be run within our workspace. /// @@ -9,5 +9,5 @@ use super::*; /// there are no errors. pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. - fn lint(source: &TokenStream) -> Result<()>; + fn lint(source: &TokenStream) -> Result; } diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index ca6232297..14569bd2f 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -2,22 +2,20 @@ use super::*; use proc_macro2::TokenStream; use syn::{ parse_quote, punctuated::Punctuated, visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, - Result, Token, + Token, }; pub struct RequireFreezeStruct; impl Lint for RequireFreezeStruct { - fn lint(source: &TokenStream) -> Result<()> { + fn lint(source: &TokenStream) -> Result { let mut visitor = EncodeDecodeVisitor::default(); let file = syn::parse2::(source.clone()).unwrap(); visitor.visit_file(&file); if !visitor.errors.is_empty() { - for error in visitor.errors { - return Err(error); - } + return Err(visitor.errors); } Ok(()) @@ -74,12 +72,12 @@ fn is_derive_encode_or_decode(attr: &Attribute) -> bool { mod tests { use super::*; - fn lint_struct(input: &str) -> Result<()> { + fn lint_struct(input: &str) -> Result { let item_struct: ItemStruct = syn::parse_str(input).expect("should only use on a struct"); let mut visitor = EncodeDecodeVisitor::default(); visitor.visit_item_struct(&item_struct); - if let Some(error) = visitor.errors.first() { - return Err(error.clone()); + if !visitor.errors.is_empty() { + return Err(visitor.errors); } Ok(()) } From 3b8cab18baea2439dd71661f4f0a3c75633ff7db Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 10 Aug 2024 20:34:43 -0400 Subject: [PATCH 208/269] change Lint trait to take a syn::File instead --- build.rs | 5 ++++- support/linting/src/dummy_lint.rs | 4 ++-- support/linting/src/lint.rs | 4 ++-- support/linting/src/require_freeze_struct.rs | 10 ++++------ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/build.rs b/build.rs index cc98ea32d..cc495ec13 100644 --- a/build.rs +++ b/build.rs @@ -34,7 +34,10 @@ fn main() { let Ok(content) = fs::read_to_string(file) else { return; }; - let Ok(parsed_file) = proc_macro2::TokenStream::from_str(&content) else { + let Ok(parsed_tokens) = proc_macro2::TokenStream::from_str(&content) else { + return; + }; + let Ok(parsed_file) = syn::parse2::(parsed_tokens) else { return; }; diff --git a/support/linting/src/dummy_lint.rs b/support/linting/src/dummy_lint.rs index 2fb0a5c2c..1c5e7bc3f 100644 --- a/support/linting/src/dummy_lint.rs +++ b/support/linting/src/dummy_lint.rs @@ -1,11 +1,11 @@ -use proc_macro2::TokenStream; +use syn::File; use super::*; pub struct DummyLint; impl Lint for DummyLint { - fn lint(_source: &TokenStream) -> Result { + fn lint(_source: &File) -> Result { Ok(()) } } diff --git a/support/linting/src/lint.rs b/support/linting/src/lint.rs index 985e2ecc0..3c099d40c 100644 --- a/support/linting/src/lint.rs +++ b/support/linting/src/lint.rs @@ -1,4 +1,4 @@ -use proc_macro2::TokenStream; +use syn::File; pub type Result = core::result::Result<(), Vec>; @@ -9,5 +9,5 @@ pub type Result = core::result::Result<(), Vec>; /// there are no errors. pub trait Lint: Send + Sync { /// Lints the given Rust source file, returning a compile error if any issues are found. - fn lint(source: &TokenStream) -> Result; + fn lint(source: &File) -> Result; } diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 14569bd2f..2fa5db5ac 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -1,18 +1,16 @@ use super::*; -use proc_macro2::TokenStream; use syn::{ - parse_quote, punctuated::Punctuated, visit::Visit, Attribute, ItemStruct, Meta, MetaList, Path, - Token, + parse_quote, punctuated::Punctuated, visit::Visit, Attribute, File, ItemStruct, Meta, MetaList, + Path, Token, }; pub struct RequireFreezeStruct; impl Lint for RequireFreezeStruct { - fn lint(source: &TokenStream) -> Result { + fn lint(source: &File) -> Result { let mut visitor = EncodeDecodeVisitor::default(); - let file = syn::parse2::(source.clone()).unwrap(); - visitor.visit_file(&file); + visitor.visit_file(&source); if !visitor.errors.is_empty() { return Err(visitor.errors); From ee1422ea58dd63e9748d58b3bad4b13882c59fc5 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 10 Aug 2024 20:35:54 -0400 Subject: [PATCH 209/269] cargo clippy --fix --workspace --all-features --- support/linting/src/require_freeze_struct.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/linting/src/require_freeze_struct.rs b/support/linting/src/require_freeze_struct.rs index 2fa5db5ac..8f02e2697 100644 --- a/support/linting/src/require_freeze_struct.rs +++ b/support/linting/src/require_freeze_struct.rs @@ -10,7 +10,7 @@ impl Lint for RequireFreezeStruct { fn lint(source: &File) -> Result { let mut visitor = EncodeDecodeVisitor::default(); - visitor.visit_file(&source); + visitor.visit_file(source); if !visitor.errors.is_empty() { return Err(visitor.errors); From fec737d56497f13bf54533d7c5e2967a2547f0e8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 10 Aug 2024 20:57:20 -0400 Subject: [PATCH 210/269] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d047288dd..507747e8c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -141,7 +141,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: 192, + spec_version: 193, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From bbd1b5fc181e36ceabed90ae1d253fbd9655c49f Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Sat, 10 Aug 2024 22:23:04 -0400 Subject: [PATCH 211/269] fix localnet.sh to use production profile + sane workspace setup --- scripts/localnet.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/localnet.sh b/scripts/localnet.sh index 2856603e0..4c15a3334 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -48,26 +48,26 @@ fi if [[ $BUILD_BINARY == "1" ]]; then echo "*** Building substrate binary..." - cargo build --release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" + cargo build --workspace --profile=production --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" echo "*** Binary compiled" fi echo "*** Building chainspec..." -"$BASE_DIR/target/release/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH +"$BASE_DIR/target/production/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH echo "*** Chainspec built and output to file" if [ $NO_PURGE -eq 1 ]; then echo "*** Purging previous state skipped..." else echo "*** Purging previous state..." - "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 - "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 + "$BASE_DIR/target/production/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 + "$BASE_DIR/target/production/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 echo "*** Previous chainstate purged" fi echo "*** Starting localnet nodes..." alice_start=( - "$BASE_DIR/target/release/node-subtensor" + "$BASE_DIR/target/production/node-subtensor" --base-path /tmp/alice --chain="$FULL_PATH" --alice @@ -80,7 +80,7 @@ alice_start=( ) bob_start=( - "$BASE_DIR"/target/release/node-subtensor + "$BASE_DIR"/target/production/node-subtensor --base-path /tmp/bob --chain="$FULL_PATH" --bob From 9ef28bf25c01967a686eb5e276904c48129f83d2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:34:22 -0700 Subject: [PATCH 212/269] add line --- pallets/subtensor/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6388e0331..9fab3be0e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1356,6 +1356,7 @@ where ..Default::default() }), } + } // NOTE: Add later when we put in a pre and post dispatch step. From 58196b18babd28089f8d1bbdd35dd6d002762828 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:34:30 -0700 Subject: [PATCH 213/269] remove line --- pallets/subtensor/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 9fab3be0e..6388e0331 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1356,7 +1356,6 @@ where ..Default::default() }), } - } // NOTE: Add later when we put in a pre and post dispatch step. From 144be21efcc86577a952a034e6f6259f6f98d945 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 13 Aug 2024 10:21:25 -0400 Subject: [PATCH 214/269] initial script --- .github/workflows/benchmark-weights.yml | 0 scripts/benchmark_all.sh | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .github/workflows/benchmark-weights.yml create mode 100755 scripts/benchmark_all.sh diff --git a/.github/workflows/benchmark-weights.yml b/.github/workflows/benchmark-weights.yml new file mode 100644 index 000000000..e69de29bb diff --git a/scripts/benchmark_all.sh b/scripts/benchmark_all.sh new file mode 100755 index 000000000..277c39e74 --- /dev/null +++ b/scripts/benchmark_all.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +# List of pallets you want to benchmark +pallets=("admin-utils", "collective", "commitments", "registry", "subtensor") + +# Chain spec and output directory +chain_spec="dev" # or your specific chain spec + +for pallet in "${pallets[@]}" +do + echo "Benchmarking $pallet..." + cargo run --profile=production --features=runtime-benchmarks -- benchmark pallet \ + --chain $chain_spec \ + --execution=wasm \ + --wasm-execution=compiled \ + --pallet $pallet \ + --extrinsic '*' \ + --steps 50 \ + --repeat 20 \ + --output "pallets/$pallet/src/$pallet.rs" \ + --template ./.maintain/frame-weight-template.hbs # Adjust this path to your template file +done + +echo "All pallets have been benchmarked and weights updated." From 0323d506fdb3fe0e0808d97a6941274b29b5f4cb Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Tue, 13 Aug 2024 13:25:18 -0400 Subject: [PATCH 215/269] working but running into commit reveal issues --- scripts/benchmark_all.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/benchmark_all.sh b/scripts/benchmark_all.sh index 277c39e74..580e5425e 100755 --- a/scripts/benchmark_all.sh +++ b/scripts/benchmark_all.sh @@ -1,23 +1,23 @@ #!/bin/sh +set -ex # List of pallets you want to benchmark -pallets=("admin-utils", "collective", "commitments", "registry", "subtensor") +pallets=("pallet_subtensor" "pallet_collective" "pallet_commitments" "pallet_registry" "pallet_admin_utils") # Chain spec and output directory -chain_spec="dev" # or your specific chain spec +chain_spec="finney" # or your specific chain spec for pallet in "${pallets[@]}" do echo "Benchmarking $pallet..." - cargo run --profile=production --features=runtime-benchmarks -- benchmark pallet \ + cargo run --profile=production --features=runtime-benchmarks,try-runtime --bin node-subtensor -- benchmark pallet \ --chain $chain_spec \ - --execution=wasm \ --wasm-execution=compiled \ --pallet $pallet \ --extrinsic '*' \ --steps 50 \ - --repeat 20 \ - --output "pallets/$pallet/src/$pallet.rs" \ + --repeat 5 \ + --output "pallets/$pallet/src/weights.rs" \ --template ./.maintain/frame-weight-template.hbs # Adjust this path to your template file done From 46f32879fc4c781299caf1c1fbfd8b29e75e8fdb Mon Sep 17 00:00:00 2001 From: Liam Date: Tue, 13 Aug 2024 20:36:18 +0200 Subject: [PATCH 216/269] debug runtime log level --- pallets/admin-utils/src/lib.rs | 80 +++++++++---------- pallets/subtensor/src/coinbase/root.rs | 22 ++--- pallets/subtensor/src/epoch/run_epoch.rs | 2 +- pallets/subtensor/src/macros/hooks.rs | 2 +- .../subtensor/src/rpc_info/delegate_info.rs | 2 +- pallets/subtensor/src/staking/add_stake.rs | 4 +- .../subtensor/src/staking/become_delegate.rs | 4 +- .../subtensor/src/staking/decrease_take.rs | 4 +- .../subtensor/src/staking/increase_take.rs | 4 +- pallets/subtensor/src/staking/remove_stake.rs | 4 +- pallets/subtensor/src/subnets/registration.rs | 20 ++--- pallets/subtensor/src/subnets/serving.rs | 4 +- pallets/subtensor/src/subnets/weights.rs | 10 +-- pallets/subtensor/src/utils/identity.rs | 2 +- pallets/subtensor/src/utils/misc.rs | 6 +- 15 files changed, 85 insertions(+), 85 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 59b0ed44a..4636bbce0 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -87,7 +87,7 @@ pub mod pallet { T::Aura::change_authorities(new_authorities.clone()); - log::info!("Aura authorities changed: {:?}", new_authorities); + log::debug!("Aura authorities changed: {:?}", new_authorities); // Return a successful DispatchResultWithPostInfo Ok(()) @@ -101,7 +101,7 @@ pub mod pallet { pub fn sudo_set_default_take(origin: OriginFor, default_take: u16) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_max_delegate_take(default_take); - log::info!("DefaultTakeSet( default_take: {:?} ) ", default_take); + log::debug!("DefaultTakeSet( default_take: {:?} ) ", default_take); Ok(()) } @@ -113,7 +113,7 @@ pub mod pallet { pub fn sudo_set_tx_rate_limit(origin: OriginFor, tx_rate_limit: u64) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_tx_rate_limit(tx_rate_limit); - log::info!("TxRateLimitSet( tx_rate_limit: {:?} ) ", tx_rate_limit); + log::debug!("TxRateLimitSet( tx_rate_limit: {:?} ) ", tx_rate_limit); Ok(()) } @@ -130,7 +130,7 @@ pub mod pallet { T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; T::Subtensor::set_serving_rate_limit(netuid, serving_rate_limit); - log::info!( + log::debug!( "ServingRateLimitSet( serving_rate_limit: {:?} ) ", serving_rate_limit ); @@ -154,7 +154,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_min_difficulty(netuid, min_difficulty); - log::info!( + log::debug!( "MinDifficultySet( netuid: {:?} min_difficulty: {:?} ) ", netuid, min_difficulty @@ -179,7 +179,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_max_difficulty(netuid, max_difficulty); - log::info!( + log::debug!( "MaxDifficultySet( netuid: {:?} max_difficulty: {:?} ) ", netuid, max_difficulty @@ -204,7 +204,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_weights_version_key(netuid, weights_version_key); - log::info!( + log::debug!( "WeightsVersionKeySet( netuid: {:?} weights_version_key: {:?} ) ", netuid, weights_version_key @@ -229,7 +229,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_weights_set_rate_limit(netuid, weights_set_rate_limit); - log::info!( + log::debug!( "WeightsSetRateLimitSet( netuid: {:?} weights_set_rate_limit: {:?} ) ", netuid, weights_set_rate_limit @@ -254,7 +254,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_adjustment_interval(netuid, adjustment_interval); - log::info!( + log::debug!( "AdjustmentIntervalSet( netuid: {:?} adjustment_interval: {:?} ) ", netuid, adjustment_interval @@ -285,7 +285,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_adjustment_alpha(netuid, adjustment_alpha); - log::info!( + log::debug!( "AdjustmentAlphaSet( adjustment_alpha: {:?} ) ", adjustment_alpha ); @@ -309,7 +309,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_max_weight_limit(netuid, max_weight_limit); - log::info!( + log::debug!( "MaxWeightLimitSet( netuid: {:?} max_weight_limit: {:?} ) ", netuid, max_weight_limit @@ -334,7 +334,7 @@ pub mod pallet { ); T::Subtensor::set_immunity_period(netuid, immunity_period); - log::info!( + log::debug!( "ImmunityPeriodSet( netuid: {:?} immunity_period: {:?} ) ", netuid, immunity_period @@ -359,7 +359,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_min_allowed_weights(netuid, min_allowed_weights); - log::info!( + log::debug!( "MinAllowedWeightSet( netuid: {:?} min_allowed_weights: {:?} ) ", netuid, min_allowed_weights @@ -387,7 +387,7 @@ pub mod pallet { Error::::MaxAllowedUIdsLessThanCurrentUIds ); T::Subtensor::set_max_allowed_uids(netuid, max_allowed_uids); - log::info!( + log::debug!( "MaxAllowedUidsSet( netuid: {:?} max_allowed_uids: {:?} ) ", netuid, max_allowed_uids @@ -408,7 +408,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_kappa(netuid, kappa); - log::info!("KappaSet( netuid: {:?} kappa: {:?} ) ", netuid, kappa); + log::debug!("KappaSet( netuid: {:?} kappa: {:?} ) ", netuid, kappa); Ok(()) } @@ -425,7 +425,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_rho(netuid, rho); - log::info!("RhoSet( netuid: {:?} rho: {:?} ) ", netuid, rho); + log::debug!("RhoSet( netuid: {:?} rho: {:?} ) ", netuid, rho); Ok(()) } @@ -446,7 +446,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_activity_cutoff(netuid, activity_cutoff); - log::info!( + log::debug!( "ActivityCutoffSet( netuid: {:?} activity_cutoff: {:?} ) ", netuid, activity_cutoff @@ -473,7 +473,7 @@ pub mod pallet { T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; T::Subtensor::set_network_registration_allowed(netuid, registration_allowed); - log::info!( + log::debug!( "NetworkRegistrationAllowed( registration_allowed: {:?} ) ", registration_allowed ); @@ -498,7 +498,7 @@ pub mod pallet { T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; T::Subtensor::set_network_pow_registration_allowed(netuid, registration_allowed); - log::info!( + log::debug!( "NetworkPowRegistrationAllowed( registration_allowed: {:?} ) ", registration_allowed ); @@ -525,7 +525,7 @@ pub mod pallet { netuid, target_registrations_per_interval, ); - log::info!( + log::debug!( "RegistrationPerIntervalSet( netuid: {:?} target_registrations_per_interval: {:?} ) ", netuid, target_registrations_per_interval @@ -550,7 +550,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_min_burn(netuid, min_burn); - log::info!( + log::debug!( "MinBurnSet( netuid: {:?} min_burn: {:?} ) ", netuid, min_burn @@ -575,7 +575,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_max_burn(netuid, max_burn); - log::info!( + log::debug!( "MaxBurnSet( netuid: {:?} max_burn: {:?} ) ", netuid, max_burn @@ -599,7 +599,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_difficulty(netuid, difficulty); - log::info!( + log::debug!( "DifficultySet( netuid: {:?} difficulty: {:?} ) ", netuid, difficulty @@ -628,7 +628,7 @@ pub mod pallet { ); T::Subtensor::set_max_allowed_validators(netuid, max_allowed_validators); - log::info!( + log::debug!( "MaxAllowedValidatorsSet( netuid: {:?} max_allowed_validators: {:?} ) ", netuid, max_allowed_validators @@ -653,7 +653,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_bonds_moving_average(netuid, bonds_moving_average); - log::info!( + log::debug!( "BondsMovingAverageSet( netuid: {:?} bonds_moving_average: {:?} ) ", netuid, bonds_moving_average @@ -678,7 +678,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_max_registrations_per_block(netuid, max_registrations_per_block); - log::info!( + log::debug!( "MaxRegistrationsPerBlock( netuid: {:?} max_registrations_per_block: {:?} ) ", netuid, max_registrations_per_block @@ -702,7 +702,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_subnet_owner_cut(subnet_owner_cut); - log::info!( + log::debug!( "SubnetOwnerCut( subnet_owner_cut: {:?} ) ", subnet_owner_cut ); @@ -725,7 +725,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_network_rate_limit(rate_limit); - log::info!("NetworkRateLimit( rate_limit: {:?} ) ", rate_limit); + log::debug!("NetworkRateLimit( rate_limit: {:?} ) ", rate_limit); Ok(()) } @@ -741,7 +741,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); T::Subtensor::set_tempo(netuid, tempo); - log::info!("TempoSet( netuid: {:?} tempo: {:?} ) ", netuid, tempo); + log::debug!("TempoSet( netuid: {:?} tempo: {:?} ) ", netuid, tempo); Ok(()) } @@ -779,7 +779,7 @@ pub mod pallet { T::Subtensor::set_network_immunity_period(immunity_period); - log::info!("NetworkImmunityPeriod( period: {:?} ) ", immunity_period); + log::debug!("NetworkImmunityPeriod( period: {:?} ) ", immunity_period); Ok(()) } @@ -802,7 +802,7 @@ pub mod pallet { T::Subtensor::set_network_min_lock(lock_cost); - log::info!("NetworkMinLockCost( lock_cost: {:?} ) ", lock_cost); + log::debug!("NetworkMinLockCost( lock_cost: {:?} ) ", lock_cost); Ok(()) } @@ -821,7 +821,7 @@ pub mod pallet { ensure_root(origin)?; T::Subtensor::set_subnet_limit(max_subnets); - log::info!("SubnetLimit( max_subnets: {:?} ) ", max_subnets); + log::debug!("SubnetLimit( max_subnets: {:?} ) ", max_subnets); Ok(()) } @@ -844,7 +844,7 @@ pub mod pallet { T::Subtensor::set_lock_reduction_interval(interval); - log::info!("NetworkLockReductionInterval( interval: {:?} ) ", interval); + log::debug!("NetworkLockReductionInterval( interval: {:?} ) ", interval); Ok(()) } @@ -912,7 +912,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_tx_delegate_take_rate_limit(tx_rate_limit); - log::info!( + log::debug!( "TxRateLimitDelegateTakeSet( tx_delegate_take_rate_limit: {:?} ) ", tx_rate_limit ); @@ -927,7 +927,7 @@ pub mod pallet { pub fn sudo_set_min_delegate_take(origin: OriginFor, take: u16) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_min_delegate_take(take); - log::info!("TxMinDelegateTakeSet( tx_min_delegate_take: {:?} ) ", take); + log::debug!("TxMinDelegateTakeSet( tx_min_delegate_take: {:?} ) ", take); Ok(()) } @@ -942,7 +942,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_target_stakes_per_interval(target_stakes_per_interval); - log::info!( + log::debug!( "TxTargetStakesPerIntervalSet( set_target_stakes_per_interval: {:?} ) ", target_stakes_per_interval ); @@ -967,7 +967,7 @@ pub mod pallet { ); T::Subtensor::set_commit_reveal_weights_interval(netuid, interval); - log::info!( + log::debug!( "SetWeightCommitInterval( netuid: {:?}, interval: {:?} ) ", netuid, interval @@ -993,7 +993,7 @@ pub mod pallet { ); T::Subtensor::set_commit_reveal_weights_enabled(netuid, enabled); - log::info!("ToggleSetWeightsCommitReveal( netuid: {:?} ) ", netuid); + log::debug!("ToggleSetWeightsCommitReveal( netuid: {:?} ) ", netuid); Ok(()) } @@ -1015,7 +1015,7 @@ pub mod pallet { ) -> DispatchResult { T::Subtensor::ensure_subnet_owner_or_root(origin, netuid)?; T::Subtensor::set_liquid_alpha_enabled(netuid, enabled); - log::info!( + log::debug!( "LiquidAlphaEnableToggled( netuid: {:?}, Enabled: {:?} ) ", netuid, enabled @@ -1059,7 +1059,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; T::Subtensor::set_hotkey_emission_tempo(emission_tempo); - log::info!( + log::debug!( "HotkeyEmissionTempoSet( emission_tempo: {:?} )", emission_tempo ); diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 974931e8f..39f745b93 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -486,7 +486,7 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, hotkey @@ -529,7 +529,7 @@ impl Pallet { // --- 12.1.2 Add the new account and make them a member of the Senate. Self::append_neuron(root_netuid, &hotkey, current_block_number); - log::info!("add new neuron: {:?} on uid {:?}", hotkey, subnetwork_uid); + log::debug!("add new neuron: {:?} on uid {:?}", hotkey, subnetwork_uid); } else { // --- 13.1.1 The network is full. Perform replacement. // Find the neuron with the lowest stake value to replace. @@ -562,7 +562,7 @@ impl Pallet { // Replace the neuron account with new information. Self::replace_neuron(root_netuid, lowest_uid, &hotkey, current_block_number); - log::info!( + log::debug!( "replace neuron: {:?} with {:?} on uid {:?}", replaced_hotkey, hotkey, @@ -588,7 +588,7 @@ impl Pallet { RegistrationsThisBlock::::mutate(root_netuid, |val| *val += 1); // --- 16. Log and announce the successful registration. - log::info!( + log::debug!( "RootRegistered(netuid:{:?} uid:{:?} hotkey:{:?})", root_netuid, subnetwork_uid, @@ -622,7 +622,7 @@ impl Pallet { // --- 1. Ensure that the call originates from a signed source and retrieve the caller's account ID (coldkey). let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_root_register( coldkey: {:?}, hotkey: {:?} )", coldkey, hotkey @@ -652,7 +652,7 @@ impl Pallet { } // --- 5. Log and announce the successful Senate adjustment. - log::info!( + log::debug!( "SenateAdjusted(old_hotkey:{:?} hotkey:{:?})", replaced, hotkey @@ -733,7 +733,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // Check the caller's signature. This is the coldkey of a registered account. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_set_root_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", coldkey, netuid, @@ -834,7 +834,7 @@ impl Pallet { Self::set_last_update_for_uid(netuid, neuron_uid, current_block); // Emit the tracking event. - log::info!( + log::debug!( "RootWeightsSet( netuid:{:?}, neuron_uid:{:?} )", netuid, neuron_uid @@ -968,7 +968,7 @@ impl Pallet { SubnetOwner::::insert(netuid_to_register, coldkey); // --- 8. Emit the NetworkAdded event. - log::info!( + log::debug!( "NetworkAdded( netuid:{:?}, modality:{:?} )", netuid_to_register, 0 @@ -1012,7 +1012,7 @@ impl Pallet { Self::remove_network(netuid); // --- 5. Emit the NetworkRemoved event. - log::info!("NetworkRemoved( netuid:{:?} )", netuid); + log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); // --- 6. Return success. @@ -1274,7 +1274,7 @@ impl Pallet { } }); - log::info!("Netuids Order: {:?}", netuids); + log::debug!("Netuids Order: {:?}", netuids); match netuids.last() { Some(netuid) => *netuid, diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 1cb2c3448..c96ea05e2 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -1310,7 +1310,7 @@ impl Pallet { AlphaValues::::insert(netuid, (alpha_low, alpha_high)); - log::info!( + log::debug!( "AlphaValuesSet( netuid: {:?}, AlphaLow: {:?}, AlphaHigh: {:?} ) ", netuid, alpha_low, diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index f2556d506..76f140002 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -19,7 +19,7 @@ mod hooks { match block_step_result { Ok(_) => { // --- If the block step was successful, return the weight. - log::info!("Successfully ran block step."); + log::debug!("Successfully ran block step."); Weight::from_parts(110_634_229_000_u64, 0) .saturating_add(T::DbWeight::get().reads(8304_u64)) .saturating_add(T::DbWeight::get().writes(110_u64)) diff --git a/pallets/subtensor/src/rpc_info/delegate_info.rs b/pallets/subtensor/src/rpc_info/delegate_info.rs index 56b25d230..a41b6e17e 100644 --- a/pallets/subtensor/src/rpc_info/delegate_info.rs +++ b/pallets/subtensor/src/rpc_info/delegate_info.rs @@ -148,7 +148,7 @@ impl Pallet { } } - log::info!( + log::debug!( "Total delegated stake for coldkey {:?}: {}", coldkey, total_delegated diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index f62fd7cd2..c9cbd7e04 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -37,7 +37,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // We check that the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_add_stake( origin:{:?} hotkey:{:?}, stake_to_be_added:{:?} )", coldkey, hotkey, @@ -102,7 +102,7 @@ impl Pallet { stakes_this_interval.saturating_add(1), block, ); - log::info!( + log::debug!( "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", hotkey, actual_amount_to_stake diff --git a/pallets/subtensor/src/staking/become_delegate.rs b/pallets/subtensor/src/staking/become_delegate.rs index 064f47c12..a75716653 100644 --- a/pallets/subtensor/src/staking/become_delegate.rs +++ b/pallets/subtensor/src/staking/become_delegate.rs @@ -34,7 +34,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signuture. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_become_delegate( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, hotkey, @@ -72,7 +72,7 @@ impl Pallet { Self::set_last_tx_block_delegate_take(&coldkey, block); // --- 7. Emit the staking event. - log::info!( + log::debug!( "DelegateAdded( coldkey:{:?}, hotkey:{:?}, take:{:?} )", coldkey, hotkey, diff --git a/pallets/subtensor/src/staking/decrease_take.rs b/pallets/subtensor/src/staking/decrease_take.rs index 9e48bac91..d08c41e6d 100644 --- a/pallets/subtensor/src/staking/decrease_take.rs +++ b/pallets/subtensor/src/staking/decrease_take.rs @@ -34,7 +34,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signature. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_decrease_take( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, hotkey, @@ -58,7 +58,7 @@ impl Pallet { Delegates::::insert(hotkey.clone(), take); // --- 5. Emit the take value. - log::info!( + log::debug!( "TakeDecreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", coldkey, hotkey, diff --git a/pallets/subtensor/src/staking/increase_take.rs b/pallets/subtensor/src/staking/increase_take.rs index aa6dd443c..9362c9378 100644 --- a/pallets/subtensor/src/staking/increase_take.rs +++ b/pallets/subtensor/src/staking/increase_take.rs @@ -37,7 +37,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. We check the coldkey signature. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_increase_take( origin:{:?} hotkey:{:?}, take:{:?} )", coldkey, hotkey, @@ -74,7 +74,7 @@ impl Pallet { Delegates::::insert(hotkey.clone(), take); // --- 7. Emit the take value. - log::info!( + log::debug!( "TakeIncreased( coldkey:{:?}, hotkey:{:?}, take:{:?} )", coldkey, hotkey, diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index a6f3db08d..4118e8d07 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -37,7 +37,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_remove_stake( origin:{:?} hotkey:{:?}, stake_to_be_removed:{:?} )", coldkey, hotkey, @@ -96,7 +96,7 @@ impl Pallet { unstakes_this_interval.saturating_add(1), block, ); - log::info!( + log::debug!( "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", hotkey, stake_to_be_removed diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 9319adcd1..dc321f8dd 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -41,7 +41,7 @@ impl Pallet { ) -> DispatchResult { // --- 1. Check that the caller has signed the transaction. (the coldkey of the pairing) let coldkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_registration( coldkey:{:?} netuid:{:?} hotkey:{:?} )", coldkey, netuid, @@ -131,7 +131,7 @@ impl Pallet { // --- 12.1.2 Expand subnetwork with new account. Self::append_neuron(netuid, &hotkey, current_block_number); - log::info!("add new neuron account"); + log::debug!("add new neuron account"); } else { // --- 13.1.1 Replacement required. // We take the neuron with the lowest pruning score here. @@ -139,7 +139,7 @@ impl Pallet { // --- 13.1.1 Replace the neuron account with the new info. Self::replace_neuron(netuid, subnetwork_uid, &hotkey, current_block_number); - log::info!("prune neuron"); + log::debug!("prune neuron"); } // --- 14. Record the registration and increment block and interval counters. @@ -149,7 +149,7 @@ impl Pallet { Self::increase_rao_recycled(netuid, Self::get_burn_as_u64(netuid)); // --- 15. Deposit successful event. - log::info!( + log::debug!( "NeuronRegistered( netuid:{:?} uid:{:?} hotkey:{:?} ) ", netuid, subnetwork_uid, @@ -220,7 +220,7 @@ impl Pallet { // --- 1. Check that the caller has signed the transaction. // TODO( const ): This not be the hotkey signature or else an exterior actor can register the hotkey and potentially control it? let signing_origin = ensure_signed(origin)?; - log::info!( + log::debug!( "do_registration( origin:{:?} netuid:{:?} hotkey:{:?}, coldkey:{:?} )", signing_origin, netuid, @@ -326,7 +326,7 @@ impl Pallet { // --- 11.1.2 Expand subnetwork with new account. Self::append_neuron(netuid, &hotkey, current_block_number); - log::info!("add new neuron account"); + log::debug!("add new neuron account"); } else { // --- 11.1.1 Replacement required. // We take the neuron with the lowest pruning score here. @@ -334,7 +334,7 @@ impl Pallet { // --- 11.1.1 Replace the neuron account with the new info. Self::replace_neuron(netuid, subnetwork_uid, &hotkey, current_block_number); - log::info!("prune neuron"); + log::debug!("prune neuron"); } // --- 12. Record the registration and increment block and interval counters. @@ -343,7 +343,7 @@ impl Pallet { RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); // --- 13. Deposit successful event. - log::info!( + log::debug!( "NeuronRegistered( netuid:{:?} uid:{:?} hotkey:{:?} ) ", netuid, subnetwork_uid, @@ -366,7 +366,7 @@ impl Pallet { // --- 1. Check that the caller has signed the transaction. let coldkey = ensure_signed(origin)?; - log::info!("do_faucet( coldkey:{:?} )", coldkey); + log::debug!("do_faucet( coldkey:{:?} )", coldkey); // --- 2. Ensure the passed block number is valid, not in the future or too old. // Work must have been done within 3 blocks (stops long range attacks). @@ -400,7 +400,7 @@ impl Pallet { Self::add_balance_to_coldkey_account(&coldkey, balance_to_add); // --- 6. Deposit successful event. - log::info!( + log::debug!( "Faucet( coldkey:{:?} amount:{:?} ) ", coldkey, balance_to_add diff --git a/pallets/subtensor/src/subnets/serving.rs b/pallets/subtensor/src/subnets/serving.rs index eb7fa4369..1a9240c36 100644 --- a/pallets/subtensor/src/subnets/serving.rs +++ b/pallets/subtensor/src/subnets/serving.rs @@ -106,7 +106,7 @@ impl Pallet { Axons::::insert(netuid, hotkey_id.clone(), prev_axon); // We deposit axon served event. - log::info!("AxonServed( hotkey:{:?} ) ", hotkey_id.clone()); + log::debug!("AxonServed( hotkey:{:?} ) ", hotkey_id.clone()); Self::deposit_event(Event::AxonServed(netuid, hotkey_id)); // Return is successful dispatch. @@ -204,7 +204,7 @@ impl Pallet { Prometheus::::insert(netuid, hotkey_id.clone(), prev_prometheus); // We deposit prometheus served event. - log::info!("PrometheusServed( hotkey:{:?} ) ", hotkey_id.clone()); + log::debug!("PrometheusServed( hotkey:{:?} ) ", hotkey_id.clone()); Self::deposit_event(Event::PrometheusServed(netuid, hotkey_id)); // Return is successful dispatch. diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 67b1d485e..1a53e44cc 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -28,7 +28,7 @@ impl Pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - log::info!("do_commit_weights( hotkey:{:?} netuid:{:?})", who, netuid); + log::debug!("do_commit_weights( hotkey:{:?} netuid:{:?})", who, netuid); ensure!( Self::get_commit_reveal_weights_enabled(netuid), @@ -89,7 +89,7 @@ impl Pallet { ) -> DispatchResult { let who = ensure_signed(origin.clone())?; - log::info!("do_reveal_weights( hotkey:{:?} netuid:{:?})", who, netuid); + log::debug!("do_reveal_weights( hotkey:{:?} netuid:{:?})", who, netuid); ensure!( Self::get_commit_reveal_weights_enabled(netuid), @@ -188,7 +188,7 @@ impl Pallet { ) -> dispatch::DispatchResult { // --- 1. Check the caller's signature. This is the hotkey of a registered account. let hotkey = ensure_signed(origin)?; - log::info!( + log::debug!( "do_set_weights( origin:{:?} netuid:{:?}, uids:{:?}, values:{:?})", hotkey, netuid, @@ -289,7 +289,7 @@ impl Pallet { Self::set_last_update_for_uid(netuid, neuron_uid, current_block); // --- 19. Emit the tracking event. - log::info!( + log::debug!( "WeightsSet( netuid:{:?}, neuron_uid:{:?} )", netuid, neuron_uid @@ -308,7 +308,7 @@ impl Pallet { /// pub fn check_version_key(netuid: u16, version_key: u64) -> bool { let network_version_key: u64 = WeightsVersionKey::::get(netuid); - log::info!( + log::debug!( "check_version_key( network_version_key:{:?}, version_key:{:?} )", network_version_key, version_key diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 1c9c3c25d..460bcb838 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -66,7 +66,7 @@ impl Pallet { Identities::::insert(coldkey.clone(), identity.clone()); // Log the identity set event - log::info!("ChainIdentitySet( coldkey:{:?} ) ", coldkey.clone()); + log::debug!("ChainIdentitySet( coldkey:{:?} ) ", coldkey.clone()); // Emit an event to notify that an identity has been set Self::deposit_event(Event::ChainIdentitySet(coldkey.clone())); diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 9155357c0..cd81060eb 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -150,12 +150,12 @@ impl Pallet { Active::::insert(netuid, updated_active_vec); } pub fn set_pruning_score_for_uid(netuid: u16, uid: u16, pruning_score: u16) { - log::info!("netuid = {:?}", netuid); - log::info!( + log::debug!("netuid = {:?}", netuid); + log::debug!( "SubnetworkN::::get( netuid ) = {:?}", SubnetworkN::::get(netuid) ); - log::info!("uid = {:?}", uid); + log::debug!("uid = {:?}", uid); assert!(uid < SubnetworkN::::get(netuid)); PruningScores::::mutate(netuid, |v| { if let Some(s) = v.get_mut(uid as usize) { From ce0dfa7ec2c7d2480c9f0ab254e9c020c91ff846 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 14 Aug 2024 10:12:21 -0400 Subject: [PATCH 217/269] Fix rate limit for setting children --- pallets/subtensor/src/staking/set_children.rs | 12 +++++++++++- pallets/subtensor/src/utils/rate_limiting.rs | 7 +++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 402e9fd2a..ebd8db6bf 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -61,12 +61,22 @@ impl Pallet { // Ensure the hotkey passes the rate limit. ensure!( Self::passes_rate_limit_globally( - &TransactionType::SetChildren, // Set children. &hotkey, // Specific to a hotkey. + netuid, // Specific to a subnet. + &TransactionType::SetChildren, // Set children. ), Error::::TxRateLimitExceeded ); + // Set last transaction block + let current_block = Self::get_current_block_as_u64(); + Self::set_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildren, + current_block + ); + // --- 2. Check that this delegation is not on the root network. Child hotkeys are not valid on root. ensure!( netuid != Self::get_root_netuid(), diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index b02ad9855..1b75de7d5 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -58,8 +58,11 @@ impl Pallet { } /// Check if a transaction should be rate limited globally - pub fn passes_rate_limit_globally(tx_type: &TransactionType, hotkey: &T::AccountId) -> bool { - let netuid: u16 = u16::MAX; + pub fn passes_rate_limit_globally( + hotkey: &T::AccountId, + netuid: u16, + tx_type: &TransactionType, + ) -> bool { let block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit(tx_type); let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); From 9e98762c89fb78a73c76de0880aa4fc6c7c0b186 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 14 Aug 2024 11:06:34 -0400 Subject: [PATCH 218/269] Remove VERSION file and instead pass version as an argument --- Cargo.lock | 1 + VERSION | 1 - support/tools/Cargo.toml | 1 + support/tools/src/bump_version.rs | 18 +++++++++--------- 4 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 VERSION diff --git a/Cargo.lock b/Cargo.lock index 28f3f212c..5e46fa1a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9221,6 +9221,7 @@ name = "subtensor-tools" version = "0.1.0" dependencies = [ "anyhow", + "clap", "semver 1.0.23", "toml_edit 0.22.14", ] diff --git a/VERSION b/VERSION deleted file mode 100644 index 0c89fc927..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -4.0.0 \ No newline at end of file diff --git a/support/tools/Cargo.toml b/support/tools/Cargo.toml index a640fde54..fa3e1fd50 100644 --- a/support/tools/Cargo.toml +++ b/support/tools/Cargo.toml @@ -14,5 +14,6 @@ path = "src/bump_version.rs" [dependencies] anyhow = "1.0" +clap = { version = "4.5", features = ["derive"] } semver = "1.0" toml_edit = "0.22" diff --git a/support/tools/src/bump_version.rs b/support/tools/src/bump_version.rs index a806e7f36..a16293c30 100644 --- a/support/tools/src/bump_version.rs +++ b/support/tools/src/bump_version.rs @@ -1,3 +1,4 @@ +use clap::Parser; use semver::Version; use std::{ fs, @@ -18,12 +19,15 @@ const TOML_PATHS: [&str; 9] = [ "node", ]; +#[derive(Parser)] +struct CliArgs { + #[arg(required = true)] + version: Version, +} + fn main() -> anyhow::Result<()> { - let mut version_file = fs::File::options().read(true).write(true).open("VERSION")?; - let mut version_str = String::new(); - version_file.read_to_string(&mut version_str)?; - let mut version = Version::parse(&version_str)?; - version.minor = version.minor.saturating_add(1); + let args = CliArgs::parse(); + let version = args.version; for path in TOML_PATHS { let cargo_toml_path = format!("{path}/Cargo.toml"); @@ -41,9 +45,5 @@ fn main() -> anyhow::Result<()> { toml_file.write_all(modified_toml_doc.to_string().as_bytes())?; } - version_file.set_len(0)?; - version_file.rewind()?; - version_file.write_all(version.to_string().as_bytes())?; - Ok(()) } From 2c25fa3d420270739159249a7ee911d433f7ac14 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 14 Aug 2024 11:16:36 -0400 Subject: [PATCH 219/269] Include the devnet-ready branch when checking for deployment --- .github/workflows/check-finney.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-finney.yml b/.github/workflows/check-finney.yml index 3e9fb5994..6a51b3b0c 100644 --- a/.github/workflows/check-finney.yml +++ b/.github/workflows/check-finney.yml @@ -2,7 +2,7 @@ name: Finney Deploy Check on: pull_request: - branches: [finney, main] + branches: [finney, main, devnet-ready] env: CARGO_TERM_COLOR: always From 5d1e951ecb58d2f5084a0f380f0c96c407399f88 Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 14 Aug 2024 11:50:59 -0400 Subject: [PATCH 220/269] Check for devnet-ready as well --- .github/workflows/check-devnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index fcc9809d3..4396d8660 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -2,7 +2,7 @@ name: Devnet Deploy Check on: pull_request: - branches: [devnet] + branches: [devnet, devnet-ready] env: CARGO_TERM_COLOR: always From 9509ce9158f959a41610dc3ad13da0492600aaab Mon Sep 17 00:00:00 2001 From: Keith Date: Wed, 14 Aug 2024 12:03:41 -0400 Subject: [PATCH 221/269] Skip spec_version bump if 'no-spec-version-bump' label exists --- .github/workflows/check-devnet.yml | 1 + .github/workflows/check-finney.yml | 2 +- .github/workflows/check-testnet.yml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-devnet.yml b/.github/workflows/check-devnet.yml index 4396d8660..bf8f89735 100644 --- a/.github/workflows/check-devnet.yml +++ b/.github/workflows/check-devnet.yml @@ -11,6 +11,7 @@ jobs: check-spec-version: name: Check spec_version bump runs-on: SubtensorCI + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-spec-version-bump') }} steps: - name: Dependencies run: | diff --git a/.github/workflows/check-finney.yml b/.github/workflows/check-finney.yml index 6a51b3b0c..3e9fb5994 100644 --- a/.github/workflows/check-finney.yml +++ b/.github/workflows/check-finney.yml @@ -2,7 +2,7 @@ name: Finney Deploy Check on: pull_request: - branches: [finney, main, devnet-ready] + branches: [finney, main] env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/check-testnet.yml b/.github/workflows/check-testnet.yml index 71c46557c..2978156b4 100644 --- a/.github/workflows/check-testnet.yml +++ b/.github/workflows/check-testnet.yml @@ -11,6 +11,7 @@ jobs: check-spec-version: name: Check spec_version bump runs-on: SubtensorCI + if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-spec-version-bump') }} steps: - name: Dependencies run: | From f33d70155e9f7df9f59211298c820fe6ce6f49b1 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:52:22 -0700 Subject: [PATCH 222/269] add subnet identities --- pallets/subtensor/src/coinbase/root.rs | 9 +- pallets/subtensor/src/lib.rs | 16 ++ pallets/subtensor/src/macros/dispatches.rs | 30 ++++ pallets/subtensor/src/macros/events.rs | 2 + pallets/subtensor/src/rpc_info/subnet_info.rs | 5 +- pallets/subtensor/src/utils/identity.rs | 85 +++++++++ pallets/subtensor/tests/serving.rs | 165 ++++++++++++++++++ 7 files changed, 308 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 974931e8f..8dc9d7a03 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -1008,14 +1008,17 @@ impl Pallet { Error::::NotSubnetOwner ); - // --- 4. Explicitly erase the network and all its parameters. + // --- 4. Remove the subnet identity if it exists. + SubnetIdentities::::remove(netuid); + + // --- 5. Explicitly erase the network and all its parameters. Self::remove_network(netuid); - // --- 5. Emit the NetworkRemoved event. + // --- 6. Emit the NetworkRemoved event. log::info!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 6. Return success. + // --- 7. Return success. Ok(()) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b305661f7..a132390aa 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -152,6 +152,18 @@ pub mod pallet { pub additional: Vec, } + /// Struct for Prometheus. + pub type SubnetIdentityOf = SubnetIdentity; + /// Data structure for Prometheus information. + #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] + pub struct SubnetIdentity { + /// The name of the subnet + pub subnet_name: Vec, + /// The github repository associated with the chain identity + pub github_repo: Vec, + /// The subnet's contact + pub subnet_contact: Vec, + } /// ============================ /// ==== Staking + Accounts ==== /// ============================ @@ -1080,6 +1092,10 @@ pub mod pallet { pub type Identities = StorageMap<_, Blake2_128Concat, T::AccountId, ChainIdentityOf, OptionQuery>; + #[pallet::storage] // --- MAP ( netuid ) --> identity + pub type SubnetIdentities = + StorageMap<_, Blake2_128Concat, u16, SubnetIdentityOf, OptionQuery>; + /// ================================= /// ==== Axon / Promo Endpoints ===== /// ================================= diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 00865f8db..242885ecd 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -937,5 +937,35 @@ mod dispatches { ) -> DispatchResult { Self::do_set_identity(origin, name, url, image, discord, description, additional) } + + /// ---- Set the identity information for a subnet. + /// # Args: + /// * `origin` - (::Origin): + /// - The signature of the calling coldkey, which must be the owner of the subnet. + /// + /// * `netuid` (u16): + /// - The unique network identifier of the subnet. + /// + /// * `subnet_name` (Vec): + /// - The name of the subnet. + /// + /// * `github_repo` (Vec): + /// - The GitHub repository associated with the subnet identity. + /// + /// * `subnet_contact` (Vec): + /// - The contact information for the subnet. + #[pallet::call_index(69)] + #[pallet::weight((Weight::from_parts(45_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] + pub fn set_subnet_identity( + origin: OriginFor, + netuid: u16, + subnet_name: Vec, + github_repo: Vec, + subnet_contact: Vec, + ) -> DispatchResult { + Self::do_set_subnet_identity(origin, netuid, subnet_name, github_repo, subnet_contact) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 694b9779f..b67a778b9 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -179,5 +179,7 @@ mod events { NetworkMaxStakeSet(u16, u64), /// The identity of a coldkey has been set ChainIdentitySet(T::AccountId), + /// The identity of a subnet has been set + SubnetIdentitySet(u16), } } diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 4e9e756a0..6e8b4bdc5 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -4,7 +4,7 @@ use frame_support::storage::IterableStorageMap; extern crate alloc; use codec::Compact; -#[freeze_struct("fe79d58173da662a")] +#[freeze_struct("ccca539640c3f631")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetInfo { netuid: Compact, @@ -25,6 +25,7 @@ pub struct SubnetInfo { emission_values: Compact, burn: Compact, owner: T::AccountId, + identity: Option, } #[freeze_struct("55b472510f10e76a")] @@ -80,6 +81,7 @@ impl Pallet { let network_modality = >::get(netuid); let emission_values = Self::get_emission_value(netuid); let burn: Compact = Self::get_burn_as_u64(netuid).into(); + let identity: Option = SubnetIdentities::::get(netuid); // DEPRECATED let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); @@ -106,6 +108,7 @@ impl Pallet { emission_values: emission_values.into(), burn, owner: Self::get_subnet_owner(netuid), + identity, }) } diff --git a/pallets/subtensor/src/utils/identity.rs b/pallets/subtensor/src/utils/identity.rs index 1c9c3c25d..605700983 100644 --- a/pallets/subtensor/src/utils/identity.rs +++ b/pallets/subtensor/src/utils/identity.rs @@ -75,6 +75,65 @@ impl Pallet { Ok(()) } + /// Sets the identity for a subnet. + /// + /// This function allows the owner of a subnet to set or update the identity information associated with the subnet. + /// It verifies that the caller is the owner of the specified subnet, validates the provided identity information, + /// and then stores it in the blockchain state. + /// + /// # Arguments + /// + /// * `origin` - The origin of the call, which should be a signed extrinsic. + /// * `netuid` - The unique identifier for the subnet. + /// * `subnet_name` - The name of the subnet to be associated with the identity. + /// * `github_repo` - The GitHub repository URL associated with the subnet identity. + /// * `subnet_contact` - Contact information for the subnet. + /// + /// # Returns + /// + /// Returns `Ok(())` if the subnet identity is successfully set, otherwise returns an error. + pub fn do_set_subnet_identity( + origin: T::RuntimeOrigin, + netuid: u16, + subnet_name: Vec, + github_repo: Vec, + subnet_contact: Vec, + ) -> dispatch::DispatchResult { + // Ensure the call is signed and get the signer's (coldkey) account + let coldkey = ensure_signed(origin)?; + + // Ensure that the coldkey owns the subnet + ensure!( + Self::get_subnet_owner(netuid) == coldkey, + Error::::NotSubnetOwner + ); + + // Create the identity struct with the provided information + let identity: SubnetIdentityOf = SubnetIdentityOf { + subnet_name, + github_repo, + subnet_contact, + }; + + // Validate the created identity + ensure!( + Self::is_valid_subnet_identity(&identity), + Error::::InvalidIdentity + ); + + // Store the validated identity in the blockchain state + SubnetIdentities::::insert(netuid, identity.clone()); + + // Log the identity set event + log::info!("SubnetIdentitySet( netuid:{:?} ) ", netuid); + + // Emit an event to notify that an identity has been set + Self::deposit_event(Event::SubnetIdentitySet(netuid)); + + // Return Ok to indicate successful execution + Ok(()) + } + /// Validates the given ChainIdentityOf struct. /// /// This function checks if the total length of all fields in the ChainIdentityOf struct @@ -106,4 +165,30 @@ impl Pallet { && identity.description.len() <= 1024 && identity.additional.len() <= 1024 } + + /// Validates the given SubnetIdentityOf struct. + /// + /// This function checks if the total length of all fields in the SubnetIdentityOf struct + /// is less than or equal to 2304 bytes, and if each individual field is also + /// within its respective maximum byte limit. + /// + /// # Arguments + /// + /// * `identity` - A reference to the SubnetIdentityOf struct to be validated. + /// + /// # Returns + /// + /// * `bool` - Returns true if the SubnetIdentity is valid, false otherwise. + pub fn is_valid_subnet_identity(identity: &SubnetIdentityOf) -> bool { + let total_length = identity + .subnet_name + .len() + .saturating_add(identity.github_repo.len()) + .saturating_add(identity.subnet_contact.len()); + + total_length <= 256 + 1024 + 1024 + && identity.subnet_name.len() <= 256 + && identity.github_repo.len() <= 1024 + && identity.subnet_contact.len() <= 1024 + } } diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index b0eada8e6..3da807fea 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -827,3 +827,168 @@ fn test_migrate_set_hotkey_identities() { ); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_do_set_subnet_identity --exact --nocapture +#[test] +fn test_do_set_subnet_identity() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let netuid = 1; + + // Register a hotkey for the coldkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set coldkey as the owner of the subnet + SubnetOwner::::insert(netuid, coldkey); + + // Prepare subnet identity data + let subnet_name = b"Test Subnet".to_vec(); + let github_repo = b"https://github.com/test/subnet".to_vec(); + let subnet_contact = b"contact@testsubnet.com".to_vec(); + + // Set subnet identity + assert_ok!(SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + )); + + // Check if subnet identity is set correctly + let stored_identity = + SubnetIdentities::::get(netuid).expect("Subnet identity should be set"); + assert_eq!(stored_identity.subnet_name, subnet_name); + assert_eq!(stored_identity.github_repo, github_repo); + assert_eq!(stored_identity.subnet_contact, subnet_contact); + + // Test setting subnet identity by non-owner + let non_owner_coldkey = U256::from(2); + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(non_owner_coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + ), + Error::::NotSubnetOwner + ); + + // Test updating an existing subnet identity + let new_subnet_name = b"Updated Subnet".to_vec(); + let new_github_repo = b"https://github.com/test/subnet-updated".to_vec(); + assert_ok!(SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + new_subnet_name.clone(), + new_github_repo.clone(), + subnet_contact.clone() + )); + + let updated_identity = + SubnetIdentities::::get(netuid).expect("Updated subnet identity should be set"); + assert_eq!(updated_identity.subnet_name, new_subnet_name); + assert_eq!(updated_identity.github_repo, new_github_repo); + + // Test setting subnet identity with invalid data (exceeding 1024 bytes total) + let long_data = vec![0; 1025]; + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + long_data.clone(), + long_data.clone(), + long_data.clone() + ), + Error::::InvalidIdentity + ); + }); +} + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test serving -- test_is_valid_subnet_identity --exact --nocapture +#[test] +fn test_is_valid_subnet_identity() { + new_test_ext(1).execute_with(|| { + // Test valid subnet identity + let valid_identity = SubnetIdentity { + subnet_name: vec![0; 256], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_subnet_identity(&valid_identity)); + + // Test subnet identity with total length exactly at the maximum + let max_length_identity = SubnetIdentity { + subnet_name: vec![0; 256], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(SubtensorModule::is_valid_subnet_identity( + &max_length_identity + )); + + // Test subnet identity with total length exceeding the maximum + let invalid_length_identity = SubnetIdentity { + subnet_name: vec![0; 257], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_subnet_identity( + &invalid_length_identity + )); + + // Test subnet identity with one field exceeding its maximum + let invalid_field_identity = SubnetIdentity { + subnet_name: vec![0; 257], + github_repo: vec![0; 1024], + subnet_contact: vec![0; 1024], + }; + assert!(!SubtensorModule::is_valid_subnet_identity( + &invalid_field_identity + )); + + // Test subnet identity with empty fields + let empty_identity = SubnetIdentity { + subnet_name: vec![], + github_repo: vec![], + subnet_contact: vec![], + }; + assert!(SubtensorModule::is_valid_subnet_identity(&empty_identity)); + + // Test subnet identity with some empty and some filled fields + let mixed_identity = SubnetIdentity { + subnet_name: b"Test Subnet".to_vec(), + github_repo: vec![], + subnet_contact: b"contact@testsubnet.com".to_vec(), + }; + assert!(SubtensorModule::is_valid_subnet_identity(&mixed_identity)); + }); +} + +#[test] +fn test_set_identity_for_non_existent_subnet() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let netuid = 999; // Non-existent subnet ID + + // Subnet identity data + let subnet_name = b"Non-existent Subnet".to_vec(); + let github_repo = b"https://github.com/test/nonexistent".to_vec(); + let subnet_contact = b"contact@nonexistent.com".to_vec(); + + // Attempt to set identity for a non-existent subnet + assert_noop!( + SubtensorModule::do_set_subnet_identity( + <::RuntimeOrigin>::signed(coldkey), + netuid, + subnet_name.clone(), + github_repo.clone(), + subnet_contact.clone() + ), + Error::::NotSubnetOwner // Since there's no owner, it should fail + ); + }); +} From fa8da4c5c725b90b25bc896865e7d598f54c3fc8 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:43:07 -0700 Subject: [PATCH 223/269] fix comments --- pallets/subtensor/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index a132390aa..f9f5197c7 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -133,9 +133,9 @@ pub mod pallet { pub ip_type: u8, } - /// Struct for Prometheus. + /// Struct for ChainIdentities. pub type ChainIdentityOf = ChainIdentity; - /// Data structure for Prometheus information. + /// Data structure for Chain Identities. #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct ChainIdentity { /// The name of the chain identity @@ -152,9 +152,9 @@ pub mod pallet { pub additional: Vec, } - /// Struct for Prometheus. + /// Struct for SubnetIdentities. pub type SubnetIdentityOf = SubnetIdentity; - /// Data structure for Prometheus information. + /// Data structure for Subnet Identities #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct SubnetIdentity { /// The name of the subnet From ba508aaf3bbdfb5b3c071310b3a57362a6822b60 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:01:19 -0700 Subject: [PATCH 224/269] freeze SubnetIdentity struct --- pallets/subtensor/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f9f5197c7..dfac0d603 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -60,7 +60,7 @@ extern crate alloc; #[frame_support::pallet] pub mod pallet { - use crate::migrations; + use crate::{freeze_struct, migrations}; use frame_support::{ dispatch::GetDispatchInfo, pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, @@ -155,6 +155,7 @@ pub mod pallet { /// Struct for SubnetIdentities. pub type SubnetIdentityOf = SubnetIdentity; /// Data structure for Subnet Identities + #[freeze_struct("f448dc3dad763108")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct SubnetIdentity { /// The name of the subnet From f373434fc73d16f4027c6fda98576a04369799d2 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:36:44 -0700 Subject: [PATCH 225/269] add identities to subnet registration process --- pallets/admin-utils/tests/tests.rs | 2 +- pallets/subtensor/src/benchmarks.rs | 4 +- pallets/subtensor/src/coinbase/root.rs | 36 +++++++--- pallets/subtensor/src/macros/dispatches.rs | 7 +- pallets/subtensor/src/macros/events.rs | 2 + pallets/subtensor/tests/epoch.rs | 4 +- pallets/subtensor/tests/migration.rs | 3 +- pallets/subtensor/tests/root.rs | 83 +++++++++++++++++----- pallets/subtensor/tests/serving.rs | 22 ++++++ 9 files changed, 130 insertions(+), 33 deletions(-) diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 6e78a1ed6..3e57f74fe 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1243,7 +1243,7 @@ fn test_sudo_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); assert_ok!(AdminUtils::sudo_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 03e087a92..ba51f0d78 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -299,7 +299,7 @@ benchmarks! { let amount: u64 = 1; let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); - }: register_network(RawOrigin::Signed(coldkey)) + }: register_network(RawOrigin::Signed(coldkey), None) benchmark_dissolve_network { let seed : u32 = 1; @@ -311,7 +311,7 @@ benchmarks! { let amount: u64 = 1; let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); - assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); + assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into(), None)); }: dissolve_network(RawOrigin::Signed(coldkey), 1) // swap_hotkey { diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 8dc9d7a03..9786f13fd 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -894,17 +894,24 @@ impl Pallet { /// Facilitates user registration of a new subnetwork. /// /// # Args: - /// * 'origin': ('T::RuntimeOrigin'): The calling origin. Must be signed. + /// * `origin` (`T::RuntimeOrigin`): The calling origin. Must be signed. + /// * `identity` (`Option`): Optional identity to be associated with the new subnetwork. /// - /// # Event: - /// * 'NetworkAdded': Emitted when a new network is successfully added. + /// # Events: + /// * `NetworkAdded(netuid, modality)`: Emitted when a new network is successfully added. + /// * `SubnetIdentitySet(netuid)`: Emitted when a custom identity is set for a new subnetwork. + /// * `NetworkRemoved(netuid)`: Emitted when an existing network is removed to make room for the new one. + /// * `SubnetIdentityRemoved(netuid)`: Emitted when the identity of a removed network is also deleted. /// /// # Raises: /// * 'TxRateLimitExceeded': If the rate limit for network registration is exceeded. /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. /// - pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { + pub fn user_add_network( + origin: T::RuntimeOrigin, + identity: Option, + ) -> dispatch::DispatchResult { // --- 0. Ensure the caller is a signed user. let coldkey = ensure_signed(origin)?; @@ -948,6 +955,11 @@ impl Pallet { Self::remove_network(netuid_to_prune); log::debug!("remove_network: {:?}", netuid_to_prune,); Self::deposit_event(Event::NetworkRemoved(netuid_to_prune)); + + if SubnetIdentities::::take(netuid_to_prune).is_some() { + Self::deposit_event(Event::SubnetIdentityRemoved(netuid_to_prune)); + } + netuid_to_prune } }; @@ -961,13 +973,19 @@ impl Pallet { Self::init_new_network(netuid_to_register, 360); log::debug!("init_new_network: {:?}", netuid_to_register,); - // --- 7. Set netuid storage. + // --- 7. Remove the identity if it exists + if let Some(identity_value) = identity { + SubnetIdentities::::insert(netuid_to_register, identity_value); + Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); + } + + // --- 8. Set netuid storage. let current_block_number: u64 = Self::get_current_block_as_u64(); NetworkLastRegistered::::set(current_block_number); NetworkRegisteredAt::::insert(netuid_to_register, current_block_number); SubnetOwner::::insert(netuid_to_register, coldkey); - // --- 8. Emit the NetworkAdded event. + // --- 9. Emit the NetworkAdded event. log::info!( "NetworkAdded( netuid:{:?}, modality:{:?} )", netuid_to_register, @@ -975,7 +993,7 @@ impl Pallet { ); Self::deposit_event(Event::NetworkAdded(netuid_to_register, 0)); - // --- 9. Return success. + // --- 10. Return success. Ok(()) } @@ -1009,7 +1027,9 @@ impl Pallet { ); // --- 4. Remove the subnet identity if it exists. - SubnetIdentities::::remove(netuid); + if SubnetIdentities::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } // --- 5. Explicitly erase the network and all its parameters. Self::remove_network(netuid); diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 242885ecd..59de17b60 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -798,8 +798,11 @@ mod dispatches { #[pallet::weight((Weight::from_parts(157_000_000, 0) .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] - pub fn register_network(origin: OriginFor) -> DispatchResult { - Self::user_add_network(origin) + pub fn register_network( + origin: OriginFor, + identity: Option, + ) -> DispatchResult { + Self::user_add_network(origin, identity) } /// Facility extrinsic for user to get taken from faucet diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b67a778b9..a0ec67861 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -181,5 +181,7 @@ mod events { ChainIdentitySet(T::AccountId), /// The identity of a subnet has been set SubnetIdentitySet(u16), + /// The identity of a subnet has been removed + SubnetIdentityRemoved(u16), } } diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index b639a4ac4..5e4d0b79c 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -1501,7 +1501,7 @@ fn test_set_alpha_disabled() { assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); // Only owner can set alpha values - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); // Explicitly set to false SubtensorModule::set_liquid_alpha_enabled(netuid, false); @@ -2584,7 +2584,7 @@ fn test_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone())); + assert_ok!(SubtensorModule::register_network(signer.clone(), None)); assert_ok!(SubtensorModule::do_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 2a1388237..95479d7d7 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -170,7 +170,8 @@ fn test_total_issuance_global() { SubtensorModule::add_balance_to_coldkey_account(&owner, lockcost); // Add a balance of 20000 to the coldkey account. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); SubtensorModule::set_max_allowed_uids(netuid, 1); // Set the maximum allowed unique identifiers for the network to 1. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 84df71d83..b40cd788d 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -4,8 +4,9 @@ use crate::mock::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use frame_system::{EventRecord, Phase}; -use pallet_subtensor::migrations; use pallet_subtensor::Error; +use pallet_subtensor::{migrations, SubnetIdentity}; +use pallet_subtensor::{SubnetIdentities, SubnetIdentityOf}; use sp_core::{Get, H256, U256}; mod mock; @@ -235,7 +236,8 @@ fn test_root_set_weights() { for netuid in 1..n { log::debug!("Adding network with netuid: {}", netuid); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(U256::from(netuid + 456)) + <::RuntimeOrigin>::signed(U256::from(netuid + 456)), + None )); } @@ -380,7 +382,8 @@ fn test_root_set_weights_out_of_order_netuids() { if netuid % 2 == 0 { assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(U256::from(netuid)) + <::RuntimeOrigin>::signed(U256::from(netuid)), + None )); } else { add_network(netuid as u16 * 10, 1000, 0) @@ -471,14 +474,16 @@ fn test_root_subnet_creation_deletion() { SubtensorModule::add_balance_to_coldkey_account(&owner, 1_000_000_000_000_000); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); step_block(1); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 1, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 1, lock_reduction_interval: 2, current_block: 1, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation @@ -492,38 +497,44 @@ fn test_root_subnet_creation_deletion() { // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 1, lock_reduction_interval: 2, current_block: 4, mult: 2 lock_cost: 100000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); // Reaches min value assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 4, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation step_block(1); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 150000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 300000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 300_000_000_000); // Doubles from previous subnet creation step_block(1); // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 225000000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 450000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 450_000_000_000); // Increasing step_block(1); // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 337500000000 assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 675_000_000_000); // Increasing. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 1_350_000_000_000); // Double increasing. assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); assert_eq!(SubtensorModule::get_network_lock_cost(), 2_700_000_000_000); // Double increasing again. @@ -572,7 +583,8 @@ fn test_network_pruning() { 1_000 )); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); assert!(SubtensorModule::if_subnet_exist((i as u16) + 1)); @@ -645,17 +657,20 @@ fn test_network_prune_results() { SubtensorModule::add_balance_to_coldkey_account(&owner, 1_000_000_000_000_000); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); step_block(3); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(owner) + <::RuntimeOrigin>::signed(owner), + None )); step_block(3); @@ -699,7 +714,8 @@ fn test_weights_after_network_pruning() { // Register a network assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); @@ -759,7 +775,8 @@ fn test_weights_after_network_pruning() { assert_eq!(latest_weights[0][1], 21845); assert_ok!(SubtensorModule::register_network( - <::RuntimeOrigin>::signed(cold) + <::RuntimeOrigin>::signed(cold), + None )); // Subnet should not exist, as it would replace a previous subnet. @@ -979,3 +996,35 @@ fn test_dissolve_network_does_not_exist_err() { ); }); } + +#[test] +fn test_user_add_network_with_identity_fields_ok() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let balance = SubtensorModule::get_network_lock_cost() + 10_000; + + let subnet_name: Vec = b"JesusSubnet".to_vec(); + let github_repo: Vec = b"bible.com".to_vec(); + let subnet_contact: Vec = b"https://www.vatican.va".to_vec(); + + let identity_value: SubnetIdentity = SubnetIdentityOf { + subnet_name: subnet_name.clone(), + github_repo: github_repo.clone(), + subnet_contact: subnet_contact.clone(), + }; + + SubtensorModule::add_balance_to_coldkey_account(&coldkey, balance); + + // Call the function with the identity. + assert_ok!(SubtensorModule::user_add_network( + RuntimeOrigin::signed(coldkey), + Some(identity_value.clone()) + )); + + // Retrieve and verify the stored identity. + let stored_identity: SubnetIdentity = SubnetIdentities::::get(1).unwrap(); + assert_eq!(stored_identity.subnet_name, subnet_name); + assert_eq!(stored_identity.github_repo, github_repo); + assert_eq!(stored_identity.subnet_contact, subnet_contact); + }); +} diff --git a/pallets/subtensor/tests/serving.rs b/pallets/subtensor/tests/serving.rs index 3da807fea..49a963951 100644 --- a/pallets/subtensor/tests/serving.rs +++ b/pallets/subtensor/tests/serving.rs @@ -992,3 +992,25 @@ fn test_set_identity_for_non_existent_subnet() { ); }); } + +#[test] +fn test_set_subnet_identity_dispatch_info_ok() { + new_test_ext(1).execute_with(|| { + let netuid: u16 = 1; + let subnet_name: Vec = b"JesusSubnet".to_vec(); + let github_repo: Vec = b"bible.com".to_vec(); + let subnet_contact: Vec = b"https://www.vatican.va".to_vec(); + + let call: RuntimeCall = RuntimeCall::SubtensorModule(SubtensorCall::set_subnet_identity { + netuid, + subnet_name, + github_repo, + subnet_contact, + }); + + let dispatch_info: DispatchInfo = call.get_dispatch_info(); + + assert_eq!(dispatch_info.class, DispatchClass::Normal); + assert_eq!(dispatch_info.pays_fee, Pays::Yes); + }); +} From 74256020f45b4d7633cfb32d99250fbb02a7209e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:53:43 -0700 Subject: [PATCH 226/269] check if identity is valid --- pallets/subtensor/src/coinbase/root.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 9786f13fd..3dbb42d27 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -973,8 +973,13 @@ impl Pallet { Self::init_new_network(netuid_to_register, 360); log::debug!("init_new_network: {:?}", netuid_to_register,); - // --- 7. Remove the identity if it exists + // --- 7. Remove the identity if it exists if let Some(identity_value) = identity { + ensure!( + Self::is_valid_subnet_identity(&identity_value), + Error::::InvalidIdentity + ); + SubnetIdentities::::insert(netuid_to_register, identity_value); Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } From 9b0d246d7b91f3afcd52d12833ad6c3f537936b1 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 16 Aug 2024 10:45:36 -0400 Subject: [PATCH 227/269] switch localnet back to release --- scripts/localnet.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/localnet.sh b/scripts/localnet.sh index 4c15a3334..850a314d8 100755 --- a/scripts/localnet.sh +++ b/scripts/localnet.sh @@ -48,26 +48,26 @@ fi if [[ $BUILD_BINARY == "1" ]]; then echo "*** Building substrate binary..." - cargo build --workspace --profile=production --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" + cargo build --workspace --profile=release --features "$FEATURES" --manifest-path "$BASE_DIR/Cargo.toml" echo "*** Binary compiled" fi echo "*** Building chainspec..." -"$BASE_DIR/target/production/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH +"$BASE_DIR/target/release/node-subtensor" build-spec --disable-default-bootnode --raw --chain $CHAIN >$FULL_PATH echo "*** Chainspec built and output to file" if [ $NO_PURGE -eq 1 ]; then echo "*** Purging previous state skipped..." else echo "*** Purging previous state..." - "$BASE_DIR/target/production/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 - "$BASE_DIR/target/production/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 + "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/bob --chain="$FULL_PATH" >/dev/null 2>&1 + "$BASE_DIR/target/release/node-subtensor" purge-chain -y --base-path /tmp/alice --chain="$FULL_PATH" >/dev/null 2>&1 echo "*** Previous chainstate purged" fi echo "*** Starting localnet nodes..." alice_start=( - "$BASE_DIR/target/production/node-subtensor" + "$BASE_DIR/target/release/node-subtensor" --base-path /tmp/alice --chain="$FULL_PATH" --alice @@ -80,7 +80,7 @@ alice_start=( ) bob_start=( - "$BASE_DIR"/target/production/node-subtensor + "$BASE_DIR"/target/release/node-subtensor --base-path /tmp/bob --chain="$FULL_PATH" --bob From 6333edd31ced5b9cf02b3a3153e7dc62372253b8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 16 Aug 2024 10:53:48 -0400 Subject: [PATCH 228/269] strip out DummyLint --- build.rs | 1 - support/linting/src/dummy_lint.rs | 11 ----------- support/linting/src/lib.rs | 2 -- 3 files changed, 14 deletions(-) delete mode 100644 support/linting/src/dummy_lint.rs diff --git a/build.rs b/build.rs index cc495ec13..10cac0ea7 100644 --- a/build.rs +++ b/build.rs @@ -59,7 +59,6 @@ fn main() { } }; - track_lint(DummyLint::lint(&parsed_file)); track_lint(RequireFreezeStruct::lint(&parsed_file)); }); diff --git a/support/linting/src/dummy_lint.rs b/support/linting/src/dummy_lint.rs deleted file mode 100644 index 1c5e7bc3f..000000000 --- a/support/linting/src/dummy_lint.rs +++ /dev/null @@ -1,11 +0,0 @@ -use syn::File; - -use super::*; - -pub struct DummyLint; - -impl Lint for DummyLint { - fn lint(_source: &File) -> Result { - Ok(()) - } -} diff --git a/support/linting/src/lib.rs b/support/linting/src/lib.rs index 0d0df8a44..d02a70a2b 100644 --- a/support/linting/src/lib.rs +++ b/support/linting/src/lib.rs @@ -1,8 +1,6 @@ pub mod lint; pub use lint::*; -mod dummy_lint; mod require_freeze_struct; -pub use dummy_lint::DummyLint; pub use require_freeze_struct::RequireFreezeStruct; From d6790a3972d3fe90fa5351e3c2483ba6da75ac70 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Fri, 16 Aug 2024 12:03:03 -0400 Subject: [PATCH 229/269] Use passes_rate_limit_on_subnet for setting children --- pallets/subtensor/src/staking/set_children.rs | 4 ++-- pallets/subtensor/src/utils/rate_limiting.rs | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index ebd8db6bf..31bf96f52 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -60,10 +60,10 @@ impl Pallet { // Ensure the hotkey passes the rate limit. ensure!( - Self::passes_rate_limit_globally( + Self::passes_rate_limit_on_subnet( + &TransactionType::SetChildren, // Set children. &hotkey, // Specific to a hotkey. netuid, // Specific to a subnet. - &TransactionType::SetChildren, // Set children. ), Error::::TxRateLimitExceeded ); diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index 1b75de7d5..b02ad9855 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -58,11 +58,8 @@ impl Pallet { } /// Check if a transaction should be rate limited globally - pub fn passes_rate_limit_globally( - hotkey: &T::AccountId, - netuid: u16, - tx_type: &TransactionType, - ) -> bool { + pub fn passes_rate_limit_globally(tx_type: &TransactionType, hotkey: &T::AccountId) -> bool { + let netuid: u16 = u16::MAX; let block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit(tx_type); let last_block: u64 = Self::get_last_transaction_block(hotkey, netuid, tx_type); From 136dd471cdf15c8c6b0d9b96fdecfe1361075ef4 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 16 Aug 2024 09:35:15 -0700 Subject: [PATCH 230/269] Expand test --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/subtensor/tests/root.rs | 77 ++++++++++++++++++++------ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 3dbb42d27..a657bf1f5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -973,7 +973,7 @@ impl Pallet { Self::init_new_network(netuid_to_register, 360); log::debug!("init_new_network: {:?}", netuid_to_register,); - // --- 7. Remove the identity if it exists + // --- 7. Add the identity if it exists if let Some(identity_value) = identity { ensure!( Self::is_valid_subnet_identity(&identity_value), diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index b40cd788d..c134e499e 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -1000,31 +1000,72 @@ fn test_dissolve_network_does_not_exist_err() { #[test] fn test_user_add_network_with_identity_fields_ok() { new_test_ext(1).execute_with(|| { - let coldkey = U256::from(1); - let balance = SubtensorModule::get_network_lock_cost() + 10_000; + let coldkey_1 = U256::from(1); + let coldkey_2 = U256::from(2); + let balance_1 = SubtensorModule::get_network_lock_cost() + 10_000; + + let subnet_name_1: Vec = b"GenericSubnet1".to_vec(); + let github_repo_1: Vec = b"GenericSubnet1.com".to_vec(); + let subnet_contact_1: Vec = b"https://www.GenericSubnet1.co".to_vec(); + + let identity_value_1: SubnetIdentity = SubnetIdentityOf { + subnet_name: subnet_name_1.clone(), + github_repo: github_repo_1.clone(), + subnet_contact: subnet_contact_1.clone(), + }; - let subnet_name: Vec = b"JesusSubnet".to_vec(); - let github_repo: Vec = b"bible.com".to_vec(); - let subnet_contact: Vec = b"https://www.vatican.va".to_vec(); + let subnet_name_2: Vec = b"DistinctSubnet2".to_vec(); + let github_repo_2: Vec = b"https://github.com/DistinctRepo2".to_vec(); + let subnet_contact_2: Vec = b"https://contact2.example.com".to_vec(); - let identity_value: SubnetIdentity = SubnetIdentityOf { - subnet_name: subnet_name.clone(), - github_repo: github_repo.clone(), - subnet_contact: subnet_contact.clone(), + let identity_value_2: SubnetIdentity = SubnetIdentityOf { + subnet_name: subnet_name_2.clone(), + github_repo: github_repo_2.clone(), + subnet_contact: subnet_contact_2.clone(), }; - SubtensorModule::add_balance_to_coldkey_account(&coldkey, balance); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_1, balance_1); + + assert_ok!(SubtensorModule::user_add_network( + RuntimeOrigin::signed(coldkey_1), + Some(identity_value_1.clone()) + )); + + let balance_2 = SubtensorModule::get_network_lock_cost() + 10_000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey_2, balance_2); - // Call the function with the identity. assert_ok!(SubtensorModule::user_add_network( - RuntimeOrigin::signed(coldkey), - Some(identity_value.clone()) + RuntimeOrigin::signed(coldkey_2), + Some(identity_value_2.clone()) + )); + + let stored_identity_1: SubnetIdentity = SubnetIdentities::::get(1).unwrap(); + assert_eq!(stored_identity_1.subnet_name, subnet_name_1); + assert_eq!(stored_identity_1.github_repo, github_repo_1); + assert_eq!(stored_identity_1.subnet_contact, subnet_contact_1); + + let stored_identity_2: SubnetIdentity = SubnetIdentities::::get(2).unwrap(); + assert_eq!(stored_identity_2.subnet_name, subnet_name_2); + assert_eq!(stored_identity_2.github_repo, github_repo_2); + assert_eq!(stored_identity_2.subnet_contact, subnet_contact_2); + + // Now remove the first network. + assert_ok!(SubtensorModule::user_remove_network( + RuntimeOrigin::signed(coldkey_1), + 1 )); - // Retrieve and verify the stored identity. - let stored_identity: SubnetIdentity = SubnetIdentities::::get(1).unwrap(); - assert_eq!(stored_identity.subnet_name, subnet_name); - assert_eq!(stored_identity.github_repo, github_repo); - assert_eq!(stored_identity.subnet_contact, subnet_contact); + // Verify that the first network and identity have been removed. + assert!(SubnetIdentities::::get(1).is_none()); + + // Ensure the second network and identity are still intact. + let stored_identity_2_after_removal: SubnetIdentity = + SubnetIdentities::::get(2).unwrap(); + assert_eq!(stored_identity_2_after_removal.subnet_name, subnet_name_2); + assert_eq!(stored_identity_2_after_removal.github_repo, github_repo_2); + assert_eq!( + stored_identity_2_after_removal.subnet_contact, + subnet_contact_2 + ); }); } From fd379bc6bade3be746e08d7e42691c43da731443 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Fri, 16 Aug 2024 10:32:09 -0700 Subject: [PATCH 231/269] match existing pattern --- pallets/subtensor/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index dfac0d603..3262132fb 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -60,7 +60,7 @@ extern crate alloc; #[frame_support::pallet] pub mod pallet { - use crate::{freeze_struct, migrations}; + use crate::migrations; use frame_support::{ dispatch::GetDispatchInfo, pallet_prelude::{DispatchResult, StorageMap, ValueQuery, *}, @@ -155,7 +155,7 @@ pub mod pallet { /// Struct for SubnetIdentities. pub type SubnetIdentityOf = SubnetIdentity; /// Data structure for Subnet Identities - #[freeze_struct("f448dc3dad763108")] + #[crate::freeze_struct("f448dc3dad763108")] #[derive(Encode, Decode, Default, TypeInfo, Clone, PartialEq, Eq, Debug)] pub struct SubnetIdentity { /// The name of the subnet From f1866df8e497717c7e551944e1e3ded25ce0785e Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Fri, 9 Aug 2024 19:58:43 +0400 Subject: [PATCH 232/269] draft take tests --- pallets/subtensor/tests/children.rs | 303 ++++++++++++++++++++++++---- 1 file changed, 263 insertions(+), 40 deletions(-) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index f491e8b64..f66c88441 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -421,8 +421,8 @@ fn test_get_stake_for_hotkey_on_subnet() { let parent_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid); let child_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child, netuid); - println!("Parent stake: {}", parent_stake); - println!("Child stake: {}", child_stake); + log::info!("Parent stake: {}", parent_stake); + log::info!("Child stake: {}", child_stake); // The parent should have 0 stake as it's all allocated to the child assert_eq!(parent_stake, 0); @@ -2233,9 +2233,9 @@ fn test_parent_child_chain_emission() { let pending_emission_b = SubtensorModule::get_pending_hotkey_emission(&hotkey_b); let pending_emission_c = SubtensorModule::get_pending_hotkey_emission(&hotkey_c); - println!("Pending Emission for A: {:?}", pending_emission_a); - println!("Pending Emission for B: {:?}", pending_emission_b); - println!("Pending Emission for C: {:?}", pending_emission_c); + log::info!("Pending Emission for A: {:?}", pending_emission_a); + log::info!("Pending Emission for B: {:?}", pending_emission_b); + log::info!("Pending Emission for C: {:?}", pending_emission_c); // Assert that pending emissions are non-zero // A's pending emission: 2/3 of total emission (due to having 2/3 of total stake) @@ -2397,10 +2397,10 @@ fn test_dynamic_parent_child_relationships() { let child1_stake: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); let child2_stake: u64 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); - println!("Final stakes:"); - println!("Parent stake: {}", parent_stake); - println!("Child1 stake: {}", child1_stake); - println!("Child2 stake: {}", child2_stake); + log::info!("Final stakes:"); + log::info!("Parent stake: {}", parent_stake); + log::info!("Child1 stake: {}", child1_stake); + log::info!("Child2 stake: {}", child2_stake); const TOLERANCE: u64 = 5; // Allow for a small discrepancy due to potential rounding @@ -2687,9 +2687,9 @@ fn test_get_stake_for_hotkey_on_subnet_single_parent_multiple_children() { assert_eq!(child2_stake, 999); // Log the actual stake values - println!("Parent stake: {}", parent_stake); - println!("Child1 stake: {}", child1_stake); - println!("Child2 stake: {}", child2_stake); + log::info!("Parent stake: {}", parent_stake); + log::info!("Child1 stake: {}", child1_stake); + log::info!("Child2 stake: {}", child2_stake); }); } @@ -2737,9 +2737,9 @@ fn test_get_stake_for_hotkey_on_subnet_edge_cases() { let child1_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); let child2_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); - println!("Parent stake: {}", parent_stake); - println!("Child1 stake: {}", child1_stake); - println!("Child2 stake: {}", child2_stake); + log::info!("Parent stake: {}", parent_stake); + log::info!("Child1 stake: {}", child1_stake); + log::info!("Child2 stake: {}", child2_stake); assert_eq!(parent_stake, 0, "Parent should have 0 stake"); assert_eq!(child1_stake, 0, "Child1 should have 0 stake"); @@ -2795,20 +2795,20 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { total_stake, ); - println!("Initial stakes:"); - println!( + log::info!("Initial stakes:"); + log::info!( "Parent stake: {}", SubtensorModule::get_stake_for_hotkey_on_subnet(&parent, netuid) ); - println!( + log::info!( "Child1 stake: {}", SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid) ); - println!( + log::info!( "Child2 stake: {}", SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid) ); - println!( + log::info!( "Grandchild stake: {}", SubtensorModule::get_stake_for_hotkey_on_subnet(&grandchild, netuid) ); @@ -2821,16 +2821,16 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { vec![(u64::MAX / 2, child1), (u64::MAX / 2, child2)] )); - println!("After setting parent's children:"); - println!( + log::info!("After setting parent's children:"); + log::info!( "Parent's children: {:?}", SubtensorModule::get_children(&parent, netuid) ); - println!( + log::info!( "Child1's parents: {:?}", SubtensorModule::get_parents(&child1, netuid) ); - println!( + log::info!( "Child2's parents: {:?}", SubtensorModule::get_parents(&child2, netuid) ); @@ -2839,9 +2839,9 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { let child1_stake_1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child1, netuid); let child2_stake_1 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); - println!("Parent stake: {}", parent_stake_1); - println!("Child1 stake: {}", child1_stake_1); - println!("Child2 stake: {}", child2_stake_1); + log::info!("Parent stake: {}", parent_stake_1); + log::info!("Child1 stake: {}", child1_stake_1); + log::info!("Child2 stake: {}", child2_stake_1); assert_eq!( parent_stake_1, 2, @@ -2858,12 +2858,12 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { vec![(u64::MAX, grandchild)] )); - println!("After setting child1's children:"); - println!( + log::info!("After setting child1's children:"); + log::info!( "Child1's children: {:?}", SubtensorModule::get_children(&child1, netuid) ); - println!( + log::info!( "Grandchild's parents: {:?}", SubtensorModule::get_parents(&grandchild, netuid) ); @@ -2873,10 +2873,10 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { let child2_stake_2 = SubtensorModule::get_stake_for_hotkey_on_subnet(&child2, netuid); let grandchild_stake = SubtensorModule::get_stake_for_hotkey_on_subnet(&grandchild, netuid); - println!("Parent stake: {}", parent_stake_2); - println!("Child1 stake: {}", child1_stake_2); - println!("Child2 stake: {}", child2_stake_2); - println!("Grandchild stake: {}", grandchild_stake); + log::info!("Parent stake: {}", parent_stake_2); + log::info!("Child1 stake: {}", child1_stake_2); + log::info!("Child2 stake: {}", child2_stake_2); + log::info!("Grandchild stake: {}", grandchild_stake); assert_eq!(parent_stake_2, 2, "Parent stake should remain 2"); assert_eq!( @@ -2897,24 +2897,24 @@ fn test_get_stake_for_hotkey_on_subnet_complex_hierarchy() { ); // Additional checks - println!("Final parent-child relationships:"); - println!( + log::info!("Final parent-child relationships:"); + log::info!( "Parent's children: {:?}", SubtensorModule::get_children(&parent, netuid) ); - println!( + log::info!( "Child1's parents: {:?}", SubtensorModule::get_parents(&child1, netuid) ); - println!( + log::info!( "Child2's parents: {:?}", SubtensorModule::get_parents(&child2, netuid) ); - println!( + log::info!( "Child1's children: {:?}", SubtensorModule::get_children(&child1, netuid) ); - println!( + log::info!( "Grandchild's parents: {:?}", SubtensorModule::get_parents(&grandchild, netuid) ); @@ -3237,3 +3237,226 @@ fn test_rank_trust_incentive_calculation_with_parent_child() { }); } + +/// Test normal operation of childkey take +/// +/// This test verifies the correct distribution of rewards between child and parents. +/// +/// # Test Steps: +/// 1. Initialize test environment with a child and multiple parents +/// 2. Set childkey take to 9% (4915 when normalized to u16::MAX) +/// 3. Set up network parameters and register all neurons +/// 4. Set initial stakes for all neurons +/// 5. Run an epoch and process emissions +/// 6. Calculate expected reward distribution +/// 7. Compare actual distribution with expected distribution +/// +/// # Expected Results: +/// - Child should keep 9% of the rewards +/// - Remaining 91% should be distributed among parents proportional to their stake +/// - Total distributed rewards should equal total emissions + +/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_normal_childkey_take_operation --exact --nocapture + +// #[test] +// fn test_normal_childkey_take_operation() { +// new_test_ext(1).execute_with(|| { +// let netuid: u16 = 1; +// let num_neurons: u16 = 5; + +// // Create hotkeys and coldkeys +// let child_coldkey = U256::from(100); +// let child_hotkey = U256::from(0); +// let parent_coldkey = U256::from(101); +// let parent_hotkey = U256::from(1); +// let validator_coldkeys: Vec = (102..105).map(U256::from).collect(); +// let validator_hotkeys: Vec = (2..5).map(U256::from).collect(); + +// // Set childkey take to 9% (4915 when normalized to u16::MAX) +// let childkey_take: u16 = 4915; + +// // Set up network parameters and register neurons +// add_network(netuid, num_neurons, 0); +// SubtensorModule::set_max_registrations_per_block(netuid, 1000); +// SubtensorModule::set_target_registrations_per_interval(netuid, 1000); +// SubtensorModule::set_weights_set_rate_limit(netuid, 0); +// SubtensorModule::set_hotkey_emission_tempo(10); + +// register_ok_neuron(netuid, child_hotkey, child_coldkey, 0); +// register_ok_neuron(netuid, parent_hotkey, parent_coldkey, 0); +// for (&hotkey, &coldkey) in validator_hotkeys.iter().zip(validator_coldkeys.iter()) { +// register_ok_neuron(netuid, hotkey, coldkey, 0); +// } + +// // Set initial stakes +// let child_stake: u64 = 1_000_000; +// let parent_stake: u64 = 2_000_000; +// let validator_stakes: Vec = vec![3_000_000, 4_000_000, 5_000_000]; + +// SubtensorModule::add_balance_to_coldkey_account(&child_coldkey, child_stake); +// SubtensorModule::increase_stake_on_coldkey_hotkey_account( +// &child_coldkey, +// &child_hotkey, +// child_stake, +// ); + +// SubtensorModule::add_balance_to_coldkey_account(&parent_coldkey, parent_stake); +// SubtensorModule::increase_stake_on_coldkey_hotkey_account( +// &parent_coldkey, +// &parent_hotkey, +// parent_stake, +// ); + +// for (i, (&hotkey, &coldkey)) in validator_hotkeys +// .iter() +// .zip(validator_coldkeys.iter()) +// .enumerate() +// { +// SubtensorModule::add_balance_to_coldkey_account(&coldkey, validator_stakes[i]); +// SubtensorModule::increase_stake_on_coldkey_hotkey_account( +// &coldkey, +// &hotkey, +// validator_stakes[i], +// ); +// } + +// // Set up parent-child relationship +// assert_ok!(SubtensorModule::do_set_children( +// RuntimeOrigin::signed(parent_coldkey), +// parent_hotkey, +// netuid, +// vec![(u64::MAX, child_hotkey)] +// )); + +// // Set childkey take +// assert_ok!(SubtensorModule::do_set_childkey_take( +// child_coldkey, +// child_hotkey, +// childkey_take, +// netuid +// )); + +// // Set weights +// let all_uids: Vec = (0..num_neurons).collect(); +// let weights: Vec = vec![u16::MAX / num_neurons; num_neurons as usize]; + +// step_block(2); // Step to ensure weights are set + +// for &hotkey in std::iter::once(&parent_hotkey).chain(validator_hotkeys.iter()) { +// assert_ok!(SubtensorModule::set_weights( +// RuntimeOrigin::signed(hotkey), +// netuid, +// all_uids.clone(), +// weights.clone(), +// 0 +// )); +// } + +// // Run epoch and process emissions +// let rao_emission: u64 = 1_000_000_000; +// let emission = SubtensorModule::epoch(netuid, rao_emission); + +// // Store initial stakes +// let initial_stakes: Vec = [child_hotkey, parent_hotkey] +// .iter() +// .chain(validator_hotkeys.iter()) +// .map(|&hotkey| SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid)) +// .collect(); + +// log::info!("Initial stakes:"); +// log::info!("Child: {}", initial_stakes[0]); +// log::info!("Parent: {}", initial_stakes[1]); +// for (i, &stake) in initial_stakes.iter().skip(2).enumerate() { +// log::info!("Validator {}: {}", i, stake); +// } + +// // Accumulate emissions +// for (hotkey, mining_emission, validator_emission) in emission { +// SubtensorModule::accumulate_hotkey_emission( +// &hotkey, +// netuid, +// validator_emission, +// mining_emission, +// ); +// } + +// // Check pending emissions before distribution +// log::info!("\nPending emissions before distribution:"); +// let pending_emissions: Vec = [child_hotkey, parent_hotkey] +// .iter() +// .chain(validator_hotkeys.iter()) +// .map(|&hotkey| SubtensorModule::get_pending_hotkey_emission(&hotkey)) +// .collect(); + +// log::info!("Child: {}", pending_emissions[0]); +// log::info!("Parent: {}", pending_emissions[1]); +// for (i, &emission) in pending_emissions.iter().skip(2).enumerate() { +// log::info!("Validator {}: {}", i, emission); +// } + +// let total_pending_emission: u64 = pending_emissions.iter().sum(); +// log::info!("Total pending emission: {}", total_pending_emission); + +// log::info!("\nChildkey take: {}", childkey_take); + +// // Step block to trigger emission distribution +// step_block(11); + +// // Calculate actual rewards +// let final_stakes: Vec = [child_hotkey, parent_hotkey] +// .iter() +// .chain(validator_hotkeys.iter()) +// .map(|&hotkey| SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid)) +// .collect(); + +// let rewards: Vec = final_stakes +// .iter() +// .zip(initial_stakes.iter()) +// .map(|(&final_stake, &initial_stake)| final_stake - initial_stake) +// .collect(); + +// log::info!("\nRewards:"); +// log::info!("Child reward: {}", rewards[0]); +// log::info!("Parent reward: {}", rewards[1]); +// for (i, &reward) in rewards.iter().skip(2).enumerate() { +// log::info!("Validator {} reward: {}", i, reward); +// } + +// // Verify total distributed rewards +// let total_distributed: u64 = rewards.iter().sum(); +// log::info!("\nTotal distributed: {}", total_distributed); +// log::info!("Total emission: {}", total_pending_emission); + +// assert!( +// (total_distributed as i64 - total_pending_emission as i64).abs() <= 10, +// "Total distributed rewards mismatch: distributed {} vs emission {}", +// total_distributed, +// total_pending_emission +// ); + +// // Check that PendingdHotkeyEmission is cleared after distribution +// for &hotkey in [child_hotkey, parent_hotkey] +// .iter() +// .chain(validator_hotkeys.iter()) +// { +// assert_eq!( +// SubtensorModule::get_pending_hotkey_emission(&hotkey), +// 0, +// "Pending emission not cleared for hotkey {:?}", +// hotkey +// ); +// } + +// // Verify childkey take +// let child_reward = rewards[0]; +// let expected_child_reward = +// (total_pending_emission as u128 * childkey_take as u128 / u16::MAX as u128) as u64; +// log::info!("\nExpected child reward: {}", expected_child_reward); +// assert!( +// (child_reward as i64 - expected_child_reward as i64).abs() <= 1, +// "Child reward mismatch: actual {} vs expected {}", +// child_reward, +// expected_child_reward +// ); +// }); +// } From aa1920127852a3966b0041e590126a6c39973adf Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 07:22:56 +0400 Subject: [PATCH 233/269] chore: make rate limit prettier --- pallets/subtensor/src/staking/set_children.rs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 31bf96f52..ab69c3438 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -246,23 +246,24 @@ impl Pallet { Error::::InvalidChildkeyTake ); - // Check if the rate limit has been exceeded - let current_block = Self::get_current_block_as_u64(); - let last_tx_block = - Self::get_last_transaction_block(&hotkey, netuid, &TransactionType::SetChildkeyTake); - let rate_limit = TxChildkeyTakeRateLimit::::get(); - let passes = - Self::passes_rate_limit_on_subnet(&TransactionType::SetChildkeyTake, &hotkey, netuid); - - log::info!( - "Rate limit check: current_block: {}, last_tx_block: {}, rate_limit: {}, passes: {}", - current_block, - last_tx_block, - rate_limit, - passes + // Ensure the hotkey passes the rate limit. + ensure!( + Self::passes_rate_limit_on_subnet( + &TransactionType::SetChildkeyTake, // Set childkey take. + &hotkey, // Specific to a hotkey. + netuid, // Specific to a subnet. + ), + Error::::TxChildkeyTakeRateLimitExceeded ); - ensure!(passes, Error::::TxChildkeyTakeRateLimitExceeded); + // Set last transaction block + let current_block = Self::get_current_block_as_u64(); + Self::set_last_transaction_block( + &hotkey, + netuid, + &TransactionType::SetChildkeyTake, + current_block + ); // Set the new childkey take value for the given hotkey and network ChildkeyTake::::insert(hotkey.clone(), netuid, take); From c68f4e466e19a2d44d43d72f919f10157f5a5b18 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 07:23:07 +0400 Subject: [PATCH 234/269] chore: remove commented test --- pallets/subtensor/tests/children.rs | 222 ---------------------------- 1 file changed, 222 deletions(-) diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index f66c88441..e5b5d080c 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -3238,225 +3238,3 @@ fn test_rank_trust_incentive_calculation_with_parent_child() { }); } -/// Test normal operation of childkey take -/// -/// This test verifies the correct distribution of rewards between child and parents. -/// -/// # Test Steps: -/// 1. Initialize test environment with a child and multiple parents -/// 2. Set childkey take to 9% (4915 when normalized to u16::MAX) -/// 3. Set up network parameters and register all neurons -/// 4. Set initial stakes for all neurons -/// 5. Run an epoch and process emissions -/// 6. Calculate expected reward distribution -/// 7. Compare actual distribution with expected distribution -/// -/// # Expected Results: -/// - Child should keep 9% of the rewards -/// - Remaining 91% should be distributed among parents proportional to their stake -/// - Total distributed rewards should equal total emissions - -/// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test children -- test_normal_childkey_take_operation --exact --nocapture - -// #[test] -// fn test_normal_childkey_take_operation() { -// new_test_ext(1).execute_with(|| { -// let netuid: u16 = 1; -// let num_neurons: u16 = 5; - -// // Create hotkeys and coldkeys -// let child_coldkey = U256::from(100); -// let child_hotkey = U256::from(0); -// let parent_coldkey = U256::from(101); -// let parent_hotkey = U256::from(1); -// let validator_coldkeys: Vec = (102..105).map(U256::from).collect(); -// let validator_hotkeys: Vec = (2..5).map(U256::from).collect(); - -// // Set childkey take to 9% (4915 when normalized to u16::MAX) -// let childkey_take: u16 = 4915; - -// // Set up network parameters and register neurons -// add_network(netuid, num_neurons, 0); -// SubtensorModule::set_max_registrations_per_block(netuid, 1000); -// SubtensorModule::set_target_registrations_per_interval(netuid, 1000); -// SubtensorModule::set_weights_set_rate_limit(netuid, 0); -// SubtensorModule::set_hotkey_emission_tempo(10); - -// register_ok_neuron(netuid, child_hotkey, child_coldkey, 0); -// register_ok_neuron(netuid, parent_hotkey, parent_coldkey, 0); -// for (&hotkey, &coldkey) in validator_hotkeys.iter().zip(validator_coldkeys.iter()) { -// register_ok_neuron(netuid, hotkey, coldkey, 0); -// } - -// // Set initial stakes -// let child_stake: u64 = 1_000_000; -// let parent_stake: u64 = 2_000_000; -// let validator_stakes: Vec = vec![3_000_000, 4_000_000, 5_000_000]; - -// SubtensorModule::add_balance_to_coldkey_account(&child_coldkey, child_stake); -// SubtensorModule::increase_stake_on_coldkey_hotkey_account( -// &child_coldkey, -// &child_hotkey, -// child_stake, -// ); - -// SubtensorModule::add_balance_to_coldkey_account(&parent_coldkey, parent_stake); -// SubtensorModule::increase_stake_on_coldkey_hotkey_account( -// &parent_coldkey, -// &parent_hotkey, -// parent_stake, -// ); - -// for (i, (&hotkey, &coldkey)) in validator_hotkeys -// .iter() -// .zip(validator_coldkeys.iter()) -// .enumerate() -// { -// SubtensorModule::add_balance_to_coldkey_account(&coldkey, validator_stakes[i]); -// SubtensorModule::increase_stake_on_coldkey_hotkey_account( -// &coldkey, -// &hotkey, -// validator_stakes[i], -// ); -// } - -// // Set up parent-child relationship -// assert_ok!(SubtensorModule::do_set_children( -// RuntimeOrigin::signed(parent_coldkey), -// parent_hotkey, -// netuid, -// vec![(u64::MAX, child_hotkey)] -// )); - -// // Set childkey take -// assert_ok!(SubtensorModule::do_set_childkey_take( -// child_coldkey, -// child_hotkey, -// childkey_take, -// netuid -// )); - -// // Set weights -// let all_uids: Vec = (0..num_neurons).collect(); -// let weights: Vec = vec![u16::MAX / num_neurons; num_neurons as usize]; - -// step_block(2); // Step to ensure weights are set - -// for &hotkey in std::iter::once(&parent_hotkey).chain(validator_hotkeys.iter()) { -// assert_ok!(SubtensorModule::set_weights( -// RuntimeOrigin::signed(hotkey), -// netuid, -// all_uids.clone(), -// weights.clone(), -// 0 -// )); -// } - -// // Run epoch and process emissions -// let rao_emission: u64 = 1_000_000_000; -// let emission = SubtensorModule::epoch(netuid, rao_emission); - -// // Store initial stakes -// let initial_stakes: Vec = [child_hotkey, parent_hotkey] -// .iter() -// .chain(validator_hotkeys.iter()) -// .map(|&hotkey| SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid)) -// .collect(); - -// log::info!("Initial stakes:"); -// log::info!("Child: {}", initial_stakes[0]); -// log::info!("Parent: {}", initial_stakes[1]); -// for (i, &stake) in initial_stakes.iter().skip(2).enumerate() { -// log::info!("Validator {}: {}", i, stake); -// } - -// // Accumulate emissions -// for (hotkey, mining_emission, validator_emission) in emission { -// SubtensorModule::accumulate_hotkey_emission( -// &hotkey, -// netuid, -// validator_emission, -// mining_emission, -// ); -// } - -// // Check pending emissions before distribution -// log::info!("\nPending emissions before distribution:"); -// let pending_emissions: Vec = [child_hotkey, parent_hotkey] -// .iter() -// .chain(validator_hotkeys.iter()) -// .map(|&hotkey| SubtensorModule::get_pending_hotkey_emission(&hotkey)) -// .collect(); - -// log::info!("Child: {}", pending_emissions[0]); -// log::info!("Parent: {}", pending_emissions[1]); -// for (i, &emission) in pending_emissions.iter().skip(2).enumerate() { -// log::info!("Validator {}: {}", i, emission); -// } - -// let total_pending_emission: u64 = pending_emissions.iter().sum(); -// log::info!("Total pending emission: {}", total_pending_emission); - -// log::info!("\nChildkey take: {}", childkey_take); - -// // Step block to trigger emission distribution -// step_block(11); - -// // Calculate actual rewards -// let final_stakes: Vec = [child_hotkey, parent_hotkey] -// .iter() -// .chain(validator_hotkeys.iter()) -// .map(|&hotkey| SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey, netuid)) -// .collect(); - -// let rewards: Vec = final_stakes -// .iter() -// .zip(initial_stakes.iter()) -// .map(|(&final_stake, &initial_stake)| final_stake - initial_stake) -// .collect(); - -// log::info!("\nRewards:"); -// log::info!("Child reward: {}", rewards[0]); -// log::info!("Parent reward: {}", rewards[1]); -// for (i, &reward) in rewards.iter().skip(2).enumerate() { -// log::info!("Validator {} reward: {}", i, reward); -// } - -// // Verify total distributed rewards -// let total_distributed: u64 = rewards.iter().sum(); -// log::info!("\nTotal distributed: {}", total_distributed); -// log::info!("Total emission: {}", total_pending_emission); - -// assert!( -// (total_distributed as i64 - total_pending_emission as i64).abs() <= 10, -// "Total distributed rewards mismatch: distributed {} vs emission {}", -// total_distributed, -// total_pending_emission -// ); - -// // Check that PendingdHotkeyEmission is cleared after distribution -// for &hotkey in [child_hotkey, parent_hotkey] -// .iter() -// .chain(validator_hotkeys.iter()) -// { -// assert_eq!( -// SubtensorModule::get_pending_hotkey_emission(&hotkey), -// 0, -// "Pending emission not cleared for hotkey {:?}", -// hotkey -// ); -// } - -// // Verify childkey take -// let child_reward = rewards[0]; -// let expected_child_reward = -// (total_pending_emission as u128 * childkey_take as u128 / u16::MAX as u128) as u64; -// log::info!("\nExpected child reward: {}", expected_child_reward); -// assert!( -// (child_reward as i64 - expected_child_reward as i64).abs() <= 1, -// "Child reward mismatch: actual {} vs expected {}", -// child_reward, -// expected_child_reward -// ); -// }); -// } From dc67f8fbfd0d4fb7035adc1d1e80408283d44d9e Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 07:28:41 +0400 Subject: [PATCH 235/269] chore: fmt --- pallets/subtensor/src/staking/set_children.rs | 4 ++-- pallets/subtensor/tests/children.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index ab69c3438..9029d58ca 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -74,7 +74,7 @@ impl Pallet { &hotkey, netuid, &TransactionType::SetChildren, - current_block + current_block, ); // --- 2. Check that this delegation is not on the root network. Child hotkeys are not valid on root. @@ -262,7 +262,7 @@ impl Pallet { &hotkey, netuid, &TransactionType::SetChildkeyTake, - current_block + current_block, ); // Set the new childkey take value for the given hotkey and network diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index e5b5d080c..f36c21521 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -3237,4 +3237,3 @@ fn test_rank_trust_incentive_calculation_with_parent_child() { }); } - From 74fcd80bf535b51ef2b8851d57a7d45a32cc46f8 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 08:12:14 +0400 Subject: [PATCH 236/269] chore: tidy --- pallets/subtensor/src/swap/swap_coldkey.rs | 8 -------- runtime/src/lib.rs | 4 ---- 2 files changed, 12 deletions(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 5b02d49b5..498549e5c 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -49,14 +49,6 @@ impl Pallet { ); weight = weight.saturating_add(T::DbWeight::get().reads(1)); - // TODO: Consider adding a check to ensure the new coldkey is not in arbitration - // ensure!( - // !Self::coldkey_in_arbitration(new_coldkey), - // Error::::NewColdkeyIsInArbitration - // ); - - // Note: We might want to add a cooldown period for coldkey swaps to prevent abuse - // 5. Calculate the swap cost and ensure sufficient balance let swap_cost = Self::get_key_swap_cost(); ensure!( diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 20d565f9a..f644d8a2a 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -97,10 +97,6 @@ type MemberCount = u32; pub type Nonce = u32; -/// The scheduler type used for scheduling delayed calls. -// With something like this: -// type Scheduler = pallet_subtensor::Scheduler; - // Method used to calculate the fee of an extrinsic pub const fn deposit(items: u32, bytes: u32) -> Balance { pub const ITEMS_FEE: Balance = 2_000 * 10_000; From ff87b7b541809273bcb5822d47ededd7ad7d7958 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 08:35:49 +0400 Subject: [PATCH 237/269] chore: use forked substrate fixed --- Cargo.lock | 2 +- Cargo.toml | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 350831723..f444c9ba9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9115,7 +9115,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=v1.10.0-rc3#8d2 [[package]] name = "substrate-fixed" version = "0.5.9" -source = "git+https://github.com/encointer/substrate-fixed.git?tag=v0.5.9#a4fb461aae6205ffc55bed51254a40c52be04e5d" +source = "git+https://github.com/opentensor/substrate-fixed.git?tag=v0.5.9#a4fb461aae6205ffc55bed51254a40c52be04e5d" dependencies = [ "parity-scale-codec", "scale-info", diff --git a/Cargo.toml b/Cargo.toml index 36a87755b..622570dc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,13 @@ serde_json = { version = "1.0.116", default-features = false } serde_with = { version = "=2.0.0", default-features = false } smallvec = "1.13.2" litep2p = { git = "https://github.com/paritytech/litep2p", branch = "master" } -syn = { version = "2", features = ["full", "visit-mut", "visit", "extra-traits", "parsing"] } +syn = { version = "2", features = [ + "full", + "visit-mut", + "visit", + "extra-traits", + "parsing", +] } quote = "1" proc-macro2 = { version = "1", features = ["span-locations"] } walkdir = "2" @@ -76,7 +82,7 @@ subtensor-macros = { path = "support/macros" } frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-benchmarking-cli = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } frame-executive = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } -frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" , default-features = false } +frame-metadata-hash-extension = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-support = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } frame-system-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } @@ -145,7 +151,7 @@ sp-version = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1 sp-weights = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3", default-features = false } substrate-build-script-utils = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } -substrate-fixed = { git = "https://github.com/encointer/substrate-fixed.git", tag = "v0.5.9" } +substrate-fixed = { git = "https://github.com/opentensor/substrate-fixed.git", tag = "v0.5.9" } substrate-frame-rpc-system = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk.git", tag = "v1.10.0-rc3" } frame-metadata = "16" From 8ab4573e79a18f39f7c5251e11c67a6504e7c2b6 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 19 Aug 2024 13:14:51 +0800 Subject: [PATCH 238/269] clean code and fix a bug --- pallets/subtensor/src/macros/dispatches.rs | 31 -- pallets/subtensor/tests/networks.rs | 409 --------------------- 2 files changed, 440 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 112ef10b5..21668c922 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -687,36 +687,6 @@ mod dispatches { Self::do_swap_coldkey(&old_coldkey, &new_coldkey) } - /// Unstakes all tokens associated with a hotkey and transfers them to a new coldkey. - /// - /// # Arguments - /// - /// * `origin` - The origin of the call, must be signed by the current coldkey. - /// * `hotkey` - The hotkey associated with the stakes to be unstaked. - /// * `new_coldkey` - The new coldkey to receive the unstaked tokens. - /// - /// # Returns - /// - /// Returns a `DispatchResult` indicating success or failure of the operation. - /// - /// # Weight - /// - /// Weight is calculated based on the number of database reads and writes. - #[cfg(test)] - #[pallet::call_index(72)] - #[pallet::weight((Weight::from_parts(21_000_000, 0) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)), DispatchClass::Operational, Pays::No))] - pub fn schedule_coldkey_swap( - _origin: OriginFor, - _new_coldkey: T::AccountId, - _work: Vec, - _block_number: u64, - _nonce: u64, - ) -> DispatchResult { - Ok(()) - } - // ---- SUDO ONLY FUNCTIONS ------------------------------------------------------------ // ================================== @@ -1032,7 +1002,6 @@ mod dispatches { ) .map_err(|_| Error::::FailedToSchedule)?; - ColdkeySwapScheduled::::insert(&who, ()); // Emit the SwapScheduled event Self::deposit_event(Event::DissolveNetworkScheduled { account: who.clone(), diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index 1929ff543..b41ff985d 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -94,412 +94,3 @@ fn test_schedule_dissolve_network_execution() { assert!(!SubtensorModule::if_subnet_exist(netuid)); }) } - -// #[allow(dead_code)] -// fn record(event: RuntimeEvent) -> EventRecord { -// EventRecord { -// phase: Phase::Initialization, -// event, -// topics: vec![], -// } -// } - -// /*TO DO SAM: write test for LatuUpdate after it is set */ -// // --- add network tests ---- -// #[test] -// fn test_add_network_dispatch_info_ok() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let modality = 0; -// let tempo: u16 = 13; -// let call = RuntimeCall::SubtensorModule(SubtensorCall::sudo_add_network { -// netuid, -// tempo, -// modality, -// }); -// assert_eq!( -// call.get_dispatch_info(), -// DispatchInfo { -// weight: frame_support::weights::Weight::from_parts(50000000, 0), -// class: DispatchClass::Operational, -// pays_fee: Pays::No -// } -// ); -// }); -// } - -// #[test] -// fn test_add_network() { -// new_test_ext().execute_with(|| { -// let modality = 0; -// let tempo: u16 = 13; -// add_network(10, tempo, modality); -// assert_eq!(SubtensorModule::get_number_of_subnets(), 1); -// add_network(20, tempo, modality); -// assert_eq!(SubtensorModule::get_number_of_subnets(), 2); -// }); -// } - -// #[test] -// fn test_add_network_check_tempo() { -// new_test_ext().execute_with(|| { -// let modality = 0; -// let tempo: u16 = 13; -// assert_eq!(SubtensorModule::get_tempo(1), 0); -// add_network(1, tempo, modality); -// assert_eq!(SubtensorModule::get_tempo(1), 13); -// }); -// } - -// #[test] -// fn test_clear_min_allowed_weight_for_network() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let min_allowed_weight = 2; -// let tempo: u16 = 13; -// add_network(netuid, tempo, 0); -// register_ok_neuron(1, U256::from(55), U256::from(66), 0); -// SubtensorModule::set_min_allowed_weights(netuid, min_allowed_weight); -// assert_eq!(SubtensorModule::get_min_allowed_weights(netuid), 2); -// assert_ok!(SubtensorModule::do_remove_network( -// <::RuntimeOrigin>::root(), -// netuid -// )); -// assert_eq!(SubtensorModule::get_min_allowed_weights(netuid), 0); -// }); -// } - -// #[test] -// fn test_remove_uid_for_network() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let tempo: u16 = 13; -// add_network(netuid, tempo, 0); -// register_ok_neuron(1, U256::from(55), U256::from(66), 0); -// let neuron_id; -// match SubtensorModule::get_uid_for_net_and_hotkey(netuid, &U256::from(55)) { -// Ok(k) => neuron_id = k, -// Err(e) => panic!("Error: {:?}", e), -// } -// assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &U256::from(55)).is_ok()); -// assert_eq!(neuron_id, 0); -// register_ok_neuron(1, U256::from(56), U256::from(67), 300000); -// let neuron_uid = -// SubtensorModule::get_uid_for_net_and_hotkey(netuid, &U256::from(56)).unwrap(); -// assert_eq!(neuron_uid, 1); -// assert_ok!(SubtensorModule::do_remove_network( -// <::RuntimeOrigin>::root(), -// netuid -// )); -// assert!(SubtensorModule::get_uid_for_net_and_hotkey(netuid, &U256::from(55)).is_err()); -// }); -// } - -// #[test] -// fn test_remove_difficulty_for_network() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let difficulty: u64 = 10; -// let tempo: u16 = 13; -// add_network(netuid, tempo, 0); -// register_ok_neuron(1, U256::from(55), U256::from(66), 0); -// assert_ok!(SubtensorModule::sudo_set_difficulty( -// <::RuntimeOrigin>::root(), -// netuid, -// difficulty -// )); -// assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), difficulty); -// assert_ok!(SubtensorModule::do_remove_network( -// <::RuntimeOrigin>::root(), -// netuid -// )); -// assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 10000); -// }); -// } - -// #[test] -// fn test_remove_network_for_all_hotkeys() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let tempo: u16 = 13; -// add_network(netuid, tempo, 0); -// register_ok_neuron(1, U256::from(55), U256::from(66), 0); -// register_ok_neuron(1, U256::from(77), U256::from(88), 65536); -// assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 2); -// assert_ok!(SubtensorModule::do_remove_network( -// <::RuntimeOrigin>::root(), -// netuid -// )); -// assert_eq!(SubtensorModule::get_subnetwork_n(netuid), 0); -// }); -// } - -// #[test] -// fn test_network_set_default_value_for_other_parameters() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// let tempo: u16 = 13; -// add_network(netuid, tempo, 0); -// assert_eq!(SubtensorModule::get_min_allowed_weights(netuid), 0); -// assert_eq!(SubtensorModule::get_emission_value(netuid), 0); -// assert_eq!(SubtensorModule::get_max_weight_limit(netuid), u16::MAX); -// assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), 10000); -// assert_eq!(SubtensorModule::get_immunity_period(netuid), 2); -// }); -// } - -// // --- Set Emission Ratios Tests -// #[test] -// fn test_network_set_emission_ratios_dispatch_info_ok() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![100000000, 900000000]; -// let call = RuntimeCall::SubtensorModule(SubtensorCall::sudo_set_emission_values { -// netuids, -// emission, -// }); -// assert_eq!( -// call.get_dispatch_info(), -// DispatchInfo { -// weight: frame_support::weights::Weight::from_parts(28000000, 0), -// class: DispatchClass::Operational, -// pays_fee: Pays::No -// } -// ); -// }); -// } - -// #[test] -// fn test_network_set_emission_ratios_ok() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![100000000, 900000000]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_ok!(SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// )); -// }); -// } - -// #[test] -// fn test_network_set_emission_ratios_fail_summation() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![100000000, 910000000]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::InvalidEmissionValues.into()) -// ); -// }); -// } - -// #[test] -// fn test_network_set_emission_invalid_netuids() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![100000000, 900000000]; -// add_network(1, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::IncorrectNetuidsLength.into()) -// ); -// }); -// } - -// #[test] -// fn test_network_set_emission_ratios_fail_net() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![100000000, 900000000]; -// add_network(1, 0, 0); -// add_network(3, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::UidVecContainInvalidOne.into()) -// ); -// }); -// } - -// #[test] -// fn test_add_difficulty_fail() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// assert_eq!( -// SubtensorModule::sudo_set_difficulty( -// <::RuntimeOrigin>::root(), -// netuid, -// 120000 -// ), -// Err(Error::::NetworkDoesNotExist.into()) -// ); -// }); -// } - -// #[test] -// fn test_multi_tempo_with_emission() { -// new_test_ext().execute_with(|| { -// let netuid: u16 = 1; -// assert_eq!( -// SubtensorModule::sudo_set_difficulty( -// <::RuntimeOrigin>::root(), -// netuid, -// 120000 -// ), -// Err(Error::::NetworkDoesNotExist.into()) -// ); -// }); -// } - -// #[test] -// // Required by the test otherwise it would panic if compiled in debug mode -// #[allow(arithmetic_overflow)] -// fn test_set_emission_values_errors_on_emission_sum_overflow() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// // u64(u64::MAX + 1..000..1) equals to 1_000_000_000 which is the same as -// // the value of Self::get_block_emission() expected by the extrinsic -// let emission: Vec = vec![u64::MAX, 1_000_000_001]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::InvalidEmissionValues.into()) -// ); -// }); -// } - -// #[test] -// #[allow(arithmetic_overflow)] -// fn test_set_emission_values_no_errors() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// let emission: Vec = vec![600_000_000, 400_000_000]; - -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Ok(()) -// ); -// }); -// } - -// #[test] -// // Required by the test otherwise it would panic if compiled in debug mode -// #[allow(arithmetic_overflow)] -// fn test_set_emission_values_sum_too_large() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// // u64(1_000_000_000 + 1) equals to 1_000_000_001 which is more than -// // the value of Self::get_block_emission() expected by the extrinsic -// let emission: Vec = vec![1_000_000_000, 1]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::InvalidEmissionValues.into()) -// ); -// }); -// } - -// #[test] -// // Required by the test otherwise it would panic if compiled in debug mode -// #[allow(arithmetic_overflow)] -// fn test_set_emission_values_sum_too_small() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2]; -// // u64(1 + 2_000) equals to 2_001 which is LESS than -// // the value of Self::get_block_emission() expected by the extrinsic -// let emission: Vec = vec![1, 2_000]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::InvalidEmissionValues.into()) -// ); -// }); -// } - -// #[test] -// fn test_set_emission_values_too_many_netuids() { -// new_test_ext().execute_with(|| { -// let netuids: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - -// // Sums to 1_000_000_000 and has 10 elements -// let emission: Vec = vec![1_000_000_000, 0, 0, 0, 0, 0, 0, 0, 0, 0]; -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// // We only add 2 networks, so this should fail -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::IncorrectNetuidsLength.into()) -// ); -// }); -// } - -// #[test] -// fn test_set_emission_values_over_u16_max_values() { -// new_test_ext().execute_with(|| { -// // Make vec of u16 with length 2^16 + 2 -// let netuids: Vec = vec![0; 0x10002]; -// // This is greater than u16::MAX -// assert!(netuids.len() > u16::MAX as usize); -// // On cast to u16, this will be 2 -// assert!(netuids.len() as u16 == 2); - -// // Sums to 1_000_000_000 and the length is 65536 -// let mut emission: Vec = vec![0; netuids.len()]; -// emission[0] = 1_000_000_000; - -// add_network(1, 0, 0); -// add_network(2, 0, 0); -// // We only add 2 networks, so this should fail -// // but if we cast to u16 during length comparison, -// // the length will be 2 and the check will pass -// assert_eq!( -// SubtensorModule::sudo_set_emission_values( -// <::RuntimeOrigin>::root(), -// netuids, -// emission -// ), -// Err(Error::::IncorrectNetuidsLength.into()) -// ); -// }); -// } From e2f372bc33ec9b4b7a1c9ee69cccd542739abc09 Mon Sep 17 00:00:00 2001 From: open-junius Date: Mon, 19 Aug 2024 13:37:45 +0800 Subject: [PATCH 239/269] remove unused dispatch --- pallets/subtensor/src/macros/dispatches.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 21668c922..b1eda9ea6 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -811,17 +811,6 @@ mod dispatches { Self::user_remove_network(origin, netuid) } - /// Sets values for liquid alpha - #[pallet::call_index(64)] - #[pallet::weight((0, DispatchClass::Operational, Pays::No))] - pub fn sudo_hotfix_swap_coldkey_delegates( - _origin: OriginFor, - _old_coldkey: T::AccountId, - _new_coldkey: T::AccountId, - ) -> DispatchResult { - Ok(()) - } - /// Set a single child for a given hotkey on a specified network. /// /// This function allows a coldkey to set a single child for a given hotkey on a specified network. From 9cbc5a21469213bd4cd66223b57a2d3e518ab801 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 19 Aug 2024 09:55:49 -0400 Subject: [PATCH 240/269] Fix merge conflict --- pallets/admin-utils/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 46b42fbc9..3f221fd30 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -251,7 +251,7 @@ mod benchmarks { #[benchmark] fn sudo_set_hotkey_emission_tempo() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u64/*emission_tempo*/)/*set_hotkey_emission_tempo*/; @@ -259,7 +259,7 @@ mod benchmarks { #[benchmark] fn sudo_set_network_max_stake() { - T::Subtensor::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); #[extrinsic_call] _(RawOrigin::Root, 1u16/*netuid*/, 1_000_000_000_000_000u64/*max_stake*/)/*sudo_set_network_max_stake*/; From bb66f09f38a3fb4d0a06906c53b833cb3057f392 Mon Sep 17 00:00:00 2001 From: Keith Date: Mon, 19 Aug 2024 10:01:03 -0400 Subject: [PATCH 241/269] cargo fmt --- pallets/admin-utils/src/benchmarking.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 3f221fd30..7515525f0 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -251,7 +251,10 @@ mod benchmarks { #[benchmark] fn sudo_set_hotkey_emission_tempo() { - pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*sudo_tempo*/); + pallet_subtensor::Pallet::::init_new_network( + 1u16, /*netuid*/ + 1u16, /*sudo_tempo*/ + ); #[extrinsic_call] _(RawOrigin::Root, 1u64/*emission_tempo*/)/*set_hotkey_emission_tempo*/; From 57ed878d7561fddbc27f7c9ca47ae845533d8192 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:56:35 -0700 Subject: [PATCH 242/269] fix pallet index conflict --- pallets/subtensor/src/macros/dispatches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 08a3c578e..f96170626 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1080,7 +1080,7 @@ mod dispatches { /// /// * `subnet_contact` (Vec): /// - The contact information for the subnet. - #[pallet::call_index(69)] + #[pallet::call_index(73)] #[pallet::weight((Weight::from_parts(45_000_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] From 90477819b500433e0ceb5feb7816b8644fe1661b Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 21:49:01 +0400 Subject: [PATCH 243/269] chore: max childkey take --- pallets/admin-utils/tests/mock.rs | 2 ++ pallets/subtensor/src/macros/config.rs | 3 +++ pallets/subtensor/tests/mock.rs | 6 ++++-- runtime/src/lib.rs | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 688d666be..24475a8c6 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -81,6 +81,7 @@ parameter_types! { pub const InitialMinDelegateTake: u16 = 5_898; // 9%; pub const InitialDefaultChildKeyTake: u16 = 0; // Allow 0 % pub const InitialMinChildKeyTake: u16 = 0; // Allow 0 % + pub const InitialMaxChildKeyTake: u16 = 11_796; // 18 %; pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing @@ -153,6 +154,7 @@ impl pallet_subtensor::Config for Test { type InitialMinDelegateTake = InitialMinDelegateTake; type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; type InitialMinChildKeyTake = InitialMinChildKeyTake; + type InitialMaxChildKeyTake = InitialMaxChildKeyTake; type InitialWeightsVersionKey = InitialWeightsVersionKey; type InitialMaxDifficulty = InitialMaxDifficulty; type InitialMinDifficulty = InitialMinDifficulty; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 2a6d8db00..5b2d0f25e 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -122,6 +122,9 @@ mod config { /// Initial minimum childkey take. #[pallet::constant] type InitialMinChildKeyTake: Get; + /// Initial maximum childkey take. + #[pallet::constant] + type InitialMaxChildKeyTake: Get; /// Initial weights version key. #[pallet::constant] type InitialWeightsVersionKey: Get; diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 022849c56..6785bdb02 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -133,8 +133,9 @@ parameter_types! { pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production pub const InitialMinDelegateTake: u16 = 5_898; // 9%; - pub const InitialDefaultChildKeyTake: u16 = 11_796; // 18%, same as in production + pub const InitialDefaultChildKeyTake: u16 = 0 ;// 0 % pub const InitialMinChildKeyTake: u16 = 0; // 0 %; + pub const InitialMaxChildKeyTake: u16 = 11_796; // 18 %; pub const InitialWeightsVersionKey: u16 = 0; pub const InitialServingRateLimit: u64 = 0; // No limit. pub const InitialTxRateLimit: u64 = 0; // Disable rate limit for testing @@ -172,7 +173,7 @@ 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 InitialHotkeyEmissionTempo: u64 = 0; // Defaults to draining every block. - pub const InitialNetworkMaxStake: u64 = 500_000_000_000_000; // 500,000 TAO + pub const InitialNetworkMaxStake: u64 = u64::MAX; // Maximum possible value for u64 } @@ -367,6 +368,7 @@ impl pallet_subtensor::Config for Test { type InitialMinDelegateTake = InitialMinDelegateTake; type InitialDefaultChildKeyTake = InitialDefaultChildKeyTake; type InitialMinChildKeyTake = InitialMinChildKeyTake; + type InitialMaxChildKeyTake = InitialMaxChildKeyTake; type InitialTxChildKeyTakeRateLimit = InitialTxChildKeyTakeRateLimit; type InitialWeightsVersionKey = InitialWeightsVersionKey; type InitialMaxDifficulty = InitialMaxDifficulty; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 166a686e4..ab88db026 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -910,6 +910,7 @@ parameter_types! { pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take pub const SubtensorInitialMinChildKeyTake: u16 = 0; // 0 % + pub const SubtensorInitialMaxChildKeyTake: u16 = 11_796; // 18 % pub const SubtensorInitialWeightsVersionKey: u64 = 0; pub const SubtensorInitialMinDifficulty: u64 = 10_000_000; pub const SubtensorInitialMaxDifficulty: u64 = u64::MAX / 4; @@ -981,6 +982,7 @@ impl pallet_subtensor::Config for Runtime { type InitialTxRateLimit = SubtensorInitialTxRateLimit; type InitialTxDelegateTakeRateLimit = SubtensorInitialTxDelegateTakeRateLimit; type InitialTxChildKeyTakeRateLimit = SubtensorInitialTxChildKeyTakeRateLimit; + type InitialMaxChildKeyTake = SubtensorInitialMaxChildKeyTake; type InitialRAORecycledForRegistration = SubtensorInitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = SubtensorInitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = SubtensorInitialNetworkImmunity; From b60176d6a1611c75fc73d5566673948b1ac25287 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 19 Aug 2024 22:02:05 +0400 Subject: [PATCH 244/269] chore: add vars --- pallets/subtensor/src/lib.rs | 9 ++++++++- pallets/subtensor/tests/children.rs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index f264ca323..1966f64ab 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -181,12 +181,19 @@ pub mod pallet { pub fn DefaultMinDelegateTake() -> u16 { T::InitialMinDelegateTake::get() } + #[pallet::type_value] /// Default minimum childkey take. pub fn DefaultMinChildKeyTake() -> u16 { T::InitialMinChildKeyTake::get() } + #[pallet::type_value] + /// Default maximum childkey take. + pub fn DefaultMaxChildKeyTake() -> u16 { + T::InitialMaxChildKeyTake::get() + } + #[pallet::type_value] /// Default account take. pub fn DefaultAccountTake() -> u64 { @@ -619,7 +626,7 @@ pub mod pallet { #[pallet::storage] // --- ITEM ( min_delegate_take ) pub type MinDelegateTake = StorageValue<_, u16, ValueQuery, DefaultMinDelegateTake>; #[pallet::storage] // --- ITEM ( default_childkey_take ) - pub type MaxChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultChildKeyTake>; + pub type MaxChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMaxChildKeyTake>; #[pallet::storage] // --- ITEM ( min_childkey_take ) pub type MinChildkeyTake = StorageValue<_, u16, ValueQuery, DefaultMinChildKeyTake>; diff --git a/pallets/subtensor/tests/children.rs b/pallets/subtensor/tests/children.rs index f36c21521..2b99030ab 100644 --- a/pallets/subtensor/tests/children.rs +++ b/pallets/subtensor/tests/children.rs @@ -1499,7 +1499,7 @@ fn test_get_network_max_stake() { let default_max_stake = SubtensorModule::get_network_max_stake(netuid); // Check that the default value is set correctly - assert_eq!(default_max_stake, 500_000_000_000_000); + assert_eq!(default_max_stake, u64::MAX); // Set a new max stake value let new_max_stake: u64 = 1_000_000; From 1e4312b3c7fd2d33b1765f41522c48a2d28244bd Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 19 Aug 2024 19:42:11 -0400 Subject: [PATCH 245/269] fix workspace root package --- Cargo.lock | 3 --- Cargo.toml | 8 +++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 698607e7c..ee0933379 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9193,13 +9193,10 @@ version = "0.1.0" dependencies = [ "node-subtensor", "node-subtensor-runtime", - "pallet-commitments", - "pallet-subtensor", "proc-macro2", "quote", "rayon", "subtensor-linting", - "subtensor-macros", "syn 2.0.71", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index b8fe8c4d4..e3c2814ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,10 +11,7 @@ repository = "https://github.com/opentensor/subtensor" [dependencies] node-subtensor = { path = "node", version = "4.0.0-dev" } -pallet-commitments = { path = "pallets/commitments", version = "4.0.0-dev" } -pallet-subtensor = { path = "pallets/subtensor", version = "4.0.0-dev" } node-subtensor-runtime = { path = "runtime", version = "4.0.0-dev" } -subtensor-macros = { path = "support/macros", version = "0.1.0" } [build-dependencies] subtensor-linting = { path = "support/linting", version = "0.1.0" } @@ -167,3 +164,8 @@ opt-level = 3 inherits = "release" lto = true codegen-units = 1 + +[features] +default = [] +try-runtime = ["node-subtensor/try-runtime", "node-subtensor-runtime/try-runtime"] +runtime-benchmarks = ["node-subtensor/runtime-benchmarks", "node-subtensor-runtime/runtime-benchmarks"] From 917217f88986066b0a574522ce4f93beec7e8881 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Mon, 19 Aug 2024 19:46:38 -0400 Subject: [PATCH 246/269] bump CI From 6b621e63d9fb351faf19dd2f89796d69f077fc2b Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 20 Aug 2024 19:42:20 +0800 Subject: [PATCH 247/269] add sudo call to set duration --- pallets/admin-utils/src/lib.rs | 68 +++++++++++++++++++++++++ pallets/admin-utils/tests/tests.rs | 70 +++++++++++++++++++++++++- pallets/subtensor/src/macros/events.rs | 4 ++ pallets/subtensor/src/utils/misc.rs | 32 +++++++++++- 4 files changed, 172 insertions(+), 2 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index baf711b85..3e06b822e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -4,6 +4,7 @@ pub use pallet::*; pub mod weights; pub use weights::WeightInfo; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{traits::Member, RuntimeAppPublic}; mod benchmarking; @@ -1128,6 +1129,73 @@ pub mod pallet { Ok(()) } + + /// 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((0, DispatchClass::Operational, Pays::No))] + 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. + /// The dissolve network schedule determines how long it takes for a network dissolution operation to complete. + /// + /// # Arguments + /// * `origin` - The origin of the call, which must be the root account. + /// * `duration` - The new duration for the dissolve network 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(55)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_dissolve_network_schedule_duration( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + // Ensure the call is made by the root account + ensure_root(origin)?; + + // Set the duration of schedule dissolve network + pallet_subtensor::Pallet::::set_dissolve_network_schedule_duration(duration); + + // Log the change + log::trace!( + "DissolveNetworkScheduleDurationSet( duration: {:?} )", + duration + ); + + Ok(()) + } } } diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index af3bf66d7..3e29a8daa 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1,6 +1,6 @@ use frame_support::sp_runtime::DispatchError; use frame_support::{ - assert_err, assert_ok, + assert_err, assert_noop, assert_ok, dispatch::{DispatchClass, GetDispatchInfo, Pays}, }; use frame_system::Config; @@ -1361,3 +1361,71 @@ fn test_sudo_get_set_alpha() { )); }); } + +#[test] +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_duration = 100u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + 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_schedule_duration( + root.clone(), + new_duration + )); + + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::ColdkeySwapScheduleDuration::::get(), + new_duration + ); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + 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::ColdkeySwapScheduleDurationSet(new_duration).into()); + }); +} + +#[test] +fn test_sudo_set_dissolve_network_schedule_duration() { + new_test_ext().execute_with(|| { + // Arrange + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 200u32.into(); + + // Act & Assert: Non-root account should fail + assert_noop!( + AdminUtils::sudo_set_dissolve_network_schedule_duration(non_root, new_duration), + DispatchError::BadOrigin + ); + + // Act: Root account should succeed + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root.clone(), + new_duration + )); + + // Act & Assert: Setting the same value again should succeed (idempotent operation) + assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( + root, + new_duration + )); + + // You might want to check for events here if your pallet emits them + System::assert_last_event(Event::DissolveNetworkScheduleDurationSet(new_duration).into()); + }); +} diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index b5e34b842..9d8530504 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -196,5 +196,9 @@ mod events { /// extrinsic execution block number execution_block: BlockNumberFor, }, + /// The duration of schedule coldkey swap has been set + ColdkeySwapScheduleDurationSet(BlockNumberFor), + /// The duration of dissolve network has been set + DissolveNetworkScheduleDurationSet(BlockNumberFor), } } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 0e1038b18..76546a1a2 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - system::{ensure_root, ensure_signed_or_root}, + system::{ensure_root, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, Error, }; use sp_core::Get; @@ -750,4 +750,34 @@ impl Pallet { // Emit an event to notify listeners about the change Self::deposit_event(Event::NetworkMaxStakeSet(netuid, max_stake)); } + + /// 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 + /// + /// # Arguments + /// + /// * `duration` - The blocks for dissolve network execution. + /// + /// # Effects + /// + /// * Update the DissolveNetworkScheduleDuration storage. + /// * Emits a DissolveNetworkScheduleDurationSet evnet. + pub fn set_dissolve_network_schedule_duration(duration: BlockNumberFor) { + DissolveNetworkScheduleDuration::::set(duration); + Self::deposit_event(Event::DissolveNetworkScheduleDurationSet(duration)); + } } From 092a3736f35d5424e648ef8003e718492bc3c396 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 20 Aug 2024 19:51:23 +0800 Subject: [PATCH 248/269] add value check --- pallets/admin-utils/tests/tests.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index 3e29a8daa..8ab85f177 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1419,6 +1419,12 @@ fn test_sudo_set_dissolve_network_schedule_duration() { new_duration )); + // Assert: Check if the duration was actually set + assert_eq!( + pallet_subtensor::DissolveNetworkScheduleDuration::::get(), + new_duration + ); + // Act & Assert: Setting the same value again should succeed (idempotent operation) assert_ok!(AdminUtils::sudo_set_dissolve_network_schedule_duration( root, From 9be558ead7ea78bb64d3f02f65b0241a50d78223 Mon Sep 17 00:00:00 2001 From: open-junius Date: Tue, 20 Aug 2024 20:44:44 +0800 Subject: [PATCH 249/269] upgrade version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e6c0e9d32..9ad0624d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -142,7 +142,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: 193, + spec_version: 194, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 166674684f9e733aadef51ef4c66789c4168419d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 20 Aug 2024 07:46:25 -0700 Subject: [PATCH 250/269] fix pallet index conflict --- pallets/subtensor/src/macros/dispatches.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 92d9f329f..9ad71d0c9 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1179,7 +1179,7 @@ mod dispatches { /// /// * `subnet_contact` (Vec): /// - The contact information for the subnet. - #[pallet::call_index(73)] + #[pallet::call_index(78)] #[pallet::weight((Weight::from_parts(45_000_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::Yes))] From 8d9bb2db8e859c3a9fef0c6b685c6c872cda1715 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 21 Aug 2024 00:58:12 +0800 Subject: [PATCH 251/269] add more test --- pallets/subtensor/tests/networks.rs | 190 +++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index b41ff985d..ec99a6f82 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -1,7 +1,7 @@ use crate::mock::*; use frame_support::assert_ok; use frame_system::Config; -use pallet_subtensor::{DissolveNetworkScheduleDuration, Event}; +use pallet_subtensor::{ColdkeySwapScheduleDuration, DissolveNetworkScheduleDuration, Event}; use sp_core::U256; mod mock; @@ -94,3 +94,191 @@ fn test_schedule_dissolve_network_execution() { assert!(!SubtensorModule::if_subnet_exist(netuid)); }) } + +#[test] +fn test_non_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let non_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(non_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: non_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_new_owner_schedule_dissolve_network_execution() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid, + execution_block, + } + .into(), + ); + run_to_block(current_block + 1); + // become network owner after call scheduled + pallet_subtensor::SubnetOwner::::insert(netuid, new_network_owner_account_id); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} + +#[test] +fn test_schedule_dissolve_network_execution_with_coldkey_swap() { + new_test_ext(1).execute_with(|| { + let block_number: u64 = 0; + let netuid: u16 = 2; + let tempo: u16 = 13; + let hotkey_account_id: U256 = U256::from(1); + let coldkey_account_id = U256::from(0); // Neighbour of the beast, har har + let new_network_owner_account_id = U256::from(2); // + + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 1000000000000000); + + let (nonce, work): (u64, Vec) = SubtensorModule::create_work_for_block_number( + netuid, + block_number, + 129123813, + &hotkey_account_id, + ); + + //add network + add_network(netuid, tempo, 0); + + assert_ok!(SubtensorModule::register( + <::RuntimeOrigin>::signed(hotkey_account_id), + netuid, + block_number, + nonce, + work.clone(), + hotkey_account_id, + coldkey_account_id + )); + + assert!(SubtensorModule::if_subnet_exist(netuid)); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(coldkey_account_id), + new_network_owner_account_id + )); + + let current_block = System::block_number(); + let execution_block = current_block + ColdkeySwapScheduleDuration::::get(); + + run_to_block(execution_block - 1); + + // the account is not network owner when schedule the call + assert_ok!(SubtensorModule::schedule_dissolve_network( + <::RuntimeOrigin>::signed(new_network_owner_account_id), + netuid + )); + + System::assert_last_event( + Event::DissolveNetworkScheduled { + account: new_network_owner_account_id, + netuid: netuid, + execution_block: DissolveNetworkScheduleDuration::::get() + execution_block + - 1, + } + .into(), + ); + + run_to_block(execution_block); + assert_eq!( + pallet_subtensor::SubnetOwner::::get(netuid), + new_network_owner_account_id + ); + + let current_block = System::block_number(); + let execution_block = current_block + DissolveNetworkScheduleDuration::::get(); + + run_to_block(execution_block); + // network exists since the caller is no the network owner + assert!(!SubtensorModule::if_subnet_exist(netuid)); + }) +} From 9424806cff1df0b0a75e09bf7da8476f69e02415 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 21 Aug 2024 01:18:35 +0800 Subject: [PATCH 252/269] fix clippy --- pallets/subtensor/tests/networks.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index ec99a6f82..a62459e13 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -261,7 +261,7 @@ fn test_schedule_dissolve_network_execution_with_coldkey_swap() { System::assert_last_event( Event::DissolveNetworkScheduled { account: new_network_owner_account_id, - netuid: netuid, + netuid, execution_block: DissolveNetworkScheduleDuration::::get() + execution_block - 1, } From 8ea7255c60f68ef2370c79b1f01045dbd69ea102 Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 21 Aug 2024 20:08:02 +0800 Subject: [PATCH 253/269] update dissolve network origin --- pallets/subtensor/src/benchmarks.rs | 2 +- pallets/subtensor/src/coinbase/root.rs | 3 +-- pallets/subtensor/src/macros/dispatches.rs | 18 +++++++++++++----- pallets/subtensor/tests/networks.rs | 2 +- pallets/subtensor/tests/root.rs | 14 ++++++++++---- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index e9d5f804c..2af7734f4 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -312,7 +312,7 @@ benchmarks! { let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); - }: dissolve_network(RawOrigin::Signed(coldkey), 1) + }: dissolve_network(RawOrigin::Root, coldkey, 1) // swap_hotkey { // let seed: u32 = 1; diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 39f745b93..f7b523c61 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -992,9 +992,8 @@ impl Pallet { /// * 'SubNetworkDoesNotExist': If the specified network does not exist. /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// - pub fn user_remove_network(origin: T::RuntimeOrigin, netuid: u16) -> dispatch::DispatchResult { + pub fn user_remove_network(coldkey: T::AccountId, netuid: u16) -> dispatch::DispatchResult { // --- 1. Ensure the function caller is a signed user. - let coldkey = ensure_signed(origin)?; // --- 2. Ensure this subnet exists. ensure!( diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index ce3f92879..724c82a50 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -681,7 +681,7 @@ mod dispatches { new_coldkey: T::AccountId, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) - ensure_root(origin.clone())?; + ensure_root(origin)?; log::info!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); Self::do_swap_coldkey(&old_coldkey, &new_coldkey) @@ -930,8 +930,13 @@ mod dispatches { #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::No))] - pub fn dissolve_network(origin: OriginFor, netuid: u16) -> DispatchResult { - Self::user_remove_network(origin, netuid) + pub fn dissolve_network( + origin: OriginFor, + coldkey: T::AccountId, + netuid: u16, + ) -> DispatchResult { + ensure_root(origin)?; + Self::user_remove_network(coldkey, netuid) } /// Set a single child for a given hotkey on a specified network. @@ -1100,7 +1105,10 @@ mod dispatches { let duration: BlockNumberFor = DissolveNetworkScheduleDuration::::get(); let when: BlockNumberFor = current_block.saturating_add(duration); - let call = Call::::dissolve_network { netuid }; + let call = Call::::dissolve_network { + coldkey: who.clone(), + netuid, + }; let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) .map_err(|_| Error::::FailedToSchedule)?; @@ -1109,7 +1117,7 @@ mod dispatches { DispatchTime::At(when), None, 63, - frame_system::RawOrigin::Signed(who.clone()).into(), + frame_system::RawOrigin::Root.into(), bound_call, ) .map_err(|_| Error::::FailedToSchedule)?; diff --git a/pallets/subtensor/tests/networks.rs b/pallets/subtensor/tests/networks.rs index a62459e13..3d3644236 100644 --- a/pallets/subtensor/tests/networks.rs +++ b/pallets/subtensor/tests/networks.rs @@ -35,7 +35,7 @@ fn test_registration_ok() { )); assert_ok!(SubtensorModule::user_remove_network( - <::RuntimeOrigin>::signed(coldkey_account_id), + coldkey_account_id, netuid )); diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 84df71d83..38b938ce5 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -914,7 +914,8 @@ fn test_dissolve_network_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)) @@ -937,7 +938,8 @@ fn test_dissolve_network_refund_coldkey_ok() { assert!(SubtensorModule::if_subnet_exist(netuid)); assert_ok!(SubtensorModule::dissolve_network( - RuntimeOrigin::signed(owner_coldkey), + RuntimeOrigin::root(), + owner_coldkey, netuid )); assert!(!SubtensorModule::if_subnet_exist(netuid)); @@ -961,7 +963,11 @@ fn test_dissolve_network_not_owner_err() { register_ok_neuron(netuid, hotkey, owner_coldkey, 3); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(random_coldkey), netuid), + SubtensorModule::dissolve_network( + RuntimeOrigin::signed(random_coldkey), + random_coldkey, + netuid + ), Error::::NotSubnetOwner ); }); @@ -974,7 +980,7 @@ fn test_dissolve_network_does_not_exist_err() { let coldkey = U256::from(2); assert_err!( - SubtensorModule::dissolve_network(RuntimeOrigin::signed(coldkey), netuid), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), coldkey, netuid), Error::::SubNetworkDoesNotExist ); }); From 039bb08dbcb4cf8af79c3bed2e338ae944d9ce7b Mon Sep 17 00:00:00 2001 From: open-junius Date: Wed, 21 Aug 2024 20:20:47 +0800 Subject: [PATCH 254/269] fix unit test --- pallets/subtensor/tests/root.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 38b938ce5..9b0c76965 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -963,11 +963,7 @@ fn test_dissolve_network_not_owner_err() { register_ok_neuron(netuid, hotkey, owner_coldkey, 3); assert_err!( - SubtensorModule::dissolve_network( - RuntimeOrigin::signed(random_coldkey), - random_coldkey, - netuid - ), + SubtensorModule::dissolve_network(RuntimeOrigin::root(), random_coldkey, netuid), Error::::NotSubnetOwner ); }); From 120da503645d69049d61fe73d2862892b22e550d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:50:01 -1000 Subject: [PATCH 255/269] fix tests and benchmarks --- pallets/subtensor/src/benchmarks.rs | 5 ++-- pallets/subtensor/tests/swap_coldkey.rs | 35 +++++++++---------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index f6463fd57..153d84b89 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -444,7 +444,7 @@ reveal_weights { let new_rate_limit: u64 = 100; }: sudo_set_tx_childkey_take_rate_limit(RawOrigin::Root, new_rate_limit) -benchmark_set_childkey_take { + benchmark_set_childkey_take { // Setup let netuid: u16 = 1; let tempo: u16 = 1; @@ -462,6 +462,7 @@ benchmark_set_childkey_take { Subtensor::::add_balance_to_coldkey_account(&coldkey, amount_to_be_staked); assert_ok!(Subtensor::::do_burned_registration(RawOrigin::Signed(coldkey.clone()).into(), netuid, hotkey.clone())); }: set_childkey_take(RawOrigin::Signed(coldkey), hotkey, netuid, take) + swap_coldkey { // Set up initial state let old_coldkey: T::AccountId = account("old_coldkey", 0, 0); @@ -517,6 +518,6 @@ benchmark_set_childkey_take { Identities::::insert(&old_coldkey, identity); // Benchmark setup complete, now execute the extrinsic -}: swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), new_coldkey.clone()) +}: swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), old_coldkey.clone(), new_coldkey.clone()) } diff --git a/pallets/subtensor/tests/swap_coldkey.rs b/pallets/subtensor/tests/swap_coldkey.rs index e4f6b5cd2..0fe601cab 100644 --- a/pallets/subtensor/tests/swap_coldkey.rs +++ b/pallets/subtensor/tests/swap_coldkey.rs @@ -1541,10 +1541,7 @@ fn test_coldkey_swap_delegate_identity_updated() { assert!(Identities::::get(old_coldkey).is_some()); assert!(Identities::::get(new_coldkey).is_none()); - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &new_coldkey - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); assert!(Identities::::get(old_coldkey).is_none()); assert!(Identities::::get(new_coldkey).is_some()); @@ -1580,10 +1577,7 @@ fn test_coldkey_swap_no_identity_no_changes() { assert!(Identities::::get(old_coldkey).is_none()); // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey), - &new_coldkey, - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); // Ensure no identities have been changed assert!(Identities::::get(old_coldkey).is_none()); @@ -1594,8 +1588,8 @@ fn test_coldkey_swap_no_identity_no_changes() { #[test] fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { new_test_ext(1).execute_with(|| { - let old_coldkey_2 = U256::from(3); - let new_coldkey_2 = U256::from(4); + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); let netuid = 1; let burn_cost = 10; @@ -1603,12 +1597,12 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { SubtensorModule::set_burn(netuid, burn_cost); add_network(netuid, tempo, 0); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey_2, 100_000_000_000); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 100_000_000_000); assert_ok!(SubtensorModule::burned_register( - <::RuntimeOrigin>::signed(old_coldkey_2), + <::RuntimeOrigin>::signed(old_coldkey), netuid, - old_coldkey_2 + old_coldkey )); let name: Vec = b"The Coolest Identity".to_vec(); @@ -1621,19 +1615,16 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { additional: vec![], }; - Identities::::insert(new_coldkey_2, identity.clone()); + Identities::::insert(new_coldkey, identity.clone()); // Ensure the new coldkey does have an identity before the swap - assert!(Identities::::get(new_coldkey_2).is_some()); - assert!(Identities::::get(old_coldkey_2).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); + assert!(Identities::::get(old_coldkey).is_none()); // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey( - <::RuntimeOrigin>::signed(old_coldkey_2), - &new_coldkey_2, - )); + assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); // Ensure no identities have been changed - assert!(Identities::::get(old_coldkey_2).is_none()); - assert!(Identities::::get(new_coldkey_2).is_some()); + assert!(Identities::::get(old_coldkey).is_none()); + assert!(Identities::::get(new_coldkey).is_some()); }); } From bc1631ee91ad71b61b5e4835d0cf06caa26f16b7 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:04:53 -1000 Subject: [PATCH 256/269] add a delegate ID --- docs/delegate-info.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/delegate-info.json b/docs/delegate-info.json index 46355da3d..544c36e53 100644 --- a/docs/delegate-info.json +++ b/docs/delegate-info.json @@ -397,5 +397,12 @@ "url": "https://love.cosimo.fund", "description": "Love validator exists to accelerate open source AI and be good stewards of the Bittensorr network", "signature": "c221a3de3be031c149a7be912b3b75e0355605f041dc975153302b23b4d93e45e9cc7453532491e92076ccd333a4c1f95f4a2229aae8f4fcfb88e5dec3f14c87" + }, + { + "address": "5Hb63SvXBXqZ8zw6mwW1A39fHdqUrJvohXgepyhp2jgWedSB", + "name": "TAO Miner's Union", + "url": "https://minersunion.ai", + "description": "The first Bittensor validator that empowers you to choose which subnets to incentivize. Committed to transparency and integrity, we ensure fair and honest validation processes that contribute to the growth and strength of the network.", + "signature": "e8c68bc766a06f36c633e1f68d5aca4c4090a26e394372f64d5b00cc13621f361ec9df85fc9f0d247dbc1fe452bd53ffc0224dee2bc85c9d82cb250e4ac10984" } ] \ No newline at end of file From 93233fcb303a6e83e30db52699c5cb1345ed97dc Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:21:35 -1000 Subject: [PATCH 257/269] clippy --- pallets/subtensor/src/swap/swap_coldkey.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 91bf04c08..bcbd2a330 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -50,7 +50,7 @@ impl Pallet { weight = weight.saturating_add(T::DbWeight::get().reads(1)); // 5. Swap the identity if the old coldkey has one - if let Some(identity) = Identities::::take(&old_coldkey) { + if let Some(identity) = Identities::::take(old_coldkey) { Identities::::insert(new_coldkey, identity); } From 979d46d5ca0252db93f12e81ae0934f7ce328baf Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 22 Aug 2024 08:48:10 -1000 Subject: [PATCH 258/269] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e6c0e9d32..9ad0624d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -142,7 +142,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: 193, + spec_version: 194, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From cdb07599f310ecf8d334754167ebe4e7656c6d30 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 22 Aug 2024 08:48:50 -1000 Subject: [PATCH 259/269] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e6c0e9d32..9ad0624d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -142,7 +142,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: 193, + spec_version: 194, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From b4c1712b6a037a3927037f0f1878b062a8dd84f1 Mon Sep 17 00:00:00 2001 From: open-junius Date: Fri, 23 Aug 2024 23:13:30 +0800 Subject: [PATCH 260/269] fix comment index --- pallets/subtensor/src/coinbase/root.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index f7b523c61..14b1beb7f 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -993,28 +993,26 @@ impl Pallet { /// * 'NotSubnetOwner': If the caller does not own the specified subnet. /// pub fn user_remove_network(coldkey: T::AccountId, netuid: u16) -> dispatch::DispatchResult { - // --- 1. Ensure the function caller is a signed user. - - // --- 2. Ensure this subnet exists. + // --- 1. Ensure this subnet exists. ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - // --- 3. Ensure the caller owns this subnet. + // --- 2. Ensure the caller owns this subnet. ensure!( SubnetOwner::::get(netuid) == coldkey, Error::::NotSubnetOwner ); - // --- 4. Explicitly erase the network and all its parameters. + // --- 2. Explicitly erase the network and all its parameters. Self::remove_network(netuid); - // --- 5. Emit the NetworkRemoved event. + // --- 3. Emit the NetworkRemoved event. log::debug!("NetworkRemoved( netuid:{:?} )", netuid); Self::deposit_event(Event::NetworkRemoved(netuid)); - // --- 6. Return success. + // --- 5. Return success. Ok(()) } From 41b6ae66588706afaf2b4e82da8606ecf48e55e1 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Mon, 26 Aug 2024 18:03:30 +0400 Subject: [PATCH 261/269] feat: implement subnetinfov2 , add_network with identities --- pallets/admin-utils/tests/tests.rs | 2 +- pallets/subtensor/rpc/src/lib.rs | 24 ++++ pallets/subtensor/runtime-api/src/lib.rs | 2 + pallets/subtensor/src/coinbase/root.rs | 125 ++++++++++++++++-- pallets/subtensor/src/macros/dispatches.rs | 19 ++- pallets/subtensor/src/rpc_info/subnet_info.rs | 99 +++++++++++++- pallets/subtensor/tests/epoch.rs | 4 +- pallets/subtensor/tests/migration.rs | 1 - pallets/subtensor/tests/root.rs | 25 +--- runtime/src/lib.rs | 15 +++ 10 files changed, 271 insertions(+), 45 deletions(-) diff --git a/pallets/admin-utils/tests/tests.rs b/pallets/admin-utils/tests/tests.rs index a1395a056..8ab85f177 100644 --- a/pallets/admin-utils/tests/tests.rs +++ b/pallets/admin-utils/tests/tests.rs @@ -1243,7 +1243,7 @@ fn test_sudo_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone(), None)); + assert_ok!(SubtensorModule::register_network(signer.clone())); assert_ok!(AdminUtils::sudo_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 2f71e9c21..2445a5eda 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -46,6 +46,10 @@ pub trait SubtensorCustomApi { fn get_subnet_info(&self, netuid: u16, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getSubnetsInfo")] fn get_subnets_info(&self, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getSubnetInfo_v2")] + fn get_subnet_info_v2(&self, netuid: u16, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getSubnetsInf_v2")] + fn get_subnets_info_v2(&self, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getSubnetHyperparams")] fn get_subnet_hyperparams(&self, netuid: u16, at: Option) -> RpcResult>; @@ -215,6 +219,26 @@ where .map_err(|e| Error::RuntimeError(format!("Unable to get subnets info: {:?}", e)).into()) } + fn get_subnet_info_v2( + &self, + netuid: u16, + at: Option<::Hash>, + ) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_subnet_info_v2(at, netuid) + .map_err(|e| Error::RuntimeError(format!("Unable to get subnet info: {:?}", e)).into()) + } + + fn get_subnets_info_v2(&self, at: Option<::Hash>) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + + api.get_subnets_info_v2(at) + .map_err(|e| Error::RuntimeError(format!("Unable to get subnets info: {:?}", e)).into()) + } + fn get_network_lock_cost(&self, at: Option<::Hash>) -> RpcResult { let api = self.client.runtime_api(); let at = at.unwrap_or_else(|| self.client.info().best_hash); diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 9095ad54a..ca43384b8 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -21,6 +21,8 @@ sp_api::decl_runtime_apis! { pub trait SubnetInfoRuntimeApi { fn get_subnet_info(netuid: u16) -> Vec; fn get_subnets_info() -> Vec; + fn get_subnet_info_v2(netuid: u16) -> Vec; + fn get_subnets_info_v2() -> Vec; fn get_subnet_hyperparams(netuid: u16) -> Vec; } diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index c1c9b3ad9..4a25f5ab8 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -895,6 +895,100 @@ impl Pallet { /// /// # Args: /// * `origin` (`T::RuntimeOrigin`): The calling origin. Must be signed. + /// + /// # Events: + /// * `NetworkAdded(netuid, modality)`: Emitted when a new network is successfully added. + /// * `NetworkRemoved(netuid)`: Emitted when an existing network is removed to make room for the new one. + /// + /// # Raises: + /// * 'TxRateLimitExceeded': If the rate limit for network registration is exceeded. + /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. + /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. + /// + pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { + // --- 0. Ensure the caller is a signed user. + let coldkey = ensure_signed(origin)?; + + // --- 1. Rate limit for network registrations. + let current_block = Self::get_current_block_as_u64(); + let last_lock_block = Self::get_network_last_lock_block(); + ensure!( + current_block.saturating_sub(last_lock_block) >= NetworkRateLimit::::get(), + Error::::NetworkTxRateLimitExceeded + ); + + // --- 2. Calculate and lock the required tokens. + let lock_amount: u64 = Self::get_network_lock_cost(); + log::debug!("network lock_amount: {:?}", lock_amount); + ensure!( + Self::can_remove_balance_from_coldkey_account(&coldkey, lock_amount), + Error::::NotEnoughBalanceToStake + ); + + // --- 4. Determine the netuid to register. + let netuid_to_register: u16 = { + log::debug!( + "subnet count: {:?}\nmax subnets: {:?}", + Self::get_num_subnets(), + Self::get_max_subnets() + ); + if Self::get_num_subnets().saturating_sub(1) < Self::get_max_subnets() { + // We subtract one because we don't want root subnet to count towards total + let mut next_available_netuid = 0; + loop { + next_available_netuid.saturating_inc(); + if !Self::if_subnet_exist(next_available_netuid) { + log::debug!("got subnet id: {:?}", next_available_netuid); + break next_available_netuid; + } + } + } else { + let netuid_to_prune = Self::get_subnet_to_prune(); + ensure!(netuid_to_prune > 0, Error::::AllNetworksInImmunity); + + Self::remove_network(netuid_to_prune); + log::debug!("remove_network: {:?}", netuid_to_prune,); + Self::deposit_event(Event::NetworkRemoved(netuid_to_prune)); + + if SubnetIdentities::::take(netuid_to_prune).is_some() { + Self::deposit_event(Event::SubnetIdentityRemoved(netuid_to_prune)); + } + + netuid_to_prune + } + }; + + // --- 5. Perform the lock operation. + let actual_lock_amount = Self::remove_balance_from_coldkey_account(&coldkey, lock_amount)?; + Self::set_subnet_locked_balance(netuid_to_register, actual_lock_amount); + Self::set_network_last_lock(actual_lock_amount); + + // --- 6. Set initial and custom parameters for the network. + Self::init_new_network(netuid_to_register, 360); + log::debug!("init_new_network: {:?}", netuid_to_register,); + + // --- 7. Set netuid storage. + let current_block_number: u64 = Self::get_current_block_as_u64(); + NetworkLastRegistered::::set(current_block_number); + NetworkRegisteredAt::::insert(netuid_to_register, current_block_number); + SubnetOwner::::insert(netuid_to_register, coldkey); + + // --- 8. Emit the NetworkAdded event. + log::debug!( + "NetworkAdded( netuid:{:?}, modality:{:?} )", + netuid_to_register, + 0 + ); + Self::deposit_event(Event::NetworkAdded(netuid_to_register, 0)); + + // --- 9. Return success. + Ok(()) + } + + /// Facilitates user registration of a new subnetwork with subnet identity. + /// + /// # Args: + /// * `origin` (`T::RuntimeOrigin`): The calling origin. Must be signed. /// * `identity` (`Option`): Optional identity to be associated with the new subnetwork. /// /// # Events: @@ -908,7 +1002,7 @@ impl Pallet { /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. /// - pub fn user_add_network( + pub fn user_add_network_with_identity( origin: T::RuntimeOrigin, identity: Option, ) -> dispatch::DispatchResult { @@ -1126,8 +1220,8 @@ impl Pallet { /// Removes a network (identified by netuid) and all associated parameters. /// /// This function is responsible for cleaning up all the data associated with a network. - /// It ensures that all the storage values related to the network are removed, and any - /// reserved balance is returned to the network owner. + /// It ensures that all the storage values related to the network are removed, any + /// reserved balance is returned to the network owner, and the subnet identity is removed if it exists. /// /// # Args: /// * 'netuid': ('u16'): The unique identifier of the network to be removed. @@ -1136,10 +1230,15 @@ impl Pallet { /// This function does not emit any events, nor does it raise any errors. It silently /// returns if any internal checks fail. /// + /// # Example: + /// ```rust + /// let netuid_to_remove: u16 = 5; + /// Pallet::::remove_network(netuid_to_remove); + /// ``` pub fn remove_network(netuid: u16) { // --- 1. Return balance to subnet owner. - let owner_coldkey = SubnetOwner::::get(netuid); - let reserved_amount = Self::get_subnet_locked_balance(netuid); + let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); + let reserved_amount: u64 = Self::get_subnet_locked_balance(netuid); // --- 2. Remove network count. SubnetworkN::::remove(netuid); @@ -1150,13 +1249,13 @@ impl Pallet { // --- 4. Remove netuid from added networks. NetworksAdded::::remove(netuid); - // --- 6. Decrement the network counter. - TotalNetworks::::mutate(|n| *n = n.saturating_sub(1)); + // --- 5. Decrement the network counter. + TotalNetworks::::mutate(|n: &mut u16| *n = n.saturating_sub(1)); - // --- 7. Remove various network-related storages. + // --- 6. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); - // --- 8. Remove incentive mechanism memory. + // --- 7. Remove incentive mechanism memory. let _ = Uids::::clear_prefix(netuid, u32::MAX, None); let _ = Keys::::clear_prefix(netuid, u32::MAX, None); let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); @@ -1171,7 +1270,7 @@ impl Pallet { ) { // Create a new vector to hold modified weights. - let mut modified_weights = weights_i.clone(); + let mut modified_weights: Vec<(u16, u16)> = weights_i.clone(); // Iterate over each weight entry to potentially update it. for (subnet_id, weight) in modified_weights.iter_mut() { if subnet_id == &netuid { @@ -1213,6 +1312,12 @@ impl Pallet { Self::add_balance_to_coldkey_account(&owner_coldkey, reserved_amount); Self::set_subnet_locked_balance(netuid, 0); SubnetOwner::::remove(netuid); + + // --- 13. Remove subnet identity if it exists. + if SubnetIdentities::::contains_key(netuid) { + SubnetIdentities::::remove(netuid); + Self::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } } #[allow(clippy::arithmetic_side_effects)] diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index b1446b69d..f33b0c3bc 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -901,11 +901,8 @@ mod dispatches { #[pallet::weight((Weight::from_parts(157_000_000, 0) .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] - pub fn register_network( - origin: OriginFor, - identity: Option, - ) -> DispatchResult { - Self::user_add_network(origin, identity) + pub fn register_network(origin: OriginFor) -> DispatchResult { + Self::user_add_network(origin) } /// Facility extrinsic for user to get taken from faucet @@ -1201,5 +1198,17 @@ mod dispatches { ) -> DispatchResult { Self::do_set_subnet_identity(origin, netuid, subnet_name, github_repo, subnet_contact) } + + /// User register a new subnetwork + #[pallet::call_index(79)] + #[pallet::weight((Weight::from_parts(157_000_000, 0) + .saturating_add(T::DbWeight::get().reads(16)) + .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] + pub fn register_network_with_identity( + origin: OriginFor, + identity: Option, + ) -> DispatchResult { + Self::user_add_network_with_identity(origin, identity) + } } } diff --git a/pallets/subtensor/src/rpc_info/subnet_info.rs b/pallets/subtensor/src/rpc_info/subnet_info.rs index 6e8b4bdc5..9b22e0401 100644 --- a/pallets/subtensor/src/rpc_info/subnet_info.rs +++ b/pallets/subtensor/src/rpc_info/subnet_info.rs @@ -4,7 +4,7 @@ use frame_support::storage::IterableStorageMap; extern crate alloc; use codec::Compact; -#[freeze_struct("ccca539640c3f631")] +#[freeze_struct("fe79d58173da662a")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] pub struct SubnetInfo { netuid: Compact, @@ -25,6 +25,29 @@ pub struct SubnetInfo { emission_values: Compact, burn: Compact, owner: T::AccountId, +} + +#[freeze_struct("65f931972fa13222")] +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +pub struct SubnetInfov2 { + netuid: Compact, + rho: Compact, + kappa: Compact, + difficulty: Compact, + immunity_period: Compact, + max_allowed_validators: Compact, + min_allowed_weights: Compact, + max_weights_limit: Compact, + scaling_law_power: Compact, + subnetwork_n: Compact, + max_allowed_uids: Compact, + blocks_since_last_step: Compact, + tempo: Compact, + network_modality: Compact, + network_connect: Vec<[u16; 2]>, + emission_values: Compact, + burn: Compact, + owner: T::AccountId, identity: Option, } @@ -81,8 +104,6 @@ impl Pallet { let network_modality = >::get(netuid); let emission_values = Self::get_emission_value(netuid); let burn: Compact = Self::get_burn_as_u64(netuid).into(); - let identity: Option = SubnetIdentities::::get(netuid); - // DEPRECATED let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); // DEPRECATED for ( _netuid_, con_req) in < NetworkConnect as IterableStorageDoubleMap >::iter_prefix(netuid) { @@ -108,7 +129,6 @@ impl Pallet { emission_values: emission_values.into(), burn, owner: Self::get_subnet_owner(netuid), - identity, }) } @@ -134,6 +154,77 @@ impl Pallet { subnets_info } + pub fn get_subnet_info_v2(netuid: u16) -> Option> { + if !Self::if_subnet_exist(netuid) { + return None; + } + + let rho = Self::get_rho(netuid); + let kappa = Self::get_kappa(netuid); + let difficulty: Compact = Self::get_difficulty_as_u64(netuid).into(); + let immunity_period = Self::get_immunity_period(netuid); + let max_allowed_validators = Self::get_max_allowed_validators(netuid); + let min_allowed_weights = Self::get_min_allowed_weights(netuid); + let max_weights_limit = Self::get_max_weight_limit(netuid); + let scaling_law_power = Self::get_scaling_law_power(netuid); + let subnetwork_n = Self::get_subnetwork_n(netuid); + let max_allowed_uids = Self::get_max_allowed_uids(netuid); + let blocks_since_last_step = Self::get_blocks_since_last_step(netuid); + let tempo = Self::get_tempo(netuid); + let network_modality = >::get(netuid); + let emission_values = Self::get_emission_value(netuid); + let burn: Compact = Self::get_burn_as_u64(netuid).into(); + let identity: Option = SubnetIdentities::::get(netuid); + + // DEPRECATED + let network_connect: Vec<[u16; 2]> = Vec::<[u16; 2]>::new(); + // DEPRECATED for ( _netuid_, con_req) in < NetworkConnect as IterableStorageDoubleMap >::iter_prefix(netuid) { + // network_connect.push([_netuid_, con_req]); + // } + + Some(SubnetInfov2 { + rho: rho.into(), + kappa: kappa.into(), + difficulty, + immunity_period: immunity_period.into(), + netuid: netuid.into(), + max_allowed_validators: max_allowed_validators.into(), + min_allowed_weights: min_allowed_weights.into(), + max_weights_limit: max_weights_limit.into(), + scaling_law_power: scaling_law_power.into(), + subnetwork_n: subnetwork_n.into(), + max_allowed_uids: max_allowed_uids.into(), + blocks_since_last_step: blocks_since_last_step.into(), + tempo: tempo.into(), + network_modality: network_modality.into(), + network_connect, + emission_values: emission_values.into(), + burn, + owner: Self::get_subnet_owner(netuid), + identity, + }) + } + pub fn get_subnets_info_v2() -> Vec>> { + let mut subnet_netuids = Vec::::new(); + let mut max_netuid: u16 = 0; + for (netuid, added) in as IterableStorageMap>::iter() { + if added { + subnet_netuids.push(netuid); + if netuid > max_netuid { + max_netuid = netuid; + } + } + } + + let mut subnets_info = Vec::>>::new(); + for netuid_ in 0..=max_netuid { + if subnet_netuids.contains(&netuid_) { + subnets_info.push(Self::get_subnet_info(netuid_)); + } + } + + subnets_info + } pub fn get_subnet_hyperparams(netuid: u16) -> Option { if !Self::if_subnet_exist(netuid) { return None; diff --git a/pallets/subtensor/tests/epoch.rs b/pallets/subtensor/tests/epoch.rs index 30cc1d304..9c4bf87cc 100644 --- a/pallets/subtensor/tests/epoch.rs +++ b/pallets/subtensor/tests/epoch.rs @@ -1501,7 +1501,7 @@ fn test_set_alpha_disabled() { assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); assert_ok!(SubtensorModule::add_stake(signer.clone(), hotkey, 1000)); // Only owner can set alpha values - assert_ok!(SubtensorModule::register_network(signer.clone(), None)); + assert_ok!(SubtensorModule::register_network(signer.clone())); // Explicitly set to false SubtensorModule::set_liquid_alpha_enabled(netuid, false); @@ -2584,7 +2584,7 @@ fn test_get_set_alpha() { DispatchError::BadOrigin ); - assert_ok!(SubtensorModule::register_network(signer.clone(), None)); + assert_ok!(SubtensorModule::register_network(signer.clone())); assert_ok!(SubtensorModule::do_set_alpha_values( signer.clone(), diff --git a/pallets/subtensor/tests/migration.rs b/pallets/subtensor/tests/migration.rs index 95479d7d7..6c40d7d78 100644 --- a/pallets/subtensor/tests/migration.rs +++ b/pallets/subtensor/tests/migration.rs @@ -171,7 +171,6 @@ fn test_total_issuance_global() { assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); SubtensorModule::set_max_allowed_uids(netuid, 1); // Set the maximum allowed unique identifiers for the network to 1. assert_eq!(SubtensorModule::get_total_issuance(), 0); // initial is zero. diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 0c621739b..6a2904c87 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -237,7 +237,6 @@ fn test_root_set_weights() { log::debug!("Adding network with netuid: {}", netuid); assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(U256::from(netuid + 456)), - None )); } @@ -383,7 +382,6 @@ fn test_root_set_weights_out_of_order_netuids() { if netuid % 2 == 0 { assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(U256::from(netuid)), - None )); } else { add_network(netuid as u16 * 10, 1000, 0) @@ -475,7 +473,6 @@ fn test_root_subnet_creation_deletion() { // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 0, mult: 1 lock_cost: 100000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); @@ -483,7 +480,6 @@ fn test_root_subnet_creation_deletion() { // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 0, lock_reduction_interval: 2, current_block: 1, mult: 1 lock_cost: 100000000000 assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 1, lock_reduction_interval: 2, current_block: 1, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation @@ -498,7 +494,6 @@ fn test_root_subnet_creation_deletion() { assert_eq!(SubtensorModule::get_network_lock_cost(), 100_000_000_000); // Reaches min value assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 4, mult: 2 lock_cost: 200000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 200_000_000_000); // Doubles from previous subnet creation @@ -506,7 +501,6 @@ fn test_root_subnet_creation_deletion() { // last_lock: 100000000000, min_lock: 100000000000, last_lock_block: 4, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 150000000000 assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 5, mult: 2 lock_cost: 300000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 300_000_000_000); // Doubles from previous subnet creation @@ -514,7 +508,6 @@ fn test_root_subnet_creation_deletion() { // last_lock: 150000000000, min_lock: 100000000000, last_lock_block: 5, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 225000000000 assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 6, mult: 2 lock_cost: 450000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 450_000_000_000); // Increasing @@ -522,19 +515,16 @@ fn test_root_subnet_creation_deletion() { // last_lock: 225000000000, min_lock: 100000000000, last_lock_block: 6, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 337500000000 assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 675_000_000_000); // Increasing. assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); // last_lock: 337500000000, min_lock: 100000000000, last_lock_block: 7, lock_reduction_interval: 2, current_block: 7, mult: 2 lock_cost: 675000000000 assert_eq!(SubtensorModule::get_network_lock_cost(), 1_350_000_000_000); // Double increasing. assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); assert_eq!(SubtensorModule::get_network_lock_cost(), 2_700_000_000_000); // Double increasing again. @@ -584,7 +574,6 @@ fn test_network_pruning() { )); assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(cold), - None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); assert!(SubtensorModule::if_subnet_exist((i as u16) + 1)); @@ -658,19 +647,16 @@ fn test_network_prune_results() { assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); step_block(3); assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); step_block(3); assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(owner), - None )); step_block(3); @@ -715,7 +701,6 @@ fn test_weights_after_network_pruning() { // Register a network assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(cold), - None )); log::debug!("Adding network with netuid: {}", (i as u16) + 1); @@ -776,7 +761,6 @@ fn test_weights_after_network_pruning() { assert_ok!(SubtensorModule::register_network( <::RuntimeOrigin>::signed(cold), - None )); // Subnet should not exist, as it would replace a previous subnet. @@ -1028,7 +1012,7 @@ fn test_user_add_network_with_identity_fields_ok() { SubtensorModule::add_balance_to_coldkey_account(&coldkey_1, balance_1); - assert_ok!(SubtensorModule::user_add_network( + assert_ok!(SubtensorModule::user_add_network_with_identity( RuntimeOrigin::signed(coldkey_1), Some(identity_value_1.clone()) )); @@ -1036,7 +1020,7 @@ fn test_user_add_network_with_identity_fields_ok() { let balance_2 = SubtensorModule::get_network_lock_cost() + 10_000; SubtensorModule::add_balance_to_coldkey_account(&coldkey_2, balance_2); - assert_ok!(SubtensorModule::user_add_network( + assert_ok!(SubtensorModule::user_add_network_with_identity( RuntimeOrigin::signed(coldkey_2), Some(identity_value_2.clone()) )); @@ -1052,10 +1036,7 @@ fn test_user_add_network_with_identity_fields_ok() { assert_eq!(stored_identity_2.subnet_contact, subnet_contact_2); // Now remove the first network. - assert_ok!(SubtensorModule::user_remove_network( - RuntimeOrigin::signed(coldkey_1), - 1 - )); + assert_ok!(SubtensorModule::user_remove_network(coldkey_1, 1)); // Verify that the first network and identity have been removed. assert!(SubnetIdentities::::get(1).is_none()); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9ad0624d0..422954476 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1426,6 +1426,21 @@ impl_runtime_apis! { result.encode() } + fn get_subnet_info_v2(netuid: u16) -> Vec { + let _result = SubtensorModule::get_subnet_info_v2(netuid); + if _result.is_some() { + let result = _result.expect("Could not get SubnetInfo"); + result.encode() + } else { + vec![] + } + } + + fn get_subnets_info_v2() -> Vec { + let result = SubtensorModule::get_subnets_info_v2(); + result.encode() + } + fn get_subnet_hyperparams(netuid: u16) -> Vec { let _result = SubtensorModule::get_subnet_hyperparams(netuid); if _result.is_some() { From 803c5e30246b224439fd8d3d548d1a977eba5b62 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:06:03 -0700 Subject: [PATCH 262/269] remove user_add_network --- pallets/subtensor/src/coinbase/root.rs | 100 --------------------- pallets/subtensor/src/macros/dispatches.rs | 2 +- 2 files changed, 1 insertion(+), 101 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 4a25f5ab8..5c48f8d0a 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -891,100 +891,6 @@ impl Pallet { .into()) } - /// Facilitates user registration of a new subnetwork. - /// - /// # Args: - /// * `origin` (`T::RuntimeOrigin`): The calling origin. Must be signed. - /// - /// # Events: - /// * `NetworkAdded(netuid, modality)`: Emitted when a new network is successfully added. - /// * `NetworkRemoved(netuid)`: Emitted when an existing network is removed to make room for the new one. - /// - /// # Raises: - /// * 'TxRateLimitExceeded': If the rate limit for network registration is exceeded. - /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. - /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. - /// - pub fn user_add_network(origin: T::RuntimeOrigin) -> dispatch::DispatchResult { - // --- 0. Ensure the caller is a signed user. - let coldkey = ensure_signed(origin)?; - - // --- 1. Rate limit for network registrations. - let current_block = Self::get_current_block_as_u64(); - let last_lock_block = Self::get_network_last_lock_block(); - ensure!( - current_block.saturating_sub(last_lock_block) >= NetworkRateLimit::::get(), - Error::::NetworkTxRateLimitExceeded - ); - - // --- 2. Calculate and lock the required tokens. - let lock_amount: u64 = Self::get_network_lock_cost(); - log::debug!("network lock_amount: {:?}", lock_amount); - ensure!( - Self::can_remove_balance_from_coldkey_account(&coldkey, lock_amount), - Error::::NotEnoughBalanceToStake - ); - - // --- 4. Determine the netuid to register. - let netuid_to_register: u16 = { - log::debug!( - "subnet count: {:?}\nmax subnets: {:?}", - Self::get_num_subnets(), - Self::get_max_subnets() - ); - if Self::get_num_subnets().saturating_sub(1) < Self::get_max_subnets() { - // We subtract one because we don't want root subnet to count towards total - let mut next_available_netuid = 0; - loop { - next_available_netuid.saturating_inc(); - if !Self::if_subnet_exist(next_available_netuid) { - log::debug!("got subnet id: {:?}", next_available_netuid); - break next_available_netuid; - } - } - } else { - let netuid_to_prune = Self::get_subnet_to_prune(); - ensure!(netuid_to_prune > 0, Error::::AllNetworksInImmunity); - - Self::remove_network(netuid_to_prune); - log::debug!("remove_network: {:?}", netuid_to_prune,); - Self::deposit_event(Event::NetworkRemoved(netuid_to_prune)); - - if SubnetIdentities::::take(netuid_to_prune).is_some() { - Self::deposit_event(Event::SubnetIdentityRemoved(netuid_to_prune)); - } - - netuid_to_prune - } - }; - - // --- 5. Perform the lock operation. - let actual_lock_amount = Self::remove_balance_from_coldkey_account(&coldkey, lock_amount)?; - Self::set_subnet_locked_balance(netuid_to_register, actual_lock_amount); - Self::set_network_last_lock(actual_lock_amount); - - // --- 6. Set initial and custom parameters for the network. - Self::init_new_network(netuid_to_register, 360); - log::debug!("init_new_network: {:?}", netuid_to_register,); - - // --- 7. Set netuid storage. - let current_block_number: u64 = Self::get_current_block_as_u64(); - NetworkLastRegistered::::set(current_block_number); - NetworkRegisteredAt::::insert(netuid_to_register, current_block_number); - SubnetOwner::::insert(netuid_to_register, coldkey); - - // --- 8. Emit the NetworkAdded event. - log::debug!( - "NetworkAdded( netuid:{:?}, modality:{:?} )", - netuid_to_register, - 0 - ); - Self::deposit_event(Event::NetworkAdded(netuid_to_register, 0)); - - // --- 9. Return success. - Ok(()) - } - /// Facilitates user registration of a new subnetwork with subnet identity. /// /// # Args: @@ -1229,12 +1135,6 @@ impl Pallet { /// # Note: /// This function does not emit any events, nor does it raise any errors. It silently /// returns if any internal checks fail. - /// - /// # Example: - /// ```rust - /// let netuid_to_remove: u16 = 5; - /// Pallet::::remove_network(netuid_to_remove); - /// ``` pub fn remove_network(netuid: u16) { // --- 1. Return balance to subnet owner. let owner_coldkey: T::AccountId = SubnetOwner::::get(netuid); diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f33b0c3bc..125c4a6cd 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -902,7 +902,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor) -> DispatchResult { - Self::user_add_network(origin) + Self::user_add_network_with_identity(origin, None) } /// Facility extrinsic for user to get taken from faucet From 619757f1325e11c01faaeaaa7c288da1e49c0def Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:26:45 -0700 Subject: [PATCH 263/269] fix benchmarks --- pallets/subtensor/src/benchmarks.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index 4af039ad6..4915bb3ac 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -299,7 +299,7 @@ benchmarks! { let amount: u64 = 1; let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); - }: register_network(RawOrigin::Signed(coldkey), None) + }: register_network(RawOrigin::Signed(coldkey)) benchmark_dissolve_network { let seed : u32 = 1; @@ -311,8 +311,8 @@ benchmarks! { let amount: u64 = 1; let amount_to_be_staked = 100_000_000_000_000u64; Subtensor::::add_balance_to_coldkey_account(&coldkey.clone(), amount_to_be_staked); - assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into(), None)); - }: dissolve_network(RawOrigin::Signed(coldkey), 1) + assert_ok!(Subtensor::::register_network(RawOrigin::Signed(coldkey.clone()).into())); + }: dissolve_network(RawOrigin::Root, coldkey.clone(), 1) // swap_hotkey { @@ -519,6 +519,6 @@ reveal_weights { Identities::::insert(&old_coldkey, identity); // Benchmark setup complete, now execute the extrinsic -}: swap_coldkey(RawOrigin::Signed(old_coldkey.clone()), old_coldkey.clone(), new_coldkey.clone()) +}: swap_coldkey(RawOrigin::Root, old_coldkey.clone(), new_coldkey.clone()) } From 5af9888e16fb4eab0a8f0d22f2919bfe79e02b1e Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:41:01 -0700 Subject: [PATCH 264/269] revert rename user_add_network --- pallets/subtensor/src/coinbase/root.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- pallets/subtensor/tests/root.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 5c48f8d0a..067d5855b 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -908,7 +908,7 @@ impl Pallet { /// * 'NotEnoughBalanceToStake': If there isn't enough balance to stake for network registration. /// * 'BalanceWithdrawalError': If an error occurs during balance withdrawal for network registration. /// - pub fn user_add_network_with_identity( + pub fn user_add_network( origin: T::RuntimeOrigin, identity: Option, ) -> dispatch::DispatchResult { diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 125c4a6cd..a97e4494d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -902,7 +902,7 @@ mod dispatches { .saturating_add(T::DbWeight::get().reads(16)) .saturating_add(T::DbWeight::get().writes(30)), DispatchClass::Operational, Pays::No))] pub fn register_network(origin: OriginFor) -> DispatchResult { - Self::user_add_network_with_identity(origin, None) + Self::user_add_network(origin, None) } /// Facility extrinsic for user to get taken from faucet @@ -1208,7 +1208,7 @@ mod dispatches { origin: OriginFor, identity: Option, ) -> DispatchResult { - Self::user_add_network_with_identity(origin, identity) + Self::user_add_network(origin, identity) } } } diff --git a/pallets/subtensor/tests/root.rs b/pallets/subtensor/tests/root.rs index 6a2904c87..caf1e5935 100644 --- a/pallets/subtensor/tests/root.rs +++ b/pallets/subtensor/tests/root.rs @@ -1012,7 +1012,7 @@ fn test_user_add_network_with_identity_fields_ok() { SubtensorModule::add_balance_to_coldkey_account(&coldkey_1, balance_1); - assert_ok!(SubtensorModule::user_add_network_with_identity( + assert_ok!(SubtensorModule::user_add_network( RuntimeOrigin::signed(coldkey_1), Some(identity_value_1.clone()) )); @@ -1020,7 +1020,7 @@ fn test_user_add_network_with_identity_fields_ok() { let balance_2 = SubtensorModule::get_network_lock_cost() + 10_000; SubtensorModule::add_balance_to_coldkey_account(&coldkey_2, balance_2); - assert_ok!(SubtensorModule::user_add_network_with_identity( + assert_ok!(SubtensorModule::user_add_network( RuntimeOrigin::signed(coldkey_2), Some(identity_value_2.clone()) )); From 435d15ccc9b243ff11d4a31dd103fdbfac3e7efd Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 28 Aug 2024 10:41:36 +0200 Subject: [PATCH 265/269] fix and explicitly set pallet indicies --- runtime/src/lib.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 422954476..8cffeadad 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1030,27 +1030,27 @@ impl pallet_admin_utils::Config for Runtime { construct_runtime!( pub struct Runtime { - System: frame_system, - RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip, - Timestamp: pallet_timestamp, - Aura: pallet_aura, - Grandpa: pallet_grandpa, - Balances: pallet_balances, - TransactionPayment: pallet_transaction_payment, - SubtensorModule: pallet_subtensor, - Triumvirate: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config}, - TriumvirateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config}, - SenateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config}, - Utility: pallet_utility, - Sudo: pallet_sudo, - Multisig: pallet_multisig, - Preimage: pallet_preimage, - Proxy: pallet_proxy, - Registry: pallet_registry, - Commitments: pallet_commitments, - AdminUtils: pallet_admin_utils, - SafeMode: pallet_safe_mode, - Scheduler: pallet_scheduler, + System: frame_system = 0, + RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip = 1, + Timestamp: pallet_timestamp = 2, + Aura: pallet_aura = 3, + Grandpa: pallet_grandpa = 4, + Balances: pallet_balances = 5, + TransactionPayment: pallet_transaction_payment = 6, + SubtensorModule: pallet_subtensor = 7, + Triumvirate: pallet_collective::::{Pallet, Call, Storage, Origin, Event, Config} = 8, + TriumvirateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config} = 9, + SenateMembers: pallet_membership::::{Pallet, Call, Storage, Event, Config} = 10, + Utility: pallet_utility = 11, + Sudo: pallet_sudo = 12, + Multisig: pallet_multisig = 13, + Preimage: pallet_preimage = 14, + Scheduler: pallet_scheduler = 15, + Proxy: pallet_proxy = 16, + Registry: pallet_registry = 17, + Commitments: pallet_commitments = 18, + AdminUtils: pallet_admin_utils = 19, + SafeMode: pallet_safe_mode = 20, } ); From 9ddc2eb339f81d2fbaad41444e48d3b9c39de0b8 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 28 Aug 2024 14:28:27 +0200 Subject: [PATCH 266/269] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8cffeadad..ca8f83911 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -142,7 +142,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: 194, + spec_version: 195, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From bc15020bcb3ad2444d1d97ce24692caee81617b4 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Wed, 28 Aug 2024 13:18:10 -0400 Subject: [PATCH 267/269] completely revamp Dockerfile to be better :O --- .dockerignore | 5 +++-- Dockerfile | 51 +++++++++++++++++---------------------------------- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6b7ebf648..2886a059a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .devcontainer .github .vscode -!scripts/init.sh -target \ No newline at end of file +target/ +.dockerignore +Dockerfile diff --git a/Dockerfile b/Dockerfile index 2fc6cbcc6..e4bc5e9ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,9 @@ - ARG BASE_IMAGE=ubuntu:20.04 FROM $BASE_IMAGE as builder SHELL ["/bin/bash", "-c"] -# This is being set so that no interactive components are allowed when updating. +# Set noninteractive mode for apt-get ARG DEBIAN_FRONTEND=noninteractive LABEL ai.opentensor.image.authors="operations@opentensor.ai" \ @@ -15,51 +14,35 @@ LABEL ai.opentensor.image.authors="operations@opentensor.ai" \ ai.opentensor.image.created="${BUILD_DATE}" \ ai.opentensor.image.documentation="https://docs.bittensor.com" -# show backtraces +# Set up Rust environment ENV RUST_BACKTRACE 1 - -# Necessary libraries for Rust execution RUN apt-get update && \ apt-get install -y curl build-essential protobuf-compiler clang git && \ rm -rf /var/lib/apt/lists/* -# Install cargo and Rust RUN set -o pipefail && curl https://sh.rustup.rs -sSf | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN mkdir -p /subtensor && \ - mkdir /subtensor/scripts - -# Scripts -COPY ./scripts/init.sh /subtensor/scripts/ +RUN rustup update nightly +RUN rustup update stable +RUN rustup target add wasm32-unknown-unknown --toolchain nightly -# Capture dependencies -COPY Cargo.lock Cargo.toml /subtensor/ +# Copy entire repository +COPY . /build +WORKDIR /build -# Specs -COPY ./snapshot.json /subtensor/snapshot.json -COPY ./raw_spec_testfinney.json /subtensor/raw_spec_testfinney.json -COPY ./raw_spec_finney.json /subtensor/raw_spec_finney.json +# Build the project +RUN cargo build -p node-subtensor --profile production --features runtime-benchmarks --locked -# Copy our sources -COPY ./node /subtensor/node -COPY ./pallets /subtensor/pallets -COPY ./runtime /subtensor/runtime -COPY ./support /subtensor/support +# Verify the binary was produced +RUN test -e /build/target/production/node-subtensor -# Copy our toolchain -COPY rust-toolchain.toml /subtensor/ -RUN /subtensor/scripts/init.sh - -# Cargo build -WORKDIR /subtensor -RUN cargo build --profile production --features runtime-benchmarks --locked EXPOSE 30333 9933 9944 - FROM $BASE_IMAGE AS subtensor -COPY --from=builder /subtensor/snapshot.json / -COPY --from=builder /subtensor/raw_spec_testfinney.json / -COPY --from=builder /subtensor/raw_spec_finney.json / -COPY --from=builder /subtensor/target/production/node-subtensor /usr/local/bin +# Copy all chainspec files +COPY --from=builder /build/*.json / + +# Copy final binary +COPY --from=builder /build/target/production/node-subtensor /usr/local/bin From bb932e24c4d5f8f465b614f59d863a821ead3d1a Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Wed, 28 Aug 2024 22:30:20 +0400 Subject: [PATCH 268/269] chore: add metadatahash, build images only on publish tags --- .github/workflows/docker.yml | 33 +++++++++++++-------------------- Cargo.toml | 11 +++++++++-- Dockerfile | 12 ++++-------- node/Cargo.toml | 6 ++++-- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 19bda7463..72e3f1b12 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,49 +1,42 @@ name: Publish Docker Image on: - push: - branches: - - main - tags: - - '*' - pull_request: - branches: - - main - workflow_dispatch: + release: + types: [published] permissions: - contents: read - packages: write - actions: read - security-events: write + contents: read + packages: write + actions: read + security-events: write jobs: publish: runs-on: SubtensorCI - + steps: - name: Checkout code uses: actions/checkout@v4 - + - name: Set up QEMU uses: docker/setup-qemu-action@v2 - + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - + - name: Login to GHCR uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v4 with: images: ghcr.io/${{ github.repository }} - + - name: Build and push Docker image uses: docker/build-push-action@v4 with: @@ -52,4 +45,4 @@ jobs: tags: | ${{ steps.meta.outputs.tags }} ghcr.io/${{ github.repository }}:latest - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} diff --git a/Cargo.toml b/Cargo.toml index e3c2814ff..f9a7968b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,5 +167,12 @@ codegen-units = 1 [features] default = [] -try-runtime = ["node-subtensor/try-runtime", "node-subtensor-runtime/try-runtime"] -runtime-benchmarks = ["node-subtensor/runtime-benchmarks", "node-subtensor-runtime/runtime-benchmarks"] +try-runtime = [ + "node-subtensor/try-runtime", + "node-subtensor-runtime/try-runtime", +] +runtime-benchmarks = [ + "node-subtensor/runtime-benchmarks", + "node-subtensor-runtime/runtime-benchmarks", +] +metadata-hash = ["node-subtensor-runtime/metadata-hash"] diff --git a/Dockerfile b/Dockerfile index e4bc5e9ee..2dd2e2370 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_IMAGE=ubuntu:20.04 -FROM $BASE_IMAGE as builder +FROM $BASE_IMAGE AS builder SHELL ["/bin/bash", "-c"] # Set noninteractive mode for apt-get @@ -10,29 +10,25 @@ LABEL ai.opentensor.image.authors="operations@opentensor.ai" \ ai.opentensor.image.vendor="Opentensor Foundation" \ ai.opentensor.image.title="opentensor/subtensor" \ ai.opentensor.image.description="Opentensor Subtensor Blockchain" \ - ai.opentensor.image.revision="${VCS_REF}" \ - ai.opentensor.image.created="${BUILD_DATE}" \ ai.opentensor.image.documentation="https://docs.bittensor.com" # Set up Rust environment -ENV RUST_BACKTRACE 1 +ENV RUST_BACKTRACE=1 RUN apt-get update && \ apt-get install -y curl build-essential protobuf-compiler clang git && \ rm -rf /var/lib/apt/lists/* RUN set -o pipefail && curl https://sh.rustup.rs -sSf | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" - -RUN rustup update nightly RUN rustup update stable -RUN rustup target add wasm32-unknown-unknown --toolchain nightly +RUN rustup target add wasm32-unknown-unknown --toolchain stable # Copy entire repository COPY . /build WORKDIR /build # Build the project -RUN cargo build -p node-subtensor --profile production --features runtime-benchmarks --locked +RUN cargo build -p node-subtensor --profile production --features="runtime-benchmarks metadata-hash" --locked # Verify the binary was produced RUN test -e /build/target/production/node-subtensor diff --git a/node/Cargo.toml b/node/Cargo.toml index 0e1a418a3..3c5c91b92 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -92,7 +92,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-commitments/runtime-benchmarks" + "pallet-commitments/runtime-benchmarks", ] pow-faucet = [] @@ -103,5 +103,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-transaction-payment/try-runtime", "sp-runtime/try-runtime", - "pallet-commitments/try-runtime" + "pallet-commitments/try-runtime", ] + +metadata-hash = ["node-subtensor-runtime/metadata-hash"] From 24d581970df96ff056e82606905cb6f8761b1561 Mon Sep 17 00:00:00 2001 From: Samuel Dare Date: Thu, 29 Aug 2024 21:51:13 +0400 Subject: [PATCH 269/269] feat: use nucleus archive node --- .github/workflows/check-finney.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-finney.yml b/.github/workflows/check-finney.yml index 3e9fb5994..642e02c0a 100644 --- a/.github/workflows/check-finney.yml +++ b/.github/workflows/check-finney.yml @@ -30,7 +30,7 @@ jobs: - name: Check that spec_version has been bumped run: | - spec_version=$(PATH=$PATH:$HOME/.cargo/.bin substrate-spec-version wss://entrypoint-finney.opentensor.ai:443 | tr -d '\n') + spec_version=$(PATH=$PATH:$HOME/.cargo/.bin substrate-spec-version ${{ secrets.NUCLEUS_ARCHIVE_NODE }} | tr -d '\n') echo "network spec_version: $spec_version" : ${spec_version:?bad spec version} local_spec_version=$(cargo run -p node-subtensor-runtime --bin spec_version | tr -d '\n') @@ -49,6 +49,6 @@ jobs: uses: "paritytech/try-runtime-gha@v0.1.0" with: runtime-package: "node-subtensor-runtime" - node-uri: "wss://entrypoint-finney.opentensor.ai:443" + node-uri: ${{ secrets.NUCLEUS_ARCHIVE_NODE }} checks: "pre-and-post" - extra-args: "--disable-spec-version-check --no-weight-warnings" \ No newline at end of file + extra-args: "--disable-spec-version-check --no-weight-warnings"