From 0ff5f9a136dd681c79abfa40acb40784b81628cc Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 13 Jan 2025 20:07:17 -0500 Subject: [PATCH 01/25] wip --- pallets/subtensor/src/coinbase/root.rs | 5 ++ pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrate_fix_is_network_member.rs | 58 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index fee6e672b..c4fc1a9f9 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -527,6 +527,7 @@ impl Pallet { // --- 7. Remove incentive mechanism memory. let _ = Uids::::clear_prefix(netuid, u32::MAX, None); + let keys = Keys::::iter_prefix(netuid).collect::>(); let _ = Keys::::clear_prefix(netuid, u32::MAX, None); let _ = Bonds::::clear_prefix(netuid, u32::MAX, None); @@ -564,6 +565,10 @@ impl Pallet { ValidatorPermit::::remove(netuid); ValidatorTrust::::remove(netuid); + for key in keys { + IsNetworkMember::::remove(key, netuid); + } + // --- 11. Erase network parameters. Tempo::::remove(netuid); Kappa::::remove(netuid); diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index bf255c1c7..2a43238ab 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -74,7 +74,9 @@ mod hooks { // Migrate Commit-Reval 2.0 .saturating_add(migrations::migrate_commit_reveal_v2::migrate_commit_reveal_2::()) // Migrate to RAO - .saturating_add(migrations::migrate_rao::migrate_rao::()); + .saturating_add(migrations::migrate_rao::migrate_rao::()) + // Fix the IsNetworkMember map to be consistent with other storage maps + .saturating_add(migrations::migrate_fix_is_network_member::migrate_fix_is_network_member::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs b/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs new file mode 100644 index 000000000..0225cd8fb --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs @@ -0,0 +1,58 @@ +use super::*; +use alloc::string::String; +use frame_support::{traits::Get, weights::Weight}; +use log; + +pub fn migrate_fix_is_network_member() -> Weight { + let migration_name = b"migrate_fix_is_network_member".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) + ); + + weight = do_fix_is_network_member::(weight); + + // 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 +} + +fn do_fix_is_network_member(weight: Weight) -> Weight { + // Clear the IsNetworkMember storage + let mut curr = IsNetworkMember::::clear(U32::MAX, None); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(curr.loops, curr.unique)); + while curr.maybe_cursor.is_some() { + // Clear until empty + curr = IsNetworkMember::::clear(U32::MAX, curr.maybe_cursor); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(curr.loops, curr.unique)); + } + // Repopulate the IsNetworkMember storage using the Keys map + let netuids = Subtensor::::get_all_subnet_netuids(); + for netuid in netuids { + for key in Keys::::iter_prefix(netuid) { + IsNetworkMember::::insert(key, netuid, true); + } + } + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index dc9011f3e..6cf358c4d 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -4,6 +4,7 @@ pub mod migrate_commit_reveal_v2; pub mod migrate_create_root_network; pub mod migrate_delete_subnet_21; pub mod migrate_delete_subnet_3; +pub mod migrate_fix_is_network_member; pub mod migrate_fix_total_coldkey_stake; pub mod migrate_init_total_issuance; pub mod migrate_populate_owned_hotkeys; From 53e78f94cbc256aff8d7984f675e8a5efe318fc5 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Mon, 13 Jan 2025 21:31:42 -0500 Subject: [PATCH 02/25] fix syntax --- pallets/subtensor/src/coinbase/root.rs | 2 +- .../migrate_fix_is_network_member.rs | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index c4fc1a9f9..112c88785 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -565,7 +565,7 @@ impl Pallet { ValidatorPermit::::remove(netuid); ValidatorTrust::::remove(netuid); - for key in keys { + for (_uid, key) in keys { IsNetworkMember::::remove(key, netuid); } diff --git a/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs b/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs index 0225cd8fb..3c89717f1 100644 --- a/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs +++ b/pallets/subtensor/src/migrations/migrate_fix_is_network_member.rs @@ -38,20 +38,21 @@ pub fn migrate_fix_is_network_member() -> Weight { } fn do_fix_is_network_member(weight: Weight) -> Weight { + let mut weight = weight; // Clear the IsNetworkMember storage - let mut curr = IsNetworkMember::::clear(U32::MAX, None); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(curr.loops, curr.unique)); + let mut curr = IsNetworkMember::::clear(u32::MAX, None); + weight = weight + .saturating_add(T::DbWeight::get().reads_writes(curr.loops as u64, curr.unique as u64)); while curr.maybe_cursor.is_some() { // Clear until empty - curr = IsNetworkMember::::clear(U32::MAX, curr.maybe_cursor); - weight = weight.saturating_add(T::DbWeight::get().reads_writes(curr.loops, curr.unique)); + curr = IsNetworkMember::::clear(u32::MAX, curr.maybe_cursor.as_deref()); + weight = weight + .saturating_add(T::DbWeight::get().reads_writes(curr.loops as u64, curr.unique as u64)); } // Repopulate the IsNetworkMember storage using the Keys map - let netuids = Subtensor::::get_all_subnet_netuids(); - for netuid in netuids { - for key in Keys::::iter_prefix(netuid) { - IsNetworkMember::::insert(key, netuid, true); - } + for (netuid, _uid, key) in Keys::::iter() { + IsNetworkMember::::insert(key, netuid, true); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); } weight From 5ff711dd9a83d2760f64bff2059e9c847d565794 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 17 Jan 2025 15:12:18 -0500 Subject: [PATCH 03/25] make docker image builds trigger on key branches --- .github/workflows/docker.yml | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd00e9a31..4ace8c6fe 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,11 @@ on: release: types: [published] workflow_dispatch: + push: + branches: + - devnet-ready + - devnet + - testnet permissions: contents: read @@ -32,11 +37,24 @@ jobs: 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: Determine Docker tag + id: tag + run: | + case "${{ github.ref_name }}" in + devnet-ready) + echo "tag=devnet-ready" >> $GITHUB_ENV + ;; + devnet) + echo "tag=devnet" >> $GITHUB_ENV + ;; + testnet) + echo "tag=testnet" >> $GITHUB_ENV + ;; + *) + echo "Branch not recognized for custom tagging." + exit 1 + ;; + esac - name: Build and push Docker image uses: docker/build-push-action@v4 @@ -44,6 +62,5 @@ jobs: context: . push: true tags: | - ${{ steps.meta.outputs.tags }} + ghcr.io/${{ github.repository }}:${{ env.tag }} ghcr.io/${{ github.repository }}:latest - labels: ${{ steps.meta.outputs.labels }} From 18a4c6de7e264427ce75ed36201fc122186bdbc8 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 17 Jan 2025 15:58:10 -0500 Subject: [PATCH 04/25] fix --- .github/workflows/docker.yml | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4ace8c6fe..fa15d8174 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,6 +4,12 @@ on: release: types: [published] workflow_dispatch: + inputs: + branch-or-tag: + description: "Branch or tag to use for the Docker image tag (optional)" + required: false + default: "" + push: branches: - devnet-ready @@ -40,21 +46,10 @@ jobs: - name: Determine Docker tag id: tag run: | - case "${{ github.ref_name }}" in - devnet-ready) - echo "tag=devnet-ready" >> $GITHUB_ENV - ;; - devnet) - echo "tag=devnet" >> $GITHUB_ENV - ;; - testnet) - echo "tag=testnet" >> $GITHUB_ENV - ;; - *) - echo "Branch not recognized for custom tagging." - exit 1 - ;; - esac + # Use the provided branch-or-tag input or fallback to the current ref + branch_or_tag="${{ github.event.inputs.branch-or-tag || github.ref_name }}" + echo "Determined branch or tag: $branch_or_tag" + echo "tag=$branch_or_tag" >> $GITHUB_ENV - name: Build and push Docker image uses: docker/build-push-action@v4 From 53cdd8f2ed2bea4d45a20724eca7ff2faaacd543 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 17 Jan 2025 16:00:42 -0500 Subject: [PATCH 05/25] use specified ref --- .github/workflows/docker.yml | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fa15d8174..85fd20269 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: branch-or-tag: - description: "Branch or tag to use for the Docker image tag (optional)" + description: "Branch or tag to use for the Docker image tag and ref to checkout (optional)" required: false default: "" @@ -27,8 +27,19 @@ jobs: runs-on: SubtensorCI steps: + - name: Determine Docker tag and ref + id: tag + run: | + # Use the provided branch-or-tag input or fallback to the current ref + branch_or_tag="${{ github.event.inputs.branch-or-tag || github.ref_name }}" + echo "Determined branch or tag: $branch_or_tag" + echo "tag=$branch_or_tag" >> $GITHUB_ENV + echo "ref=$branch_or_tag" >> $GITHUB_ENV + - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ env.ref }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -43,14 +54,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Determine Docker tag - id: tag - run: | - # Use the provided branch-or-tag input or fallback to the current ref - branch_or_tag="${{ github.event.inputs.branch-or-tag || github.ref_name }}" - echo "Determined branch or tag: $branch_or_tag" - echo "tag=$branch_or_tag" >> $GITHUB_ENV - - name: Build and push Docker image uses: docker/build-push-action@v4 with: From fede19b80817315a512ec5a69b86db2198997e5c Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Fri, 17 Jan 2025 16:17:26 -0500 Subject: [PATCH 06/25] only bump latest tag if this is a github release! --- .github/workflows/docker.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 85fd20269..530c7cd8d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -30,7 +30,6 @@ jobs: - name: Determine Docker tag and ref id: tag run: | - # Use the provided branch-or-tag input or fallback to the current ref branch_or_tag="${{ github.event.inputs.branch-or-tag || github.ref_name }}" echo "Determined branch or tag: $branch_or_tag" echo "tag=$branch_or_tag" >> $GITHUB_ENV @@ -61,4 +60,3 @@ jobs: push: true tags: | ghcr.io/${{ github.repository }}:${{ env.tag }} - ghcr.io/${{ github.repository }}:latest From 8aa57171345bcabbbb88e03df344b96c24f0016d Mon Sep 17 00:00:00 2001 From: camfairchild Date: Mon, 20 Jan 2025 12:02:21 -0500 Subject: [PATCH 07/25] put owner cut in a pending --- Cargo.lock | 184 +++++------------- .../subtensor/src/coinbase/run_coinbase.rs | 9 +- pallets/subtensor/src/lib.rs | 3 + 3 files changed, 57 insertions(+), 139 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90c2fb86f..964c583e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1550,9 +1550,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] @@ -2187,9 +2187,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", "tiny-keccak", ] @@ -2220,12 +2220,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", - "primitive-types 0.12.2", + "impl-serde", + "primitive-types", "scale-info", - "uint 0.9.5", + "uint", ] [[package]] @@ -2269,7 +2269,7 @@ dependencies = [ "evm-runtime", "log", "parity-scale-codec", - "primitive-types 0.12.2", + "primitive-types", "rlp", "scale-info", "serde", @@ -2283,7 +2283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" dependencies = [ "parity-scale-codec", - "primitive-types 0.12.2", + "primitive-types", "scale-info", "serde", ] @@ -2297,7 +2297,7 @@ dependencies = [ "environmental", "evm-core", "evm-runtime", - "primitive-types 0.12.2", + "primitive-types", ] [[package]] @@ -2309,7 +2309,7 @@ dependencies = [ "auto_impl", "environmental", "evm-core", - "primitive-types 0.12.2", + "primitive-types", "sha3", ] @@ -2694,7 +2694,7 @@ version = "1.0.0-dev" source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac882333afed827053f31ef56ab739f7a2e" dependencies = [ "hex", - "impl-serde 0.4.0", + "impl-serde", "libsecp256k1", "log", "parity-scale-codec", @@ -3876,26 +3876,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-codec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-num-traits" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" -dependencies = [ - "integer-sqrt", - "num-traits", - "uint 0.10.0", -] - [[package]] name = "impl-rlp" version = "0.3.0" @@ -3914,15 +3894,6 @@ dependencies = [ "serde", ] -[[package]] -name = "impl-serde" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -4459,7 +4430,7 @@ dependencies = [ "sha2 0.10.8", "smallvec", "thiserror", - "uint 0.9.5", + "uint", "unsigned-varint 0.7.2", "void", ] @@ -4921,7 +4892,7 @@ dependencies = [ "tokio-util", "tracing", "trust-dns-resolver", - "uint 0.9.5", + "uint", "unsigned-varint 0.8.0", "url", "webpki", @@ -6664,7 +6635,7 @@ dependencies = [ "lru 0.8.1", "parity-util-mem-derive", "parking_lot 0.12.3", - "primitive-types 0.12.2", + "primitive-types", "smallvec", "winapi", ] @@ -6906,7 +6877,7 @@ dependencies = [ "libc", "log", "polkavm-assembler", - "polkavm-common 0.9.0", + "polkavm-common", "polkavm-linux-raw", ] @@ -6928,28 +6899,13 @@ dependencies = [ "log", ] -[[package]] -name = "polkavm-common" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0dbafef4ab6ceecb4982ac3b550df430ef4f9fdbf07c108b7d4f91a0682fce" - [[package]] name = "polkavm-derive" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ - "polkavm-derive-impl-macro 0.9.0", -] - -[[package]] -name = "polkavm-derive" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206caf322dfc02144510ad8360ff2051e5072f0874dcab3b410f78cdd52d0ebb" -dependencies = [ - "polkavm-derive-impl-macro 0.17.0", + "polkavm-derive-impl-macro", ] [[package]] @@ -6958,19 +6914,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ - "polkavm-common 0.9.0", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "polkavm-derive-impl" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42565aed4adbc4034612d0b17dea8db3681fb1bd1aed040d6edc5455a9f478a1" -dependencies = [ - "polkavm-common 0.17.0", + "polkavm-common", "proc-macro2", "quote", "syn 2.0.90", @@ -6982,17 +6926,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl 0.9.0", - "syn 2.0.90", -] - -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d9838e95241b0bce4fe269cdd4af96464160505840ed5a8ac8536119ba19e2" -dependencies = [ - "polkavm-derive-impl 0.17.0", + "polkavm-derive-impl", "syn 2.0.90", ] @@ -7006,7 +6940,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "object 0.32.2", - "polkavm-common 0.9.0", + "polkavm-common", "regalloc2 0.9.3", "rustc-demangle", ] @@ -7143,23 +7077,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", - "uint 0.9.5", -] - -[[package]] -name = "primitive-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" -dependencies = [ - "fixed-hash", - "impl-codec 0.7.0", - "impl-num-traits", - "uint 0.10.0", + "uint", ] [[package]] @@ -9405,9 +9327,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -9442,9 +9364,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -9936,7 +9858,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde 0.4.0", + "impl-serde", "itertools 0.11.0", "k256", "libsecp256k1", @@ -9946,7 +9868,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types 0.12.2", + "primitive-types", "rand", "scale-info", "schnorrkel", @@ -9970,7 +9892,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -10066,7 +9988,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "proc-macro2", "quote", @@ -10076,7 +9998,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "environmental", "parity-scale-codec", @@ -10129,7 +10051,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive", "rustversion", "secp256k1", "sp-core", @@ -10254,13 +10176,13 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.17.1", - "primitive-types 0.13.1", + "polkavm-derive", + "primitive-types", "sp-externalities 0.25.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", @@ -10278,8 +10200,8 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.9.1", - "primitive-types 0.12.2", + "polkavm-derive", + "primitive-types", "sp-externalities 0.29.0", "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", @@ -10292,7 +10214,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "Inflector", "expander", @@ -10394,14 +10316,14 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ - "impl-serde 0.5.0", + "impl-serde", "parity-scale-codec", "ref-cast", "serde", @@ -10413,7 +10335,7 @@ name = "sp-storage" version = "21.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "ref-cast", "serde", @@ -10435,7 +10357,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "parity-scale-codec", "tracing", @@ -10505,7 +10427,7 @@ name = "sp-version" version = "37.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "parity-wasm", "scale-info", @@ -10531,7 +10453,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -11705,18 +11627,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uint" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.17" diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index a150df654..11ea4887f 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -225,6 +225,10 @@ impl Pallet { PendingEmission::::mutate(*netuid, |total| { *total = total.saturating_add(pending_alpha_emission.to_num::()); }); + // Accumulate the owner cut in pending. + PendingOwnerCut::::mutate(*netuid, |total| { + *total = total.saturating_add(owner_cut); + }); } // --- 5. Drain pending emission through the subnet based on tempo. @@ -251,8 +255,9 @@ impl Pallet { let pending_root_divs: u64 = PendingRootDivs::::get(netuid); PendingRootDivs::::insert(netuid, 0); - // 5.2.3 Get owner cut. - let owner_cut: u64 = *owner_cuts.get(&netuid).unwrap_or(&0); + // 5.2.3 Get owner cut and drain. + let owner_cut: u64 = PendingOwnerCut::::get(netuid); + PendingOwnerCut::::insert(netuid, 0); // 5.2.4 Drain pending root divs, alpha emission, and owner cut. Self::drain_pending_emission( diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7f75568ba..4a2150f7f 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1097,6 +1097,9 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_root_emission pub type PendingRootDivs = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; #[pallet::storage] + /// --- MAP ( netuid ) --> pending_owner_cut + pub type PendingOwnerCut = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; + #[pallet::storage] /// --- MAP ( netuid ) --> blocks_since_last_step pub type BlocksSinceLastStep = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBlocksSinceLastStep>; From eef3ce637a1888286b00ef8386a7faa55d8d6edb Mon Sep 17 00:00:00 2001 From: camfairchild Date: Mon, 20 Jan 2025 14:49:25 -0500 Subject: [PATCH 08/25] move val prop to outside func --- pallets/subtensor/src/coinbase/run_coinbase.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 11ea4887f..259bf68cd 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -18,7 +18,11 @@ pub struct WeightsTlockPayload { } impl Pallet { - pub fn get_root_divs_in_alpha(netuid: u16, alpha_out_emission: I96F32) -> I96F32 { + pub fn get_root_divs_in_alpha( + netuid: u16, + alpha_out_emission: I96F32, + validator_proportion: I96F32, + ) -> I96F32 { // Get total TAO on root. let total_root_tao: I96F32 = I96F32::from_num(SubnetTAO::::get(0)); // Get total ALPHA on subnet. @@ -32,7 +36,8 @@ impl Pallet { // Get root proportion of alpha_out dividends. let root_divs_in_alpha: I96F32 = root_proportion .saturating_mul(alpha_out_emission) - .saturating_mul(I96F32::from_num(0.41)); + .saturating_mul(validator_proportion); // % of emission that goes to *all* validators. + // Return root_divs_in_alpha } @@ -207,9 +212,14 @@ impl Pallet { remaining_emission ); + // Validators get 50% of remaining emission. + let validator_proportion: I96F32 = I96F32::from_num(0.5); // Get proportion of alpha out emission as root divs. - let root_emission_in_alpha: I96F32 = - Self::get_root_divs_in_alpha(*netuid, I96F32::from_num(remaining_emission)); + let root_emission_in_alpha: I96F32 = Self::get_root_divs_in_alpha( + *netuid, + I96F32::from_num(remaining_emission), + validator_proportion, + ); // Subtract root divs from alpha divs. let pending_alpha_emission: I96F32 = I96F32::from_num(remaining_emission).saturating_sub(root_emission_in_alpha); From 2bb5e208455e37ac91ec393ca3f98955d659409a Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 21 Jan 2025 14:57:13 -0500 Subject: [PATCH 09/25] Fix/charge-for-swap-before-schedule (#1141) * charge for swap before sched * add swap cost to the events * pass swap cost into swap_coldkey call * fmt * oops, add arg * commit Cargo.lock * bump spec * fix tests --- pallets/subtensor/src/benchmarks.rs | 2 +- pallets/subtensor/src/macros/dispatches.rs | 12 +++- pallets/subtensor/src/macros/events.rs | 4 ++ pallets/subtensor/src/swap/swap_coldkey.rs | 5 +- pallets/subtensor/src/tests/swap_coldkey.rs | 67 ++++++++++++++++++--- runtime/src/lib.rs | 2 +- 6 files changed, 78 insertions(+), 14 deletions(-) diff --git a/pallets/subtensor/src/benchmarks.rs b/pallets/subtensor/src/benchmarks.rs index ecb5a1303..30d1f39e1 100644 --- a/pallets/subtensor/src/benchmarks.rs +++ b/pallets/subtensor/src/benchmarks.rs @@ -515,7 +515,7 @@ reveal_weights { Identities::::insert(&old_coldkey, identity); // Benchmark setup complete, now execute the extrinsic -}: swap_coldkey(RawOrigin::Root, old_coldkey.clone(), new_coldkey.clone()) +}: swap_coldkey(RawOrigin::Root, old_coldkey.clone(), new_coldkey.clone(), swap_cost) batch_reveal_weights { let tempo: u16 = 0; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7073a1413..285c9df66 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -962,12 +962,13 @@ mod dispatches { origin: OriginFor, old_coldkey: T::AccountId, new_coldkey: T::AccountId, + swap_cost: u64, ) -> DispatchResultWithPostInfo { // Ensure it's called with root privileges (scheduler has root privileges) ensure_root(origin)?; log::info!("swap_coldkey: {:?} -> {:?}", old_coldkey, new_coldkey); - Self::do_swap_coldkey(&old_coldkey, &new_coldkey) + Self::do_swap_coldkey(&old_coldkey, &new_coldkey, swap_cost) } /// Sets the childkey take for a given hotkey. @@ -1327,6 +1328,13 @@ mod dispatches { Error::::SwapAlreadyScheduled ); + // Calculate the swap cost and ensure sufficient balance + let swap_cost = Self::get_key_swap_cost(); + ensure!( + Self::can_remove_balance_from_coldkey_account(&who, swap_cost), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + let current_block: BlockNumberFor = >::block_number(); let duration: BlockNumberFor = ColdkeySwapScheduleDuration::::get(); let when: BlockNumberFor = current_block.saturating_add(duration); @@ -1334,6 +1342,7 @@ mod dispatches { let call = Call::::swap_coldkey { old_coldkey: who.clone(), new_coldkey: new_coldkey.clone(), + swap_cost, }; let bound_call = T::Preimages::bound(LocalCallOf::::from(call.clone())) @@ -1354,6 +1363,7 @@ mod dispatches { old_coldkey: who.clone(), new_coldkey: new_coldkey.clone(), execution_block: when, + swap_cost, }); Ok(().into()) diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 30566ac47..c1d25b8f4 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -155,6 +155,8 @@ mod events { old_coldkey: T::AccountId, /// the account ID of new coldkey new_coldkey: T::AccountId, + /// the swap cost + swap_cost: u64, }, /// All balance of a hotkey has been unstaked and transferred to a new coldkey AllBalanceUnstakedAndTransferredToNewColdkey { @@ -175,6 +177,8 @@ mod events { new_coldkey: T::AccountId, /// The arbitration block for the coldkey swap execution_block: BlockNumberFor, + /// The swap cost + swap_cost: u64, }, /// The arbitration period has been extended ArbitrationPeriodExtended { diff --git a/pallets/subtensor/src/swap/swap_coldkey.rs b/pallets/subtensor/src/swap/swap_coldkey.rs index 2f0a6f2c6..075538421 100644 --- a/pallets/subtensor/src/swap/swap_coldkey.rs +++ b/pallets/subtensor/src/swap/swap_coldkey.rs @@ -33,6 +33,7 @@ impl Pallet { pub fn do_swap_coldkey( old_coldkey: &T::AccountId, new_coldkey: &T::AccountId, + swap_cost: u64, ) -> DispatchResultWithPostInfo { // 2. Initialize the weight for this operation let mut weight: Weight = T::DbWeight::get().reads(2); @@ -55,8 +56,7 @@ impl Pallet { Identities::::insert(new_coldkey, identity); } - // 6. Calculate the swap cost and ensure sufficient balance - let swap_cost = Self::get_key_swap_cost(); + // 6. Ensure sufficient balance for the swap cost ensure!( Self::can_remove_balance_from_coldkey_account(old_coldkey, swap_cost), Error::::NotEnoughBalanceToPaySwapColdKey @@ -83,6 +83,7 @@ impl Pallet { Self::deposit_event(Event::ColdkeySwapped { old_coldkey: old_coldkey.clone(), new_coldkey: new_coldkey.clone(), + swap_cost, }); // 12. Return the result with the updated weight diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 0cbcecfaf..bf4f1ad40 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -709,7 +709,8 @@ fn test_do_swap_coldkey_success() { assert_ok!(SubtensorModule::do_swap_coldkey( // <::RuntimeOrigin>::signed(old_coldkey), &old_coldkey, - &new_coldkey + &new_coldkey, + swap_cost )); // Log state after swap @@ -782,6 +783,7 @@ fn test_do_swap_coldkey_success() { Event::ColdkeySwapped { old_coldkey, new_coldkey, + swap_cost, } .into(), ); @@ -1195,7 +1197,11 @@ fn test_do_swap_coldkey_with_subnet_ownership() { OwnedHotkeys::::insert(old_coldkey, vec![hotkey]); // Perform the swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + swap_cost + )); // Verify subnet ownership transfer assert_eq!(SubnetOwner::::get(netuid), new_coldkey); @@ -1652,8 +1658,10 @@ fn test_schedule_swap_coldkey_success() { let old_coldkey: U256 = U256::from(1); let new_coldkey: U256 = U256::from(2); + let swap_cost = SubtensorModule::get_key_swap_cost(); + // Add balance to the old coldkey account - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 1000); + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost + 1_000); // Schedule the coldkey swap assert_ok!(SubtensorModule::schedule_swap_coldkey( @@ -1673,6 +1681,7 @@ fn test_schedule_swap_coldkey_success() { old_coldkey, new_coldkey, execution_block: expected_execution_block, + swap_cost, } .into(), ); @@ -1689,7 +1698,9 @@ fn test_schedule_swap_coldkey_duplicate() { let old_coldkey = U256::from(1); let new_coldkey = U256::from(2); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 2000); + let swap_cost = SubtensorModule::get_key_swap_cost(); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost + 2_000); assert_ok!(SubtensorModule::schedule_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), @@ -1734,6 +1745,8 @@ fn test_schedule_swap_coldkey_execution() { "Initial ownership check failed" ); + let swap_cost = SubtensorModule::get_key_swap_cost(); + // Schedule the swap assert_ok!(SubtensorModule::schedule_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), @@ -1749,6 +1762,7 @@ fn test_schedule_swap_coldkey_execution() { old_coldkey, new_coldkey, execution_block, + swap_cost, } .into(), ); @@ -1790,6 +1804,7 @@ fn test_schedule_swap_coldkey_execution() { Event::ColdkeySwapped { old_coldkey, new_coldkey, + swap_cost, } .into(), ); @@ -1807,7 +1822,8 @@ fn test_direct_swap_coldkey_call_fails() { SubtensorModule::swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), old_coldkey, - new_coldkey + new_coldkey, + 0 ), BadOrigin ); @@ -1822,7 +1838,9 @@ fn test_schedule_swap_coldkey_with_pending_swap() { let new_coldkey1 = U256::from(2); let new_coldkey2 = U256::from(3); - SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, 2000); + let swap_cost = SubtensorModule::get_key_swap_cost(); + + SubtensorModule::add_balance_to_coldkey_account(&old_coldkey, swap_cost + 1_000); assert_ok!(SubtensorModule::schedule_swap_coldkey( <::RuntimeOrigin>::signed(old_coldkey), @@ -1876,7 +1894,11 @@ 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(&old_coldkey, &new_coldkey)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost + )); assert!(Identities::::get(old_coldkey).is_none()); assert!(Identities::::get(new_coldkey).is_some()); @@ -1912,7 +1934,11 @@ 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(&old_coldkey, &new_coldkey)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost + )); // Ensure no identities have been changed assert!(Identities::::get(old_coldkey).is_none()); @@ -1956,10 +1982,33 @@ fn test_coldkey_swap_no_identity_no_changes_newcoldkey_exists() { assert!(Identities::::get(old_coldkey).is_none()); // Perform the coldkey swap - assert_ok!(SubtensorModule::do_swap_coldkey(&old_coldkey, &new_coldkey)); + assert_ok!(SubtensorModule::do_swap_coldkey( + &old_coldkey, + &new_coldkey, + burn_cost + )); // Ensure no identities have been changed assert!(Identities::::get(old_coldkey).is_none()); assert!(Identities::::get(new_coldkey).is_some()); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=info cargo test --test swap_coldkey -- test_cant_schedule_swap_without_enough_to_burn --exact --nocapture +#[test] +fn test_cant_schedule_swap_without_enough_to_burn() { + new_test_ext(1).execute_with(|| { + let old_coldkey = U256::from(3); + let new_coldkey = U256::from(4); + let hotkey = U256::from(5); + + let burn_cost = SubtensorModule::get_key_swap_cost(); + assert_noop!( + SubtensorModule::schedule_swap_coldkey( + <::RuntimeOrigin>::signed(old_coldkey), + new_coldkey + ), + Error::::NotEnoughBalanceToPaySwapColdKey + ); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 72221b9ff..128d2b8f3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,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: 222, + spec_version: 223, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 09e205b1bd151dfe50d4486528b2e4a5f6f0690b Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 21 Jan 2025 15:28:51 -0500 Subject: [PATCH 10/25] Fix/per 1k estimate always zero (#1152) * add fix for return per 1k * extract helper and add test --- .../subtensor/src/rpc_info/delegate_info.rs | 38 +++++++++++++++---- pallets/subtensor/src/tests/delegate_info.rs | 33 ++++++++++++++++ pallets/subtensor/src/tests/mod.rs | 1 + 3 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 pallets/subtensor/src/tests/delegate_info.rs diff --git a/pallets/subtensor/src/rpc_info/delegate_info.rs b/pallets/subtensor/src/rpc_info/delegate_info.rs index 5cd234b48..5776c48af 100644 --- a/pallets/subtensor/src/rpc_info/delegate_info.rs +++ b/pallets/subtensor/src/rpc_info/delegate_info.rs @@ -21,6 +21,34 @@ pub struct DelegateInfo { } impl Pallet { + fn return_per_1000_tao( + take: Compact, + total_stake: U64F64, + emissions_per_day: U64F64, + ) -> U64F64 { + // Get the take as a percentage and subtract it from 1 for remainder. + let without_take: U64F64 = U64F64::from_num(1) + .saturating_sub(U64F64::from_num(take.0).saturating_div(u16::MAX.into())); + + if total_stake > U64F64::from_num(0) { + emissions_per_day + .saturating_mul(without_take) + // Divide by 1000 TAO for return per 1k + .saturating_div(total_stake.saturating_div(U64F64::from_num(1000.0 * 1e9))) + } else { + U64F64::from_num(0) + } + } + + #[cfg(test)] + pub fn return_per_1000_tao_test( + take: Compact, + total_stake: U64F64, + emissions_per_day: U64F64, + ) -> U64F64 { + Self::return_per_1000_tao(take, total_stake, emissions_per_day) + } + fn get_delegate_by_existing_account(delegate: AccountIdOf) -> DelegateInfo { let mut nominators = Vec::<(T::AccountId, Compact)>::new(); @@ -63,14 +91,8 @@ impl Pallet { let total_stake: U64F64 = Self::get_stake_for_hotkey_on_subnet(&delegate.clone(), Self::get_root_netuid()).into(); - let return_per_1000: U64F64 = if total_stake > U64F64::from_num(0) { - emissions_per_day - .saturating_mul(u16::MAX.saturating_sub(take.0).into()) - .saturating_div(u16::MAX.into()) - .saturating_div(total_stake.saturating_div(U64F64::from_num(1000))) - } else { - U64F64::from_num(0) - }; + let return_per_1000: U64F64 = + Self::return_per_1000_tao(take, total_stake, emissions_per_day); DelegateInfo { delegate_ss58: delegate.clone(), diff --git a/pallets/subtensor/src/tests/delegate_info.rs b/pallets/subtensor/src/tests/delegate_info.rs new file mode 100644 index 000000000..78ea48482 --- /dev/null +++ b/pallets/subtensor/src/tests/delegate_info.rs @@ -0,0 +1,33 @@ +use codec::Compact; +use substrate_fixed::types::U64F64; + +use super::mock::*; + +#[test] +fn test_return_per_1000_tao() { + let take = // 18% take to the Validator + Compact::::from((U64F64::from_num(0.18 * u16::MAX as f64)).saturating_to_num::()); + + // 10_000 TAO total validator stake + let total_stake = U64F64::from_num(10_000.0 * 1e9); + // 1000 TAO emissions per day + let emissions_per_day = U64F64::from_num(1000.0 * 1e9); + + let return_per_1000 = + SubtensorModule::return_per_1000_tao_test(take, total_stake, emissions_per_day); + + // We expect 82 TAO per day with 10% of total_stake + let expected_return_per_1000 = U64F64::from_num(82.0); + + let diff_from_expected: f64 = (return_per_1000 / U64F64::from_num(1e9)) + .saturating_sub(expected_return_per_1000) + .to_num::(); + + let eps: f64 = 0.0005e9; // Precision within 0.0005 TAO + assert!( + diff_from_expected.abs() <= eps, + "Difference from expected: {} is greater than precision: {}", + diff_from_expected, + eps + ); +} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index e0fef9d55..6865c9fa4 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -1,6 +1,7 @@ mod batch_tx; mod children; mod coinbase; +mod delegate_info; mod difficulty; mod emission; mod epoch; From d392516865272997a982258e968b6dd8557d6c2c Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 21 Jan 2025 15:32:28 -0500 Subject: [PATCH 11/25] Send staking and unstaking fees to SubnetTAO --- pallets/subtensor/src/staking/stake_utils.rs | 63 +++++++++---- pallets/subtensor/src/tests/staking.rs | 97 ++++++++++++++++++++ 2 files changed, 140 insertions(+), 20 deletions(-) diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 524cd2069..b79b141d0 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -1,5 +1,7 @@ use super::*; +use crate::DefaultMinStake; use share_pool::{SharePool, SharePoolDataOperations}; +use sp_core::Get; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64}; @@ -621,11 +623,19 @@ impl Pallet { // }); // } - // Step 4. Deposit and log the unstaking event. + // Step 4. Reduce tao amount by staking fee and credit this fee to SubnetTAO + let fee = DefaultMinStake::::get(); + let tao_unstaked = tao.saturating_sub(fee); + let actual_fee = tao.saturating_sub(tao_unstaked); + SubnetTAO::::mutate(netuid, |total| { + *total = total.saturating_add(actual_fee); + }); + + // Step 5. Deposit and log the unstaking event. Self::deposit_event(Event::StakeRemoved( coldkey.clone(), hotkey.clone(), - tao, + tao_unstaked, alpha, netuid, )); @@ -633,13 +643,13 @@ impl Pallet { "StakeRemoved( coldkey: {:?}, hotkey:{:?}, tao: {:?}, alpha:{:?}, netuid: {:?} )", coldkey.clone(), hotkey.clone(), - tao, + tao_unstaked, alpha, netuid ); - // Step 5: Return the amount of TAO unstaked. - tao + // Step 6: Return the amount of TAO unstaked. + tao_unstaked } /// Stakes TAO into a subnet for a given hotkey and coldkey pair. @@ -651,24 +661,37 @@ impl Pallet { netuid: u16, tao: u64, ) -> u64 { - // Step 1. Swap the tao to alpha. - let alpha: u64 = Self::swap_tao_for_alpha(netuid, tao); - - // Step 2: Increase the alpha on the hotkey account. - Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, alpha); - - // Step 4: Update the list of hotkeys staking for this coldkey - let mut staking_hotkeys = StakingHotkeys::::get(coldkey); - if !staking_hotkeys.contains(hotkey) { - staking_hotkeys.push(hotkey.clone()); - StakingHotkeys::::insert(coldkey, staking_hotkeys.clone()); + // Step 1. Reduce tao amount by staking fee and credit this fee to SubnetTAO + // At this point tao was already withdrawn from the user balance and is considered + // available + let fee = DefaultMinStake::::get(); + let tao_staked = tao.saturating_sub(fee); + let actual_fee = tao.saturating_sub(tao_staked); + + // Step 2. Swap the tao to alpha. + let alpha: u64 = Self::swap_tao_for_alpha(netuid, tao_staked); + if (tao_staked > 0) && (alpha > 0) { + // Step 3: Increase the alpha on the hotkey account. + Self::increase_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid, alpha); + + // Step 4: Update the list of hotkeys staking for this coldkey + let mut staking_hotkeys = StakingHotkeys::::get(coldkey); + if !staking_hotkeys.contains(hotkey) { + staking_hotkeys.push(hotkey.clone()); + StakingHotkeys::::insert(coldkey, staking_hotkeys.clone()); + } } - // Step 5. Deposit and log the staking event. + // Step 5. Increase Tao reserves by the fee amount. + SubnetTAO::::mutate(netuid, |total| { + *total = total.saturating_add(actual_fee); + }); + + // Step 6. Deposit and log the staking event. Self::deposit_event(Event::StakeAdded( coldkey.clone(), hotkey.clone(), - tao, + tao_staked, alpha, netuid, )); @@ -676,12 +699,12 @@ impl Pallet { "StakeAdded( coldkey: {:?}, hotkey:{:?}, tao: {:?}, alpha:{:?}, netuid: {:?} )", coldkey.clone(), hotkey.clone(), - tao, + tao_staked, alpha, netuid ); - // Step 6: Return the amount of alpha staked + // Step 7: Return the amount of alpha staked alpha } diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 6594887b9..882184811 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -1941,3 +1941,100 @@ fn test_staking_too_little_fails() { ); }); } + +// cargo test --package pallet-subtensor --lib -- tests::staking::test_add_stake_fee_goes_to_subnet_tao --exact --show-output --nocapture +#[test] +fn test_add_stake_fee_goes_to_subnet_tao() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let existential_deposit = ExistentialDeposit::get(); + let tao_to_stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + let subnet_tao_before = SubnetTAO::::get(netuid); + + // Add stake + SubtensorModule::add_balance_to_coldkey_account(&coldkey, tao_to_stake); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + tao_to_stake + )); + + // Calculate expected stake + let expected_alpha = tao_to_stake - existential_deposit - fee; + let actual_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let subnet_tao_after = SubnetTAO::::get(netuid); + + // Total subnet stake should match the sum of delegators' stakes minus existential deposits. + assert_abs_diff_eq!( + actual_alpha, + expected_alpha, + epsilon = expected_alpha / 1000 + ); + + // Subnet TAO should have increased by the full tao_to_stake amount + assert_abs_diff_eq!( + subnet_tao_before + tao_to_stake, + subnet_tao_after, + epsilon = 10 + ); + }); +} + +// cargo test --package pallet-subtensor --lib -- tests::staking::test_remove_stake_fee_goes_to_subnet_tao --exact --show-output --nocapture +#[test] +fn test_remove_stake_fee_goes_to_subnet_tao() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let tao_to_stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + let subnet_tao_before = SubnetTAO::::get(netuid); + + // Add stake + SubtensorModule::add_balance_to_coldkey_account(&coldkey, tao_to_stake); + assert_ok!(SubtensorModule::add_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + tao_to_stake + )); + + // Remove all stake + let alpha_to_unstake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_ok!(SubtensorModule::remove_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + alpha_to_unstake + )); + let subnet_tao_after = SubnetTAO::::get(netuid); + + // Subnet TAO should have increased by 2x fee as a result of staking + unstaking + assert_abs_diff_eq!( + subnet_tao_before + 2 * fee, + subnet_tao_after, + epsilon = alpha_to_unstake / 1000 + ); + + // User balance should decrease by 2x fee as a result of staking + unstaking + let balance_after = SubtensorModule::get_coldkey_balance(&coldkey); + assert_abs_diff_eq!( + balance_after + 2 * fee, + tao_to_stake, + epsilon = tao_to_stake / 1000 + ); + }); +} \ No newline at end of file From 01203b329539c81ecd4dded6c14332bf1fb0c70f Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 21 Jan 2025 15:52:45 -0500 Subject: [PATCH 12/25] add alpha swapped tracker --- .../subtensor/src/coinbase/run_coinbase.rs | 22 +++++++++++++++---- pallets/subtensor/src/lib.rs | 4 ++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 259bf68cd..e86c07c24 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -231,6 +231,10 @@ impl Pallet { PendingRootDivs::::mutate(*netuid, |total| { *total = total.saturating_add(root_emission_in_tao); }); + // Accumulate alpha that was swapped for the pending root divs. + PendingAlphaSwapped::::mutate(*netuid, |total| { + *total = total.saturating_add(root_emission_in_alpha.to_num::()); + }); // Accumulate alpha emission in pending. PendingEmission::::mutate(*netuid, |total| { *total = total.saturating_add(pending_alpha_emission.to_num::()); @@ -261,10 +265,14 @@ impl Pallet { let pending_emission: u64 = PendingEmission::::get(netuid); PendingEmission::::insert(netuid, 0); - // 5.2.2 Get and drain the subnet pending root divs. + // 5.2.2a Get and drain the subnet pending root divs. let pending_root_divs: u64 = PendingRootDivs::::get(netuid); PendingRootDivs::::insert(netuid, 0); + // 5.2.2b Get this amount as alpha that was swapped for pending root divs. + let pending_alpha_swapped: u64 = PendingAlphaSwapped::::get(netuid); + PendingAlphaSwapped::::insert(netuid, 0); + // 5.2.3 Get owner cut and drain. let owner_cut: u64 = PendingOwnerCut::::get(netuid); PendingOwnerCut::::insert(netuid, 0); @@ -274,6 +282,7 @@ impl Pallet { netuid, pending_emission, pending_root_divs, + pending_alpha_swapped, owner_cut, ); } else { @@ -287,19 +296,24 @@ impl Pallet { netuid: u16, pending_alpha_emission: u64, pending_root_divs: u64, + pending_alpha_swapped: u64, owner_cut: u64, ) { log::debug!( - "Draining pending alpha emission for netuid {:?}: {:?}, with pending root divs {:?}, and owner cut {:?}", + "Draining pending alpha emission for netuid {:?}: {:?}, with pending root divs {:?}, pending alpha swapped {:?}, and owner cut {:?}", netuid, pending_alpha_emission, pending_root_divs, + pending_alpha_swapped, owner_cut ); // Run the epoch() --> hotkey emission. - let hotkey_emission: Vec<(T::AccountId, u64, u64)> = - Self::epoch(netuid, pending_alpha_emission); + // Needs to run on the full emission to the subnet. + let hotkey_emission: Vec<(T::AccountId, u64, u64)> = Self::epoch( + netuid, + pending_alpha_emission.saturating_add(pending_alpha_swapped), + ); log::debug!( "Hotkey emission for netuid {:?}: {:?}", netuid, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 4a2150f7f..1d2690503 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1097,6 +1097,10 @@ pub mod pallet { /// --- MAP ( netuid ) --> pending_root_emission pub type PendingRootDivs = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; #[pallet::storage] + /// --- MAP ( netuid ) --> pending_alpha_swapped + pub type PendingAlphaSwapped = + StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; + #[pallet::storage] /// --- MAP ( netuid ) --> pending_owner_cut pub type PendingOwnerCut = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; #[pallet::storage] From f0f4c6b990dccb5cf7e9bdd6dc8f3e0dbf7f9fc7 Mon Sep 17 00:00:00 2001 From: JohnReedV <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:34:46 -0800 Subject: [PATCH 13/25] add transfer_stake & tests --- pallets/subtensor/src/macros/dispatches.rs | 44 ++++ pallets/subtensor/src/macros/events.rs | 5 + pallets/subtensor/src/staking/move_stake.rs | 107 +++++++++ pallets/subtensor/src/tests/move_stake.rs | 253 ++++++++++++++++++++ 4 files changed, 409 insertions(+) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7073a1413..34712ccb5 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1596,5 +1596,49 @@ mod dispatches { alpha_amount, ) } + + /// Transfers a specified amount of stake from one coldkey to another, optionally across subnets, + /// while keeping the same hotkey. + /// + /// # Arguments + /// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`. + /// * `destination_coldkey` - The coldkey to which the stake is transferred. + /// * `hotkey` - The hotkey associated with the stake. + /// * `origin_netuid` - The network/subnet ID to move stake from. + /// * `destination_netuid` - The network/subnet ID to move stake to (for cross-subnet transfer). + /// * `alpha_amount` - The amount of stake to transfer. + /// + /// # Weight + /// Uses a fixed weight of 3_000_000 (plus any DB write overhead). + /// + /// # Errors + /// Returns an error if: + /// * The origin is not signed by the correct coldkey. + /// * Either subnet does not exist. + /// * The hotkey does not exist. + /// * There is insufficient stake on `(origin_coldkey, hotkey, origin_netuid)`. + /// * The transfer amount is below the minimum stake requirement. + /// + /// # Events + /// May emit a `StakeTransferred` event on success. + #[pallet::call_index(86)] + #[pallet::weight((Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Operational, Pays::No))] + pub fn transfer_stake( + origin: T::RuntimeOrigin, + destination_coldkey: T::AccountId, + hotkey: T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> DispatchResult { + Self::do_transfer_stake( + origin, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 30566ac47..782f6b792 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -248,5 +248,10 @@ mod events { /// /// - **error**: The dispatch error emitted by the failed item. BatchWeightItemFailed(sp_runtime::DispatchError), + + /// Stake has been transferred from one coldkey to another on the same subnet. + /// Parameters: + /// (origin_coldkey, destination_coldkey, hotkey, origin_netuid, destination_netuid, amount) + StakeTransferred(T::AccountId, T::AccountId, T::AccountId, u16, u16, u64), } } diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 9628c5814..49d823b86 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -110,4 +110,111 @@ impl Pallet { // -- 10. Ok and return. Ok(()) } + + /// Transfers stake from one coldkey to another, optionally moving from one subnet to another, + /// while keeping the same hotkey. + /// + /// # Arguments + /// * `origin` - The origin of the transaction, which must be signed by the `origin_coldkey`. + /// * `destination_coldkey` - The account ID of the coldkey to which the stake is being transferred. + /// * `hotkey` - The account ID of the hotkey associated with this stake. + /// * `origin_netuid` - The network ID (subnet) from which the stake is being transferred. + /// * `destination_netuid` - The network ID (subnet) to which the stake is being transferred. + /// * `alpha_amount` - The amount of stake to transfer. + /// + /// # Returns + /// * `DispatchResult` - Indicates success or failure. + /// + /// # Errors + /// This function will return an error if: + /// * The transaction is not signed by the `origin_coldkey`. + /// * The subnet (`origin_netuid` or `destination_netuid`) does not exist. + /// * The `hotkey` does not exist. + /// * The `(origin_coldkey, hotkey, origin_netuid)` does not have enough stake for `alpha_amount`. + /// * The amount to be transferred is below the minimum stake requirement. + /// * There is a failure in staking or unstaking logic. + /// + /// # Events + /// Emits a `StakeTransferred` event upon successful completion of the transfer. + pub fn do_transfer_stake( + origin: T::RuntimeOrigin, + destination_coldkey: T::AccountId, + hotkey: T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> dispatch::DispatchResult { + // 1. Ensure the extrinsic is signed by the origin_coldkey. + let coldkey = ensure_signed(origin)?; + + // 2. Ensure both subnets exist. + ensure!( + Self::if_subnet_exist(origin_netuid), + Error::::SubnetNotExists + ); + ensure!( + Self::if_subnet_exist(destination_netuid), + Error::::SubnetNotExists + ); + + // 3. Check that the hotkey exists. + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + // 4. Check that the signed coldkey actually owns the given hotkey. + ensure!( + Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::NonAssociatedColdKey + ); + + // 5. Get current stake. + let origin_alpha = + Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, origin_netuid); + ensure!( + alpha_amount <= origin_alpha, + Error::::NotEnoughStakeToWithdraw + ); + + // 6. Unstake from the origin coldkey; this returns an amount of TAO. + let origin_tao = Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount); + + // 7. Ensure the returned TAO meets a minimum stake requirement (if required). + ensure!( + origin_tao >= DefaultMinStake::::get(), + Error::::AmountTooLow + ); + + // 8. Stake the TAO into `(destination_coldkey, hotkey)` on the destination subnet. + // Create the account if it does not exist. + Self::stake_into_subnet( + &hotkey, + &destination_coldkey, + destination_netuid, + origin_tao, + ); + + // 9. Emit an event for logging/monitoring. + log::info!( + "StakeTransferred(origin_coldkey: {:?}, destination_coldkey: {:?}, hotkey: {:?}, origin_netuid: {:?}, destination_netuid: {:?}, amount: {:?})", + coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + origin_tao + ); + Self::deposit_event(Event::StakeTransferred( + coldkey, + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + origin_tao, + )); + + // 10. Return success. + Ok(()) + } } diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index f26a82432..59afc9b62 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -773,3 +773,256 @@ fn test_moving_too_little_fails() { ); }); } + +#[test] +fn test_do_transfer_success() { + new_test_ext(1).execute_with(|| { + // 1. Create a new dynamic network and IDs. + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // 2. Define the origin coldkey, destination coldkey, and hotkey to be used. + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // 3. Set up initial stake: (origin_coldkey, hotkey) on netuid. + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::create_account_if_non_existent(&destination_coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid, + ); + + // 4. Transfer the entire stake to the destination coldkey on the same subnet (netuid, netuid). + assert_ok!(SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + alpha + )); + + // 5. Check that the stake has moved. + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + netuid + ), + 0 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &destination_coldkey, + netuid + ), + stake_amount, + epsilon = stake_amount / 1000 + ); + }); +} + +#[test] +fn test_do_transfer_nonexistent_subnet() { + new_test_ext(1).execute_with(|| { + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let nonexistent_netuid = 9999; + let stake_amount = DefaultMinStake::::get() * 5; + + assert_noop!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + nonexistent_netuid, + nonexistent_netuid, + stake_amount + ), + Error::::SubnetNotExists + ); + }); +} + +#[test] +fn test_do_transfer_nonexistent_hotkey() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let nonexistent_hotkey = U256::from(999); + + assert_noop!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + nonexistent_hotkey, + netuid, + netuid, + 100 + ), + Error::::HotKeyAccountNotExists + ); + }); +} + +#[test] +fn test_do_transfer_insufficient_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + + let alpha = stake_amount * 2; + assert_noop!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + alpha + ), + Error::::NotEnoughStakeToWithdraw + ); + }); +} + +#[test] +fn test_do_transfer_wrong_origin() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1010); + let subnet_owner_hotkey = U256::from(1011); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let wrong_coldkey = U256::from(9999); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let stake_amount = 100_000; + + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, 1_000_000_000); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + + assert_noop!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(wrong_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + stake_amount + ), + Error::::NonAssociatedColdKey + ); + }); +} + +#[test] +fn test_do_transfer_minimum_stake_check() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + + let stake_amount = DefaultMinStake::::get(); + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + + assert_err!( + SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + netuid, + netuid, + 1 + ), + Error::::AmountTooLow + ); + }); +} + +#[test] +fn test_do_transfer_different_subnets() { + new_test_ext(1).execute_with(|| { + // 1. Create two distinct subnets. + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // 2. Define origin/destination coldkeys and hotkey. + let origin_coldkey = U256::from(1); + let destination_coldkey = U256::from(2); + let hotkey = U256::from(3); + let stake_amount = DefaultMinStake::::get() * 10; + + // 3. Create accounts if needed. + SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); + SubtensorModule::create_account_if_non_existent(&destination_coldkey, &hotkey); + + // 4. Deposit free balance so transaction fees do not reduce staked funds. + SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, 1_000_000_000); + + // 5. Stake into the origin subnet. + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, origin_netuid, stake_amount); + + // 6. Transfer entire stake from origin_netuid -> destination_netuid. + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + origin_netuid, + ); + assert_ok!(SubtensorModule::do_transfer_stake( + RuntimeOrigin::signed(origin_coldkey), + destination_coldkey, + hotkey, + origin_netuid, + destination_netuid, + alpha + )); + + // 7. Verify origin now has 0 in origin_netuid. + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &origin_coldkey, + origin_netuid + ), + 0 + ); + + // 8. Verify stake ended up in destination subnet for destination coldkey. + let dest_stake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &destination_coldkey, + destination_netuid, + ); + assert_abs_diff_eq!(dest_stake, stake_amount, epsilon = stake_amount / 1000); + }); +} From a516c2b6de51cfb1fbb59bceef2da27b93ccd65b Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 21 Jan 2025 19:22:51 -0500 Subject: [PATCH 14/25] Implement fees for add_stake, remove_stake, and move_stake --- pallets/subtensor/src/lib.rs | 42 +++- pallets/subtensor/src/staking/add_stake.rs | 3 +- pallets/subtensor/src/staking/helpers.rs | 3 +- pallets/subtensor/src/staking/move_stake.rs | 5 +- pallets/subtensor/src/staking/remove_stake.rs | 17 +- pallets/subtensor/src/staking/stake_utils.rs | 14 +- pallets/subtensor/src/tests/mock.rs | 3 +- pallets/subtensor/src/tests/move_stake.rs | 90 +++++--- pallets/subtensor/src/tests/senate.rs | 30 +-- pallets/subtensor/src/tests/staking.rs | 197 +++++++++++++----- pallets/subtensor/src/tests/swap_coldkey.rs | 51 +++-- pallets/subtensor/src/tests/swap_hotkey.rs | 5 +- pallets/subtensor/src/tests/weights.rs | 12 +- 13 files changed, 326 insertions(+), 146 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7f75568ba..58aef11a8 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -12,7 +12,10 @@ use frame_support::{ dispatch::{self, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, pallet_macros::import_section, - traits::{tokens::fungible, IsSubType}, + traits::{ + tokens::{fungible, Fortitude, Preservation}, + IsSubType, + }, }; use codec::{Decode, Encode}; @@ -21,6 +24,7 @@ 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_core::Get; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, transaction_validity::{TransactionValidity, TransactionValidityError}, @@ -697,9 +701,10 @@ pub mod pallet { #[pallet::type_value] /// Default minimum stake. - /// 2M rao matches $1 at $500/TAO + /// 500k rao matches $0.25 at $500/TAO + /// Also used as staking fee pub fn DefaultMinStake() -> u64 { - 2_000_000 + 500_000 } #[pallet::type_value] @@ -1532,12 +1537,14 @@ pub enum CallType { #[derive(Debug, PartialEq)] pub enum CustomTransactionError { ColdkeyInSwapSchedule, + StakeAmountTooLow, } impl From for u8 { fn from(variant: CustomTransactionError) -> u8 { match variant { CustomTransactionError::ColdkeyInSwapSchedule => 0, + CustomTransactionError::StakeAmountTooLow => 1, } } } @@ -1687,10 +1694,31 @@ where Err(InvalidTransaction::Custom(7).into()) } } - Some(Call::add_stake { .. }) => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), + Some(Call::add_stake { + hotkey: _, + netuid: _, + amount_staked, + }) => { + // Check that amount parameter is at least the min stake + // also check the coldkey balance + let coldkey_balance = <::Currency as fungible::Inspect< + ::AccountId, + >>::reducible_balance( + who, Preservation::Expendable, Fortitude::Polite + ); + + if (*amount_staked < DefaultMinStake::::get()) + || (coldkey_balance < DefaultMinStake::::get()) + { + InvalidTransaction::Custom(CustomTransactionError::StakeAmountTooLow.into()) + .into() + } else { + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } + } Some(Call::remove_stake { .. }) => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index f63735cd3..66b337b3d 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -74,7 +74,8 @@ impl Pallet { // 6. Swap the stake into alpha on the subnet and increase counters. // Emit the staking event. - Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked); + let fee = DefaultMinStake::::get(); + Self::stake_into_subnet(&hotkey, &coldkey, netuid, tao_staked, fee); // Ok and return. Ok(()) diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 4f99d954e..1a1a2d00d 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -163,7 +163,8 @@ impl Pallet { // Log the clearing of a small nomination // Remove the stake from the nominator account. (this is a more forceful unstake operation which ) // Actually deletes the staking account. - let cleared_stake = Self::unstake_from_subnet(hotkey, coldkey, netuid, stake); + // Do not apply any fees + let cleared_stake = Self::unstake_from_subnet(hotkey, coldkey, netuid, stake, 0); // Add the stake to the coldkey account. Self::add_balance_to_coldkey_account(coldkey, cleared_stake); } diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 9628c5814..6fbd0db07 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -68,11 +68,13 @@ impl Pallet { ); // --- 7. Unstake the amount of alpha from the origin subnet, converting it to TAO + let fee = DefaultMinStake::::get().saturating_div(2); // fee is half of min stake because it is applied twice let origin_tao = Self::unstake_from_subnet( &origin_hotkey.clone(), &coldkey.clone(), origin_netuid, alpha_amount, + fee, ); // Ensure origin_tao is at least DefaultMinStake @@ -87,6 +89,7 @@ impl Pallet { &coldkey.clone(), destination_netuid, origin_tao, + fee, ); // --- 9. Log the event. @@ -104,7 +107,7 @@ impl Pallet { origin_netuid, destination_hotkey, destination_netuid, - origin_tao, + origin_tao.saturating_sub(fee), )); // -- 10. Ok and return. diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 5578219d3..5b3ed3390 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,4 +1,5 @@ use super::*; +use sp_core::Get; impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. @@ -65,8 +66,9 @@ impl Pallet { ); // 6. Swap the alpba to tao and update counters for this subnet. + let fee = DefaultMinStake::::get(); let tao_unstaked: u64 = - Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked); + Self::unstake_from_subnet(&hotkey, &coldkey, netuid, alpha_unstaked, fee); // 7. We add the balance to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked); @@ -81,10 +83,6 @@ impl Pallet { }) } - // TODO: Regression - // Emit the unstaking event. - // Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed)); - // Done and ok. Ok(()) } @@ -119,6 +117,8 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { + let fee = DefaultMinStake::::get(); + // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey); @@ -141,7 +141,7 @@ impl Pallet { if alpha_unstaked > 0 { // Swap the alpha to tao and update counters for this subnet. let tao_unstaked: u64 = - Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked); + Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee); // Add the balance to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, tao_unstaked); @@ -185,6 +185,8 @@ impl Pallet { origin: T::RuntimeOrigin, hotkey: T::AccountId, ) -> dispatch::DispatchResult { + let fee = DefaultMinStake::::get(); + // 1. We check the transaction is signed by the caller and retrieve the T::AccountId coldkey information. let coldkey = ensure_signed(origin)?; log::info!("do_unstake_all( origin:{:?} hotkey:{:?} )", coldkey, hotkey); @@ -210,7 +212,7 @@ impl Pallet { if alpha_unstaked > 0 { // Swap the alpha to tao and update counters for this subnet. let tao_unstaked: u64 = - Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked); + Self::unstake_from_subnet(&hotkey, &coldkey, *netuid, alpha_unstaked, fee); // Increment total total_tao_unstaked = total_tao_unstaked.saturating_add(tao_unstaked); @@ -227,6 +229,7 @@ impl Pallet { &coldkey, Self::get_root_netuid(), total_tao_unstaked, + 0, // no fee for restaking ); // 5. Done and ok. diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index b79b141d0..d6901eec2 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -1,7 +1,5 @@ use super::*; -use crate::DefaultMinStake; use share_pool::{SharePool, SharePoolDataOperations}; -use sp_core::Get; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64}; @@ -608,6 +606,7 @@ impl Pallet { coldkey: &T::AccountId, netuid: u16, alpha: u64, + fee: u64, ) -> u64 { // Step 1: Swap the alpha for TAO. let tao: u64 = Self::swap_alpha_for_tao(netuid, alpha); @@ -624,12 +623,14 @@ impl Pallet { // } // Step 4. Reduce tao amount by staking fee and credit this fee to SubnetTAO - let fee = DefaultMinStake::::get(); let tao_unstaked = tao.saturating_sub(fee); let actual_fee = tao.saturating_sub(tao_unstaked); SubnetTAO::::mutate(netuid, |total| { *total = total.saturating_add(actual_fee); }); + TotalStake::::mutate(|total| { + *total = total.saturating_add(actual_fee); + }); // Step 5. Deposit and log the unstaking event. Self::deposit_event(Event::StakeRemoved( @@ -660,11 +661,11 @@ impl Pallet { coldkey: &T::AccountId, netuid: u16, tao: u64, + fee: u64, ) -> u64 { // Step 1. Reduce tao amount by staking fee and credit this fee to SubnetTAO - // At this point tao was already withdrawn from the user balance and is considered + // At this point tao was already withdrawn from the user balance and is considered // available - let fee = DefaultMinStake::::get(); let tao_staked = tao.saturating_sub(fee); let actual_fee = tao.saturating_sub(tao_staked); @@ -686,6 +687,9 @@ impl Pallet { SubnetTAO::::mutate(netuid, |total| { *total = total.saturating_add(actual_fee); }); + TotalStake::::mutate(|total| { + *total = total.saturating_add(actual_fee); + }); // Step 6. Deposit and log the staking event. Self::deposit_event(Event::StakeAdded( diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index a6f9caed7..9731113e5 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -726,7 +726,8 @@ pub fn increase_stake_on_coldkey_hotkey_account( tao_staked: u64, netuid: u16, ) { - SubtensorModule::stake_into_subnet(hotkey, coldkey, netuid, tao_staked); + let fee = 0; + SubtensorModule::stake_into_subnet(hotkey, coldkey, netuid, tao_staked, fee); } /// Increases the stake on the hotkey account under its owning coldkey. diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index f26a82432..f46327305 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -17,11 +17,12 @@ fn test_do_move_success() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -53,7 +54,7 @@ fn test_do_move_success() { &coldkey, netuid ), - stake_amount, + stake_amount - 2 * fee, epsilon = stake_amount / 1000 ); }); @@ -73,11 +74,18 @@ fn test_do_move_different_subnets() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake and subnets SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + stake_amount, + fee, + ); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -109,7 +117,7 @@ fn test_do_move_different_subnets() { &coldkey, destination_netuid ), - stake_amount, + stake_amount - 2 * fee, epsilon = stake_amount / 1000 ); }); @@ -129,9 +137,16 @@ fn test_do_move_nonexistent_subnet() { let destination_hotkey = U256::from(3); let nonexistent_netuid = 99; // Assuming this subnet doesn't exist let stake_amount = 1_000_000; + let fee = 0; // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + stake_amount, + fee, + ); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -221,9 +236,10 @@ fn test_do_move_nonexistent_destination_hotkey() { let nonexistent_destination_hotkey = U256::from(99); // Assuming this hotkey doesn't exist let netuid = 1; let stake_amount = 1_000_000; + let fee = 0; // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); // Attempt to move stake from a non-existent origin hotkey add_network(netuid, 0, 0); @@ -272,9 +288,10 @@ fn test_do_move_all_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -308,7 +325,7 @@ fn test_do_move_all_stake() { &coldkey, netuid ), - stake_amount, + stake_amount - 2 * fee, epsilon = stake_amount / 1000 ); }); @@ -324,9 +341,10 @@ fn test_do_move_half_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -352,8 +370,8 @@ fn test_do_move_half_stake() { &coldkey, netuid ), - stake_amount / 2, - epsilon = stake_amount / 1000 + alpha / 2, + epsilon = alpha / 1000 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -361,8 +379,8 @@ fn test_do_move_half_stake() { &coldkey, netuid ), - stake_amount / 2, - epsilon = stake_amount / 1000 + alpha / 2 - fee, + epsilon = alpha / 1000 ); }); } @@ -380,9 +398,10 @@ fn test_do_move_partial_stake() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let total_stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, total_stake); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, total_stake, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -416,7 +435,7 @@ fn test_do_move_partial_stake() { &coldkey, netuid ), - total_stake, + total_stake - 2 * fee, epsilon = total_stake / 1000 ); }); @@ -435,11 +454,12 @@ fn test_do_move_multiple_times() { let hotkey1 = U256::from(2); let hotkey2 = U256::from(3); let initial_stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey1); SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey2); - SubtensorModule::stake_into_subnet(&hotkey1, &coldkey, netuid, initial_stake); + SubtensorModule::stake_into_subnet(&hotkey1, &coldkey, netuid, initial_stake, fee); // Move stake multiple times for _ in 0..3 { @@ -470,7 +490,7 @@ fn test_do_move_multiple_times() { // Check final stake distribution assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey1, &coldkey, netuid), - initial_stake, + initial_stake - 7 * fee, epsilon = initial_stake / 1000 ); assert_eq!( @@ -492,9 +512,10 @@ fn test_do_move_wrong_origin() { let destination_hotkey = U256::from(3); let netuid = 1; let stake_amount = 1000; + let fee = 0; // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -549,10 +570,11 @@ fn test_do_move_same_hotkey() { let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); @@ -569,8 +591,8 @@ fn test_do_move_same_hotkey() { // Check that stake remains unchanged assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid), - alpha, - epsilon = 5 + alpha - fee, + epsilon = alpha / 1000 ); }); } @@ -588,11 +610,12 @@ fn test_do_move_event_emission() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, stake_amount, 0); // use 0 fee for precision let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -618,7 +641,7 @@ fn test_do_move_event_emission() { netuid, destination_hotkey, netuid, - 19999999, // Should be TAO equivalent + stake_amount - fee - 1, // Should be TAO equivalent ) .into(), ); @@ -639,9 +662,16 @@ fn test_do_move_storage_updates() { let origin_hotkey = U256::from(2); let destination_hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // Set up initial stake - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet( + &origin_hotkey, + &coldkey, + origin_netuid, + stake_amount, + fee, + ); // Move stake SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); @@ -676,8 +706,8 @@ fn test_do_move_storage_updates() { &coldkey, destination_netuid ), - alpha, - epsilon = 5 + alpha - fee, + epsilon = alpha / 1000 ); }); } @@ -695,11 +725,12 @@ fn test_do_move_max_values() { let destination_hotkey = U256::from(3); let max_stake = u64::MAX; let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = 0; // Set up initial stake with maximum value SubtensorModule::create_account_if_non_existent(&coldkey, &origin_hotkey); SubtensorModule::create_account_if_non_existent(&coldkey, &destination_hotkey); - SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, max_stake); + SubtensorModule::stake_into_subnet(&origin_hotkey, &coldkey, netuid, max_stake, fee); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &origin_hotkey, &coldkey, @@ -744,19 +775,20 @@ fn test_moving_too_little_fails() { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); let amount = DefaultMinStake::::get(); + let fee = DefaultMinStake::::get(); //add network let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); let netuid2: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); // Give it some $$$ in his coldkey balance - SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, amount + fee); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, netuid, - amount + amount + fee )); // Coldkey / hotkey 0 decreases take to 5%. This should fail as the minimum take is 9% diff --git a/pallets/subtensor/src/tests/senate.rs b/pallets/subtensor/src/tests/senate.rs index 00a1b897f..01dfc17d7 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -67,6 +67,7 @@ fn test_senate_join_works() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let stake = DefaultMinStake::::get() * 100; + let fee = DefaultMinStake::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -112,12 +113,12 @@ fn test_senate_join_works() { &staker_coldkey, netuid ), - stake, + stake - fee, epsilon = 10 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake, + stake - fee, epsilon = 10 ); @@ -140,6 +141,7 @@ fn test_senate_vote_works() { let hotkey_account_id = U256::from(6); let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har + let fee = DefaultMinStake::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -185,12 +187,12 @@ fn test_senate_vote_works() { &staker_coldkey, netuid ), - stake, + stake - fee, epsilon = stake / 1000 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake, + stake - fee, epsilon = stake / 1000 ); @@ -313,6 +315,7 @@ fn test_senate_leave_works() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -357,12 +360,12 @@ fn test_senate_leave_works() { &staker_coldkey, netuid ), - stake, + stake - fee, epsilon = stake / 1000 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake, + stake - fee, epsilon = stake / 1000 ); @@ -387,6 +390,7 @@ fn test_senate_leave_vote_removal() { let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har let coldkey_origin = <::RuntimeOrigin>::signed(coldkey_account_id); let stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -431,12 +435,12 @@ fn test_senate_leave_vote_removal() { &staker_coldkey, netuid ), - stake, + stake - fee, epsilon = 10 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake, + stake - fee, epsilon = 10 ); @@ -528,6 +532,7 @@ fn test_senate_not_leave_when_stake_removed() { let hotkey_account_id = U256::from(6); let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har + let fee = DefaultMinStake::::get(); //add network SubtensorModule::set_burn(netuid, burn_cost); @@ -573,12 +578,12 @@ fn test_senate_not_leave_when_stake_removed() { &staker_coldkey, netuid ), - stake_amount, + stake_amount - fee, epsilon = stake_amount / 1000 ); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_on_subnet(&hotkey_account_id, netuid), - stake_amount, + stake_amount - fee, epsilon = stake_amount / 1000 ); @@ -686,6 +691,7 @@ fn test_adjust_senate_events() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); let root_netuid = SubtensorModule::get_root_netuid(); + let fee = DefaultMinStake::::get(); let max_senate_size: u16 = SenateMaxMembers::get() as u16; let stake_threshold: u64 = DefaultMinStake::::get(); // Give this much to every senator @@ -806,7 +812,7 @@ fn test_adjust_senate_events() { &coldkey_account_id, root_netuid ), - stake, + stake - fee, epsilon = stake / 1000 ); assert_abs_diff_eq!( @@ -814,7 +820,7 @@ fn test_adjust_senate_events() { &replacement_hotkey_account_id, root_netuid ), - stake, + stake - fee, epsilon = stake / 1000 ); diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 882184811..ef5ac5f4a 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -42,6 +42,7 @@ fn test_add_stake_ok_no_emission() { let hotkey_account_id = U256::from(533453); let coldkey_account_id = U256::from(55453); let amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); //add network let netuid: u16 = add_dynamic_network(&hotkey_account_id, &coldkey_account_id); @@ -69,7 +70,7 @@ fn test_add_stake_ok_no_emission() { // Check if stake has increased assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - amount, + amount - fee, epsilon = amount / 1000, ); @@ -78,7 +79,6 @@ fn test_add_stake_ok_no_emission() { // Check if total stake has increased accordingly. assert_eq!(SubtensorModule::get_total_stake(), amount); - assert_abs_diff_eq!(SubtensorModule::get_total_stake(), amount, epsilon = 1,); }); } @@ -350,7 +350,8 @@ fn test_remove_stake_ok_no_emission() { let subnet_owner_hotkey = U256::from(2); let coldkey_account_id = U256::from(4343); let hotkey_account_id = U256::from(4968585); - let amount = 10000; + let amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -379,12 +380,12 @@ fn test_remove_stake_ok_no_emission() { )); // we do not expect the exact amount due to slippage - assert!(SubtensorModule::get_coldkey_balance(&coldkey_account_id) > amount / 10 * 9,); + assert!(SubtensorModule::get_coldkey_balance(&coldkey_account_id) > amount / 10 * 9 - fee); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), 0 ); - assert_eq!(SubtensorModule::get_total_stake(), 0); + assert_eq!(SubtensorModule::get_total_stake(), fee); }); } @@ -498,6 +499,7 @@ fn test_remove_stake_no_enough_stake() { #[test] fn test_remove_stake_total_balance_no_change() { // When we remove stake, the total balance of the coldkey account should not change + // (except for staking fees) // this is because the stake should be part of the coldkey account balance (reserved/locked) // then the removed stake just becomes free balance new_test_ext(1).execute_with(|| { @@ -505,7 +507,8 @@ fn test_remove_stake_total_balance_no_change() { let subnet_owner_hotkey = U256::from(2); let hotkey_account_id = U256::from(571337); let coldkey_account_id = U256::from(71337); - let amount = 10_000; + let amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); @@ -537,18 +540,18 @@ fn test_remove_stake_total_balance_no_change() { assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), - amount, - epsilon = 1, + amount - fee, + epsilon = amount / 1000, ); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), 0 ); - assert_eq!(SubtensorModule::get_total_stake(), 0); + assert_eq!(SubtensorModule::get_total_stake(), fee); // Check total balance is equal to the added stake. Even after remove stake (no fee, includes reserved/locked balance) let total_balance = Balances::total_balance(&coldkey_account_id); - assert_abs_diff_eq!(total_balance, amount, epsilon = 1,); + assert_abs_diff_eq!(total_balance, amount - fee, epsilon = amount / 1000); }); } @@ -564,6 +567,7 @@ fn test_remove_stake_total_issuance_no_change() { let coldkey_account_id = U256::from(81337); let amount = DefaultMinStake::::get() * 10; let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, 192213123); // Give it some $$$ in his coldkey balance @@ -610,14 +614,18 @@ fn test_remove_stake_total_issuance_no_change() { assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), - amount, - epsilon = 1, + amount - fee * 2, + epsilon = amount / 1000, ); assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), 0 ); - assert_abs_diff_eq!(SubtensorModule::get_total_stake(), 0, epsilon = 1,); + assert_abs_diff_eq!( + SubtensorModule::get_total_stake(), + fee * 2, + epsilon = fee / 1000 + ); // Check if total issuance is equal to the added stake, even after remove stake (no fee, includes reserved/locked balance) assert_abs_diff_eq!( @@ -625,10 +633,13 @@ fn test_remove_stake_total_issuance_no_change() { total_issuance_after_stake + amount, epsilon = 1, ); + + // After staking + unstaking the 2 * fee amount stays in SubnetTAO and TotalStake, + // so the total issuance should be lower by that amount assert_abs_diff_eq!( inital_total_issuance, - total_issuance_after_unstake, - epsilon = 1, + total_issuance_after_unstake + 2 * fee, + epsilon = inital_total_issuance / 10000, ); }); } @@ -1108,6 +1119,8 @@ fn test_clear_small_nominations() { let cold2 = U256::from(4); let netuid: u16 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let amount = DefaultMinStake::::get() * 10; + let fee: u64 = DefaultMinStake::::get(); + let init_balance = amount + fee + ExistentialDeposit::get(); // Register hot1. register_ok_neuron(netuid, hot1, cold1, 0); @@ -1120,80 +1133,84 @@ fn test_clear_small_nominations() { assert_eq!(SubtensorModule::get_owning_coldkey_for_hotkey(&hot2), cold2); // Add stake cold1 --> hot1 (non delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold1, amount); + SubtensorModule::add_balance_to_coldkey_account(&cold1, init_balance); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold1), hot1, netuid, - amount + amount + fee )); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(cold1), hot1, netuid, - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid) - 1 + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid) + - 100 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 + 100 ); // Add stake cold2 --> hot1 (is delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold2, amount); + SubtensorModule::add_balance_to_coldkey_account(&cold2, init_balance); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold2), hot1, netuid, - amount + amount + fee )); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(cold2), hot1, netuid, - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid) - 1 + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid) + - 100 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), - 1 + 100 ); // Add stake cold1 --> hot2 (non delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold1, amount); + SubtensorModule::add_balance_to_coldkey_account(&cold1, init_balance); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold1), hot2, netuid, - amount + amount + fee )); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(cold1), hot2, netuid, - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid) - 1 + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid) + - 100 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), - 1 + 100 ); let balance1_before_cleaning = Balances::free_balance(cold1); // Add stake cold2 --> hot2 (is delegation.) - SubtensorModule::add_balance_to_coldkey_account(&cold2, amount); + SubtensorModule::add_balance_to_coldkey_account(&cold2, init_balance); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(cold2), hot2, netuid, - amount + amount + fee )); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(cold2), hot2, netuid, - SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid) - 1 + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid) + - 100 )); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 + 100 ); let balance2_before_cleaning = Balances::free_balance(cold2); @@ -1203,19 +1220,19 @@ fn test_clear_small_nominations() { SubtensorModule::clear_small_nominations(); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 + 100 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), - 1 + 100 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold2, netuid), - 1 + 100 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 + 100 ); // Set min nomination to 10 @@ -1224,13 +1241,13 @@ fn test_clear_small_nominations() { let total_hot1_stake_before = TotalHotkeyAlpha::::get(hot1, netuid); let total_hot2_stake_before = TotalHotkeyAlpha::::get(hot2, netuid); let total_stake_before = TotalStake::::get(); - SubtensorModule::set_nominator_min_required_stake(10); + SubtensorModule::set_nominator_min_required_stake(1000); // Run clear all small nominations (removes delegations under 10) SubtensorModule::clear_small_nominations(); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot1, &cold1, netuid), - 1 + 100 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold1, netuid), @@ -1242,24 +1259,26 @@ fn test_clear_small_nominations() { ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hot2, &cold2, netuid), - 1 + 100 ); // Balances have been added back into accounts. let balance1_after_cleaning = Balances::free_balance(cold1); let balance2_after_cleaning = Balances::free_balance(cold2); - assert_eq!(balance1_before_cleaning + 1, balance1_after_cleaning); - assert_eq!(balance2_before_cleaning + 1, balance2_after_cleaning); + assert_eq!(balance1_before_cleaning + 100, balance1_after_cleaning); + assert_eq!(balance2_before_cleaning + 100, balance2_after_cleaning); - assert_eq!( + assert_abs_diff_eq!( TotalHotkeyAlpha::::get(hot2, netuid), - total_hot2_stake_before - 1 + total_hot2_stake_before - 100, + epsilon = 1 ); - assert_eq!( + assert_abs_diff_eq!( TotalHotkeyAlpha::::get(hot1, netuid), - total_hot1_stake_before - 1 + total_hot1_stake_before - 100, + epsilon = 1 ); - assert_eq!(TotalStake::::get(), total_stake_before - 2); + assert_eq!(TotalStake::::get(), total_stake_before - 200); }); } @@ -1590,6 +1609,7 @@ fn test_get_total_delegated_stake_after_unstaking() { let unstake_amount = DefaultMinStake::::get() * 5; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); @@ -1607,12 +1627,12 @@ fn test_get_total_delegated_stake_after_unstaking() { // Check initial delegated stake assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&delegator), - initial_stake - existential_deposit, + initial_stake - existential_deposit - fee, epsilon = initial_stake / 1000, ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey), - initial_stake - existential_deposit, + initial_stake - existential_deposit - fee, epsilon = initial_stake / 1000, ); @@ -1625,7 +1645,7 @@ fn test_get_total_delegated_stake_after_unstaking() { )); // Calculate the expected delegated stake - let expected_delegated_stake = initial_stake - unstake_amount - existential_deposit; + let expected_delegated_stake = initial_stake - unstake_amount - existential_deposit - fee; // Debug prints log::debug!("Initial stake: {}", initial_stake); @@ -1677,6 +1697,7 @@ fn test_get_total_delegated_stake_single_delegator() { let stake_amount = DefaultMinStake::::get() * 10 - 1; let existential_deposit = ExistentialDeposit::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); register_ok_neuron(netuid, delegate_hotkey, delegate_coldkey, 0); @@ -1705,7 +1726,7 @@ fn test_get_total_delegated_stake_single_delegator() { ); // Calculate expected delegated stake - let expected_delegated_stake = stake_amount - existential_deposit; + let expected_delegated_stake = stake_amount - existential_deposit - fee; let actual_delegated_stake = SubtensorModule::get_total_stake_for_hotkey(&delegate_hotkey); let actual_delegator_stake = SubtensorModule::get_total_stake_for_coldkey(&delegator); @@ -1734,6 +1755,7 @@ fn test_get_alpha_share_stake_multiple_delegators() { let existential_deposit = 2; let stake1 = DefaultMinStake::::get() * 10; let stake2 = DefaultMinStake::::get() * 10 - 1; + let fee = DefaultMinStake::::get(); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); register_ok_neuron(netuid, hotkey1, coldkey1, 0); @@ -1770,7 +1792,7 @@ fn test_get_alpha_share_stake_multiple_delegators() { ); // Calculate expected total delegated stake - let expected_total_stake = stake1 + stake2 - existential_deposit * 2; + let expected_total_stake = stake1 + stake2 - existential_deposit * 2 - fee * 2; let actual_total_stake = SubtensorModule::get_alpha_share_pool(hotkey1, netuid) .get_value(&coldkey1) + SubtensorModule::get_alpha_share_pool(hotkey2, netuid).get_value(&coldkey2); @@ -1792,6 +1814,7 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { let delegator = U256::from(3); let owner_stake = DefaultMinStake::::get() * 10; let delegator_stake = DefaultMinStake::::get() * 10 - 1; + let fee = DefaultMinStake::::get(); let netuid = add_dynamic_network(&delegate_hotkey, &delegate_coldkey); @@ -1825,7 +1848,7 @@ fn test_get_total_delegated_stake_exclude_owner_stake() { ); // Check the total delegated stake (should exclude owner's stake) - let expected_delegated_stake = delegator_stake; + let expected_delegated_stake = delegator_stake - fee; let actual_delegated_stake = SubtensorModule::get_total_stake_for_coldkey(&delegate_coldkey); @@ -1969,7 +1992,8 @@ fn test_add_stake_fee_goes_to_subnet_tao() { // Calculate expected stake let expected_alpha = tao_to_stake - existential_deposit - fee; - let actual_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let actual_alpha = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); let subnet_tao_after = SubnetTAO::::get(netuid); // Total subnet stake should match the sum of delegators' stakes minus existential deposits. @@ -2013,7 +2037,8 @@ fn test_remove_stake_fee_goes_to_subnet_tao() { )); // Remove all stake - let alpha_to_unstake = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let alpha_to_unstake = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); assert_ok!(SubtensorModule::remove_stake( RuntimeOrigin::signed(coldkey), hotkey, @@ -2037,4 +2062,68 @@ fn test_remove_stake_fee_goes_to_subnet_tao() { epsilon = tao_to_stake / 1000 ); }); -} \ No newline at end of file +} + +#[test] +fn test_stake_below_min_validate() { + // Testing the signed extension validate function + // correctly filters the `add_stake` transaction. + + new_test_ext(0).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let hotkey = U256::from(2); + let coldkey = U256::from(3); + let amount_staked = DefaultMinStake::::get() - 1; + + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::add_balance_to_coldkey_account(&coldkey, amount_staked); + + // Add stake call + let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey, + netuid, + amount_staked, + }); + + let info: crate::DispatchInfo = + crate::DispatchInfoOf::<::RuntimeCall>::default(); + + let extension = crate::SubtensorSignedExtension::::new(); + // Submit to the signed extension validate function + let result_no_stake = extension.validate(&coldkey, &call.clone(), &info, 10); + + // Should fail due to insufficient stake + assert_err!( + result_no_stake, + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(1)) + ); + + // Increase the stake to be equal to the minimum, but leave the balance low + let amount_staked = DefaultMinStake::::get(); + let call_2 = RuntimeCall::SubtensorModule(SubtensorCall::add_stake { + hotkey, + netuid, + amount_staked, + }); + + // Submit to the signed extension validate function + let result_low_balance = extension.validate(&coldkey, &call_2.clone(), &info, 10); + + // Still doesn't pass + assert_err!( + result_low_balance, + crate::TransactionValidityError::Invalid(crate::InvalidTransaction::Custom(1)) + ); + + // Increase the coldkey balance to match the minimum + SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1); + + // Submit to the signed extension validate function + let result_min_stake = extension.validate(&coldkey, &call_2.clone(), &info, 10); + + // Now the call passes + assert_ok!(result_min_stake); + }); +} diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 0cbcecfaf..ae2a1649c 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -366,6 +366,7 @@ fn test_swap_with_max_values() { let netuid2 = 2u16; let stake = 10_000; let max_stake = 21_000_000_000_000_000; // 21 Million TAO; max possible balance. + let fee = DefaultMinStake::::get(); // Add a network add_network(netuid, 1, 0); @@ -412,7 +413,7 @@ fn test_swap_with_max_values() { ); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - max_stake + max_stake - fee ); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&old_coldkey2), @@ -420,7 +421,7 @@ fn test_swap_with_max_values() { ); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey2), - max_stake + max_stake - fee ); }); } @@ -434,6 +435,8 @@ fn test_swap_with_non_existent_new_coldkey() { let hotkey = U256::from(3); let stake = DefaultMinStake::::get() * 10; let netuid = 1u16; + let fee = DefaultMinStake::::get(); + add_network(netuid, 1, 0); register_ok_neuron(netuid, hotkey, old_coldkey, 1001000); // Give old coldkey some balance. @@ -459,7 +462,7 @@ fn test_swap_with_non_existent_new_coldkey() { ); assert_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - stake + stake - fee ); }); } @@ -525,6 +528,7 @@ fn test_swap_concurrent_modifications() { let netuid: u16 = 1; let initial_stake = 1_000_000_000_000; let additional_stake = 500_000_000_000; + let fee = DefaultMinStake::::get(); // Setup initial state add_network(netuid, 1, 1); @@ -547,7 +551,7 @@ fn test_swap_concurrent_modifications() { &new_coldkey, netuid ), - initial_stake + initial_stake - fee ); // Wait some blocks @@ -576,15 +580,14 @@ fn test_swap_concurrent_modifications() { )); let eps = 500; // RAO - assert!( - (SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &new_coldkey, netuid - ) as i64 - - (stake_before_swap + additional_stake) as i64) - .abs() - <= eps + ), + stake_before_swap + additional_stake - fee, + epsilon = eps ); assert!(!Alpha::::contains_key((hotkey, old_coldkey, netuid))); }); @@ -801,6 +804,7 @@ fn test_swap_stake_for_coldkey() { let stake_amount3 = DefaultMinStake::::get() * 30; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); + let fee = DefaultMinStake::::get(); // Setup initial state // Add a network @@ -837,7 +841,7 @@ fn test_swap_stake_for_coldkey() { &old_coldkey, netuid ), - stake_amount1 + stake_amount1 - fee ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -845,7 +849,7 @@ fn test_swap_stake_for_coldkey() { &old_coldkey, netuid ), - stake_amount2 + stake_amount2 - fee ); // Insert existing for same hotkey1 @@ -892,7 +896,7 @@ fn test_swap_stake_for_coldkey() { &new_coldkey, netuid ), - stake_amount1 + stake_amount3 + stake_amount1 + stake_amount3 - fee * 2 ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -900,7 +904,7 @@ fn test_swap_stake_for_coldkey() { &new_coldkey, netuid ), - stake_amount2 + stake_amount2 - fee ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -956,6 +960,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { let stake_amount2 = DefaultMinStake::::get() * 20; let total_stake = stake_amount1 + stake_amount2; let mut weight = Weight::zero(); + let fee = DefaultMinStake::::get(); // Setup initial state // Add a network @@ -991,7 +996,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { &old_coldkey, netuid ), - stake_amount1 + stake_amount1 - fee ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -999,7 +1004,7 @@ fn test_swap_staking_hotkeys_for_coldkey() { &old_coldkey, netuid ), - stake_amount2 + stake_amount2 - fee ); // Perform the swap @@ -1027,6 +1032,7 @@ fn test_swap_delegated_stake_for_coldkey() { let stake_amount2 = DefaultMinStake::::get() * 20; let mut weight = Weight::zero(); let netuid = 1u16; + let fee = DefaultMinStake::::get(); // Setup initial state add_network(netuid, 1, 0); @@ -1081,7 +1087,7 @@ fn test_swap_delegated_stake_for_coldkey() { &new_coldkey, netuid ), - stake_amount1 + stake_amount1 - fee ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -1089,7 +1095,7 @@ fn test_swap_delegated_stake_for_coldkey() { &new_coldkey, netuid ), - stake_amount2 + stake_amount2 - fee ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -1583,6 +1589,7 @@ fn test_coldkey_delegations() { let netuid = 0u16; // Stake to 0 let netuid2 = 1u16; // Stake to 1 let stake = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); add_network(netuid, 13, 0); // root add_network(netuid2, 13, 0); @@ -1620,25 +1627,25 @@ fn test_coldkey_delegations() { // Verify stake was moved for the delegate assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&delegate), - stake * 2, + stake * 2 - fee * 2, epsilon = stake / 1000 ); assert_eq!(SubtensorModule::get_total_stake_for_coldkey(&coldkey), 0); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_coldkey(&new_coldkey), - stake * 2, + stake * 2 - fee * 2, epsilon = stake / 1000 ); assert_abs_diff_eq!( Alpha::::get((delegate, new_coldkey, netuid)).to_num::(), - stake, + stake - fee, epsilon = stake / 1000 ); assert_eq!(Alpha::::get((delegate, coldkey, netuid)), 0); assert_abs_diff_eq!( Alpha::::get((delegate, new_coldkey, netuid2)).to_num::(), - stake, + stake - fee, epsilon = stake / 1000 ); assert_eq!(Alpha::::get((delegate, coldkey, netuid2)), 0); diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index 0f11dfa98..d35dd6d56 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -66,6 +66,7 @@ fn test_swap_total_hotkey_stake() { let coldkey = U256::from(3); let amount = DefaultMinStake::::get() * 10; let mut weight = Weight::zero(); + let fee = DefaultMinStake::::get(); //add network let netuid: u16 = add_dynamic_network(&old_hotkey, &coldkey); @@ -84,7 +85,7 @@ fn test_swap_total_hotkey_stake() { // Check if stake has increased assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), - amount, + amount - fee, epsilon = amount / 1000, ); assert_abs_diff_eq!( @@ -109,7 +110,7 @@ fn test_swap_total_hotkey_stake() { ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), - amount, + amount - fee, epsilon = amount / 1000, ); }); diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index b71093776..10c829870 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -69,6 +69,7 @@ fn test_set_rootweights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! + let fee = DefaultMinStake::::get(); let who = coldkey; // The coldkey signs this transaction @@ -112,7 +113,7 @@ fn test_set_rootweights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - min_stake + min_stake + fee )); // Verify stake is equal to minimum @@ -183,6 +184,7 @@ fn test_commit_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! + let fee = DefaultMinStake::::get(); let who = hotkey; // The hotkey signs this transaction @@ -226,7 +228,7 @@ fn test_commit_weights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - min_stake + min_stake + fee )); // Verify stake is equal to minimum @@ -292,6 +294,7 @@ fn test_set_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); assert_ne!(hotkey, coldkey); + let fee = DefaultMinStake::::get(); let who = hotkey; // The hotkey signs this transaction @@ -333,7 +336,7 @@ fn test_set_weights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - min_stake + min_stake + fee )); // Verify stake is equal to minimum @@ -364,6 +367,7 @@ fn test_reveal_weights_validate() { let coldkey = U256::from(0); let hotkey: U256 = U256::from(1); // Add the hotkey field assert_ne!(hotkey, coldkey); // Ensure hotkey is NOT the same as coldkey !!! + let fee = DefaultMinStake::::get(); let who = hotkey; // The hotkey signs this transaction @@ -406,7 +410,7 @@ fn test_reveal_weights_validate() { RuntimeOrigin::signed(hotkey), hotkey, netuid, - min_stake + min_stake + fee )); // Verify stake is equal to minimum From fe28b487b90eae405411969de33a5d659746306b Mon Sep 17 00:00:00 2001 From: cuteolaf Date: Wed, 22 Jan 2025 06:36:14 -0800 Subject: [PATCH 15/25] fix wrong comments for some storage items --- pallets/subtensor/src/lib.rs | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7f75568ba..396b7fe71 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -886,34 +886,34 @@ pub mod pallet { pub type TotalStake = StorageValue<_, u64, ValueQuery>; #[pallet::storage] // --- ITEM ( dynamic_block ) -- block when dynamic was turned on. pub type DynamicBlock = StorageValue<_, u64, ValueQuery>; - #[pallet::storage] // --- DMAP ( netuid ) --> total_volume | The total amount of TAO bought and sold since the start of the network. + #[pallet::storage] // --- MAP ( netuid ) --> total_volume | The total amount of TAO bought and sold since the start of the network. pub type SubnetVolume = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> tao_in_subnet | Returns the amount of TAO in the subnet. + #[pallet::storage] // --- MAP ( netuid ) --> tao_in_subnet | Returns the amount of TAO in the subnet. pub type SubnetTAO = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> alpha_in_emission | Returns the amount of alph in emission into the pool per block. + #[pallet::storage] // --- MAP ( netuid ) --> alpha_in_emission | Returns the amount of alph in emission into the pool per block. pub type SubnetAlphaInEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> alpha_out_emission | Returns the amount of alpha out emission into the network per block. + #[pallet::storage] // --- MAP ( netuid ) --> alpha_out_emission | Returns the amount of alpha out emission into the network per block. pub type SubnetAlphaOutEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> tao_in_emission | Returns the amount of tao emitted into this subent on the last block. + #[pallet::storage] // --- MAP ( netuid ) --> tao_in_emission | Returns the amount of tao emitted into this subent on the last block. pub type SubnetTaoInEmission = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> alpha_sell_per_block | Alpha sold per block. + #[pallet::storage] // --- MAP ( netuid ) --> alpha_sell_per_block | Alpha sold per block. pub type SubnetAlphaEmissionSell = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> total_stake_at_moment_of_subnet_registration + #[pallet::storage] // --- MAP ( netuid ) --> total_stake_at_moment_of_subnet_registration pub type TotalStakeAtDynamic = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> alpha_supply_in_pool | Returns the amount of alpha in the subnet. + #[pallet::storage] // --- MAP ( netuid ) --> alpha_supply_in_pool | Returns the amount of alpha in the pool. pub type SubnetAlphaIn = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( netuid ) --> alpha_supply_in_subnet | Returns the amount of alpha in the subnet. + #[pallet::storage] // --- MAP ( netuid ) --> alpha_supply_in_subnet | Returns the amount of alpha in the subnet. pub type SubnetAlphaOut = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultZeroU64>; - #[pallet::storage] // --- DMAP ( cold ) --> Vec | Maps coldkey to hotkeys that stake to it + #[pallet::storage] // --- MAP ( 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. @@ -970,10 +970,10 @@ pub mod pallet { U64F64, // Shares ValueQuery, >; - #[pallet::storage] // --- DMAP ( netuid ) --> token_symbol | Returns the token symbol for a subnet. + #[pallet::storage] // --- MAP ( netuid ) --> token_symbol | Returns the token symbol for a subnet. pub type TokenSymbol = StorageMap<_, Identity, u16, Vec, ValueQuery, DefaultUnicodeVecU8>; - #[pallet::storage] // --- DMAP ( netuid ) --> subnet_name | Returns the name of the subnet. + #[pallet::storage] // --- MAP ( netuid ) --> subnet_name | Returns the name of the subnet. pub type SubnetName = StorageMap<_, Identity, u16, Vec, ValueQuery, DefaultUnicodeVecU8>; @@ -1267,49 +1267,49 @@ pub mod pallet { pub type Keys = StorageDoubleMap<_, Identity, u16, Identity, u16, T::AccountId, ValueQuery, DefaultKey>; #[pallet::storage] - /// --- DMAP ( netuid ) --> (hotkey, se, ve) + /// --- MAP ( netuid ) --> (hotkey, se, ve) pub type LoadedEmission = StorageMap<_, Identity, u16, Vec<(T::AccountId, u64, u64)>, OptionQuery>; #[pallet::storage] - /// --- DMAP ( netuid ) --> active + /// --- MAP ( netuid ) --> active pub type Active = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> rank + /// --- MAP ( netuid ) --> rank pub type Rank = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> trust + /// --- MAP ( netuid ) --> trust pub type Trust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> consensus + /// --- MAP ( netuid ) --> consensus pub type Consensus = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> incentive + /// --- MAP ( netuid ) --> incentive pub type Incentive = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> dividends + /// --- MAP ( netuid ) --> dividends pub type Dividends = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> emission + /// --- MAP ( netuid ) --> emission pub type Emission = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> last_update + /// --- MAP ( netuid ) --> last_update pub type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> validator_trust + /// --- MAP ( netuid ) --> validator_trust pub type ValidatorTrust = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> pruning_scores + /// --- MAP ( netuid ) --> pruning_scores pub type PruningScores = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] - /// --- DMAP ( netuid ) --> validator_permit + /// --- MAP ( netuid ) --> validator_permit pub type ValidatorPermit = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyBoolVec>; #[pallet::storage] From a1d7e8713e6320665dfdb3887715922cf16ed2ca Mon Sep 17 00:00:00 2001 From: Unconst <32490803+unconst@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:14:59 -0500 Subject: [PATCH 16/25] Metagraph struct (#1175) * universal subnet struct * oops typo * cargo clippy * cargo fmt * add new field --------- Co-authored-by: unconst Co-authored-by: camfairchild Co-authored-by: Cameron Fairchild --- Cargo.lock | 184 ++++--------- pallets/subtensor/rpc/src/lib.rs | 23 ++ pallets/subtensor/runtime-api/src/lib.rs | 2 + pallets/subtensor/src/rpc_info/metagraph.rs | 287 ++++++++++++++++++++ pallets/subtensor/src/rpc_info/mod.rs | 1 + runtime/src/lib.rs | 15 + 6 files changed, 375 insertions(+), 137 deletions(-) create mode 100644 pallets/subtensor/src/rpc_info/metagraph.rs diff --git a/Cargo.lock b/Cargo.lock index 90c2fb86f..964c583e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1550,9 +1550,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] @@ -2187,9 +2187,9 @@ checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" dependencies = [ "crunchy", "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", "tiny-keccak", ] @@ -2220,12 +2220,12 @@ checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", - "primitive-types 0.12.2", + "impl-serde", + "primitive-types", "scale-info", - "uint 0.9.5", + "uint", ] [[package]] @@ -2269,7 +2269,7 @@ dependencies = [ "evm-runtime", "log", "parity-scale-codec", - "primitive-types 0.12.2", + "primitive-types", "rlp", "scale-info", "serde", @@ -2283,7 +2283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" dependencies = [ "parity-scale-codec", - "primitive-types 0.12.2", + "primitive-types", "scale-info", "serde", ] @@ -2297,7 +2297,7 @@ dependencies = [ "environmental", "evm-core", "evm-runtime", - "primitive-types 0.12.2", + "primitive-types", ] [[package]] @@ -2309,7 +2309,7 @@ dependencies = [ "auto_impl", "environmental", "evm-core", - "primitive-types 0.12.2", + "primitive-types", "sha3", ] @@ -2694,7 +2694,7 @@ version = "1.0.0-dev" source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac882333afed827053f31ef56ab739f7a2e" dependencies = [ "hex", - "impl-serde 0.4.0", + "impl-serde", "libsecp256k1", "log", "parity-scale-codec", @@ -3876,26 +3876,6 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-codec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-num-traits" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d15461ab0dcc56706adf266158acbc44ccf719bf7d0af30705f58b90a4b8c" -dependencies = [ - "integer-sqrt", - "num-traits", - "uint 0.10.0", -] - [[package]] name = "impl-rlp" version = "0.3.0" @@ -3914,15 +3894,6 @@ dependencies = [ "serde", ] -[[package]] -name = "impl-serde" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -4459,7 +4430,7 @@ dependencies = [ "sha2 0.10.8", "smallvec", "thiserror", - "uint 0.9.5", + "uint", "unsigned-varint 0.7.2", "void", ] @@ -4921,7 +4892,7 @@ dependencies = [ "tokio-util", "tracing", "trust-dns-resolver", - "uint 0.9.5", + "uint", "unsigned-varint 0.8.0", "url", "webpki", @@ -6664,7 +6635,7 @@ dependencies = [ "lru 0.8.1", "parity-util-mem-derive", "parking_lot 0.12.3", - "primitive-types 0.12.2", + "primitive-types", "smallvec", "winapi", ] @@ -6906,7 +6877,7 @@ dependencies = [ "libc", "log", "polkavm-assembler", - "polkavm-common 0.9.0", + "polkavm-common", "polkavm-linux-raw", ] @@ -6928,28 +6899,13 @@ dependencies = [ "log", ] -[[package]] -name = "polkavm-common" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0dbafef4ab6ceecb4982ac3b550df430ef4f9fdbf07c108b7d4f91a0682fce" - [[package]] name = "polkavm-derive" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" dependencies = [ - "polkavm-derive-impl-macro 0.9.0", -] - -[[package]] -name = "polkavm-derive" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206caf322dfc02144510ad8360ff2051e5072f0874dcab3b410f78cdd52d0ebb" -dependencies = [ - "polkavm-derive-impl-macro 0.17.0", + "polkavm-derive-impl-macro", ] [[package]] @@ -6958,19 +6914,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" dependencies = [ - "polkavm-common 0.9.0", - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "polkavm-derive-impl" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42565aed4adbc4034612d0b17dea8db3681fb1bd1aed040d6edc5455a9f478a1" -dependencies = [ - "polkavm-common 0.17.0", + "polkavm-common", "proc-macro2", "quote", "syn 2.0.90", @@ -6982,17 +6926,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ - "polkavm-derive-impl 0.9.0", - "syn 2.0.90", -] - -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d9838e95241b0bce4fe269cdd4af96464160505840ed5a8ac8536119ba19e2" -dependencies = [ - "polkavm-derive-impl 0.17.0", + "polkavm-derive-impl", "syn 2.0.90", ] @@ -7006,7 +6940,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "object 0.32.2", - "polkavm-common 0.9.0", + "polkavm-common", "regalloc2 0.9.3", "rustc-demangle", ] @@ -7143,23 +7077,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", - "impl-codec 0.6.0", + "impl-codec", "impl-rlp", - "impl-serde 0.4.0", + "impl-serde", "scale-info", - "uint 0.9.5", -] - -[[package]] -name = "primitive-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" -dependencies = [ - "fixed-hash", - "impl-codec 0.7.0", - "impl-num-traits", - "uint 0.10.0", + "uint", ] [[package]] @@ -9405,9 +9327,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -9442,9 +9364,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -9936,7 +9858,7 @@ dependencies = [ "futures", "hash-db", "hash256-std-hasher", - "impl-serde 0.4.0", + "impl-serde", "itertools 0.11.0", "k256", "libsecp256k1", @@ -9946,7 +9868,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "paste", - "primitive-types 0.12.2", + "primitive-types", "rand", "scale-info", "schnorrkel", @@ -9970,7 +9892,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -10066,7 +9988,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "proc-macro2", "quote", @@ -10076,7 +9998,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "environmental", "parity-scale-codec", @@ -10129,7 +10051,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive", "rustversion", "secp256k1", "sp-core", @@ -10254,13 +10176,13 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.17.1", - "primitive-types 0.13.1", + "polkavm-derive", + "primitive-types", "sp-externalities 0.25.0", "sp-runtime-interface-proc-macro 17.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk)", @@ -10278,8 +10200,8 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.9.1", - "primitive-types 0.12.2", + "polkavm-derive", + "primitive-types", "sp-externalities 0.29.0", "sp-runtime-interface-proc-macro 18.0.0", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", @@ -10292,7 +10214,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "Inflector", "expander", @@ -10394,14 +10316,14 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ - "impl-serde 0.5.0", + "impl-serde", "parity-scale-codec", "ref-cast", "serde", @@ -10413,7 +10335,7 @@ name = "sp-storage" version = "21.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "ref-cast", "serde", @@ -10435,7 +10357,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "parity-scale-codec", "tracing", @@ -10505,7 +10427,7 @@ name = "sp-version" version = "37.0.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409#87971b3e92721bdf10bf40b410eaae779d494ca0" dependencies = [ - "impl-serde 0.4.0", + "impl-serde", "parity-scale-codec", "parity-wasm", "scale-info", @@ -10531,7 +10453,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#44766de645c741fd03d159fca7f17cd6e99c33e3" +source = "git+https://github.com/paritytech/polkadot-sdk#8614dc0e055d06de4a3774ac1da0a422b33f34e2" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -11705,18 +11627,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "uint" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicode-bidi" version = "0.3.17" diff --git a/pallets/subtensor/rpc/src/lib.rs b/pallets/subtensor/rpc/src/lib.rs index 5b4c7777a..cdbcebcac 100644 --- a/pallets/subtensor/rpc/src/lib.rs +++ b/pallets/subtensor/rpc/src/lib.rs @@ -55,6 +55,10 @@ pub trait SubtensorCustomApi { fn get_all_dynamic_info(&self, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getDynamicInfo")] fn get_dynamic_info(&self, netuid: u16, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getAllMetagraphs")] + fn get_all_metagraphs(&self, at: Option) -> RpcResult>; + #[method(name = "subnetInfo_getMetagraph")] + fn get_metagraph(&self, netuid: u16, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getSubnetState")] fn get_subnet_state(&self, netuid: u16, at: Option) -> RpcResult>; #[method(name = "subnetInfo_getLockCost")] @@ -223,6 +227,13 @@ where }) } + fn get_all_metagraphs(&self, at: Option<::Hash>) -> RpcResult> { + let api = self.client.runtime_api(); + let at = at.unwrap_or_else(|| self.client.info().best_hash); + api.get_all_metagraphs(at) + .map_err(|e| Error::RuntimeError(format!("Unable to get metagraps: {:?}", e)).into()) + } + fn get_dynamic_info( &self, netuid: u16, @@ -235,6 +246,18 @@ where }) } + fn get_metagraph( + &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_metagraph(at, netuid).map_err(|e| { + Error::RuntimeError(format!("Unable to get dynamic subnets info: {:?}", e)).into() + }) + } + fn get_subnet_state( &self, netuid: u16, diff --git a/pallets/subtensor/runtime-api/src/lib.rs b/pallets/subtensor/runtime-api/src/lib.rs index 85adada68..cdcd6ed39 100644 --- a/pallets/subtensor/runtime-api/src/lib.rs +++ b/pallets/subtensor/runtime-api/src/lib.rs @@ -25,6 +25,8 @@ sp_api::decl_runtime_apis! { fn get_subnets_info_v2() -> Vec; fn get_subnet_hyperparams(netuid: u16) -> Vec; fn get_all_dynamic_info() -> Vec; + fn get_all_metagraphs() -> Vec; + fn get_metagraph(netuid: u16) -> Vec; fn get_dynamic_info(netuid: u16) -> Vec; fn get_subnet_state(netuid: u16) -> Vec; } diff --git a/pallets/subtensor/src/rpc_info/metagraph.rs b/pallets/subtensor/src/rpc_info/metagraph.rs new file mode 100644 index 000000000..091c7e259 --- /dev/null +++ b/pallets/subtensor/src/rpc_info/metagraph.rs @@ -0,0 +1,287 @@ +use super::*; +extern crate alloc; +use crate::epoch::math::*; +use codec::Compact; +use frame_support::pallet_prelude::{Decode, Encode}; +use substrate_fixed::types::I64F64; +use subtensor_macros::freeze_struct; + +#[freeze_struct("eff674535ea437ae")] +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +pub struct Metagraph { + // Subnet index + netuid: Compact, + + // Name and symbol + name: Vec>, // name + symbol: Vec>, // token symbol + identity: Option, // identity information. + network_registered_at: Compact, // block at registration + + // Keys for owner. + owner_hotkey: T::AccountId, // hotkey + owner_coldkey: T::AccountId, // coldkey. + + // Tempo terms. + block: Compact, // block at call. + tempo: Compact, // epoch tempo + last_step: Compact, // last epoch + blocks_since_last_step: Compact, // blocks since last epoch. + + // Subnet emission terms + subnet_emission: Compact, // subnet emission via stao + alpha_in: Compact, // amount of alpha in reserve + alpha_out: Compact, // amount of alpha outstanding + tao_in: Compact, // amount of tao injected per block + alpha_out_emission: Compact, // amount injected in alpha reserves per block + alpha_in_emission: Compact, // amount injected outstanding per block + tao_in_emission: Compact, // amount of tao injected per block + pending_alpha_emission: Compact, // pending alpha to be distributed + pending_root_emission: Compact, // panding tao for root divs to be distributed + + // Hparams for epoch + rho: Compact, // subnet rho param + kappa: Compact, // subnet kappa param + + // Validator params + min_allowed_weights: Compact, // min allowed weights per val + max_weights_limit: Compact, // max allowed weights per val + weights_version: Compact, // allowed weights version + weights_rate_limit: Compact, // rate limit on weights. + activity_cutoff: Compact, // validator weights cut off period in blocks + max_validators: Compact, // max allowed validators. + + // Registration + num_uids: Compact, + max_uids: Compact, + burn: Compact, // current burn cost.. + difficulty: Compact, // current difficulty. + registration_allowed: bool, // allows registrations. + pow_registration_allowed: bool, // pow registration enabled. + immunity_period: Compact, // subnet miner immunity period + min_difficulty: Compact, // min pow difficulty + max_difficulty: Compact, // max pow difficulty + min_burn: Compact, // min tao burn + max_burn: Compact, // max tao burn + adjustment_alpha: Compact, // adjustment speed for registration params. + adjustment_interval: Compact, // pow and burn adjustment interval + target_regs_per_interval: Compact, // target registrations per interval + max_regs_per_block: Compact, // max registrations per block. + serving_rate_limit: Compact, // axon serving rate limit + + // CR + commit_reveal_weights_enabled: bool, // Is CR enabled. + commit_reveal_period: Compact, // Commit reveal interval + + // Bonds + liquid_alpha_enabled: bool, // Bonds liquid enabled. + alpha_high: Compact, // Alpha param high + alpha_low: Compact, // Alpha param low + bonds_moving_avg: Compact, // Bonds moving avg + + // Metagraph info. + hotkeys: Vec, // hotkey per UID + coldkeys: Vec, // coldkey per UID + identities: Vec, // coldkeys identities + axons: Vec, // UID axons. + active: Vec, // Avtive per UID + validator_permit: Vec, // Val permit per UID + pruning_score: Vec>, // Pruning per UID + last_update: Vec>, // Last update per UID + emission: Vec>, // Emission per UID + dividends: Vec>, // Dividends per UID + incentives: Vec>, // Mining incentives per UID + consensus: Vec>, // Consensus per UID + trust: Vec>, // Trust per UID + rank: Vec>, // Rank per UID + block_at_registration: Vec>, // Reg block per UID + alpha_stake: Vec>, // Alpha staked per UID + tao_stake: Vec>, // TAO staked per UID + total_stake: Vec>, // Total stake per UID + + // Dividend break down. + tao_dividends_per_hotkey: Vec<(T::AccountId, Compact)>, // List of dividend payouts in tao via root. + alpha_dividends_per_hotkey: Vec<(T::AccountId, Compact)>, // List of dividend payout in alpha via subnet. +} + +impl Pallet { + pub fn get_metagraph(netuid: u16) -> Option> { + if !Self::if_subnet_exist(netuid) { + return None; + } + + let n: u16 = Self::get_subnetwork_n(netuid); + let mut hotkeys: Vec = vec![]; + let mut coldkeys: Vec = vec![]; + let mut block_at_registration: Vec> = vec![]; + let mut identities: Vec = vec![]; + let mut axons: Vec = vec![]; + for uid in 0..n { + let hotkey = Keys::::get(netuid, uid); + let coldkey = Owner::::get(hotkey.clone()); + hotkeys.push(hotkey.clone()); + coldkeys.push(coldkey.clone()); + block_at_registration.push(BlockAtRegistration::::get(netuid, uid).into()); + identities.push(Identities::::get(coldkey.clone())?); + axons.push(Self::get_axon_info(netuid, &hotkey)); + } + let mut tao_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; + let mut alpha_dividends_per_hotkey: Vec<(T::AccountId, Compact)> = vec![]; + for hotkey in hotkeys.clone() { + let tao_divs = TaoDividendsPerSubnet::::get(netuid, hotkey.clone()); + let alpha_divs = AlphaDividendsPerSubnet::::get(netuid, hotkey.clone()); + tao_dividends_per_hotkey.push((hotkey.clone(), tao_divs.into())); + alpha_dividends_per_hotkey.push((hotkey.clone(), alpha_divs.into())); + } + let current_block: u64 = Pallet::::get_current_block_as_u64(); + let last_step = LastMechansimStepBlock::::get(netuid); + let blocks_since_last_step: u64 = current_block.saturating_sub(last_step); + let (total_stake_fl, alpha_stake_fl, tao_stake_fl): ( + Vec, + Vec, + Vec, + ) = Self::get_stake_weights_for_network(netuid); + Some(Metagraph { + // Subnet index + netuid: netuid.into(), // subnet index. + + // Name and symbol + name: Self::get_name_for_subnet(netuid) + .into_iter() + .map(Compact) + .collect(), // Name + symbol: Self::get_symbol_for_subnet(netuid) + .into_iter() + .map(Compact) + .collect(), // Symbol. + identity: SubnetIdentities::::get(netuid), // identity information. + network_registered_at: NetworkRegisteredAt::::get(netuid).into(), // block at registration + + // Keys for owner. + owner_hotkey: SubnetOwnerHotkey::::get(netuid), // Owner hotkey + owner_coldkey: SubnetOwner::::get(netuid), // Owner Coldkey + + // Tempo terms. + block: current_block.into(), // Block at call. + tempo: Self::get_tempo(netuid).into(), // epoch tempo + last_step: LastMechansimStepBlock::::get(netuid).into(), // last epoch + blocks_since_last_step: blocks_since_last_step.into(), // blocks since last epoch. + + // Subnet emission terms + subnet_emission: EmissionValues::::get(netuid).into(), // subnet emission via stao + alpha_in: SubnetAlphaIn::::get(netuid).into(), // amount of alpha in reserve + alpha_out: SubnetAlphaOut::::get(netuid).into(), // amount of alpha outstanding + tao_in: SubnetTAO::::get(netuid).into(), // amount of tao injected per block + alpha_out_emission: SubnetAlphaOutEmission::::get(netuid).into(), // amount injected in alpha reserves per block + alpha_in_emission: SubnetAlphaInEmission::::get(netuid).into(), // amount injected outstanding per block + tao_in_emission: SubnetTaoInEmission::::get(netuid).into(), // amount of tao injected per block + pending_alpha_emission: PendingEmission::::get(netuid).into(), // pending alpha to be distributed + pending_root_emission: PendingRootDivs::::get(netuid).into(), // panding tao for root divs to be distributed + + // Hparams for epoch + rho: Self::get_rho(netuid).into(), // subnet rho param + kappa: Self::get_kappa(netuid).into(), // subnet kappa param + + // Validator params + min_allowed_weights: Self::get_min_allowed_weights(netuid).into(), // min allowed weights per val + max_weights_limit: Self::get_max_weight_limit(netuid).into(), // max allowed weight + weights_version: Self::get_weights_version_key(netuid).into(), // allowed weights version + weights_rate_limit: Self::get_weights_set_rate_limit(netuid).into(), // rate limit on weights. + activity_cutoff: Self::get_activity_cutoff(netuid).into(), // validator weights cut off period in blocks + max_validators: Self::get_max_allowed_validators(netuid).into(), // max allowed validators. + + // Registration + num_uids: Self::get_subnetwork_n(netuid).into(), + max_uids: Self::get_max_allowed_uids(netuid).into(), + registration_allowed: Self::get_network_registration_allowed(netuid), // allows registrations. + pow_registration_allowed: Self::get_network_pow_registration_allowed(netuid), // allows pow registrations. + difficulty: Self::get_difficulty_as_u64(netuid).into(), // current difficulty. + burn: Self::get_burn_as_u64(netuid).into(), + immunity_period: Self::get_immunity_period(netuid).into(), // subnet miner immunity period + min_difficulty: Self::get_min_difficulty(netuid).into(), // min pow difficulty + max_difficulty: Self::get_max_difficulty(netuid).into(), // max pow difficulty + min_burn: Self::get_min_burn_as_u64(netuid).into(), // min tao burn + max_burn: Self::get_max_burn_as_u64(netuid).into(), // max tao burn + adjustment_alpha: Self::get_adjustment_alpha(netuid).into(), // adjustment speed for registration params. + adjustment_interval: Self::get_adjustment_interval(netuid).into(), // pow and burn adjustment interval + target_regs_per_interval: Self::get_target_registrations_per_interval(netuid).into(), // target registrations per interval + max_regs_per_block: Self::get_max_registrations_per_block(netuid).into(), // max registrations per block. + serving_rate_limit: Self::get_serving_rate_limit(netuid).into(), // axon serving rate limit + + // CR + commit_reveal_weights_enabled: Self::get_commit_reveal_weights_enabled(netuid), // Is CR enabled. + commit_reveal_period: Self::get_reveal_period(netuid).into(), // Commit reveal interval + + // Bonds + liquid_alpha_enabled: Self::get_liquid_alpha_enabled(netuid), // Bonds liquid enabled. + alpha_high: Self::get_alpha_values(netuid).1.into(), // Alpha param high + alpha_low: Self::get_alpha_values(netuid).0.into(), // Alpha param low + bonds_moving_avg: Self::get_bonds_moving_average(netuid).into(), // Bonds moving avg + + // Metagraph info. + hotkeys, // hotkey per UID + coldkeys, // coldkey per UID + axons, // Axon information per UID. + identities, + active: Active::::get(netuid), // Avtive per UID + validator_permit: ValidatorPermit::::get(netuid), // Val permit per UID + pruning_score: PruningScores::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Pruning per UID + last_update: LastUpdate::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Last update per UID + emission: Emission::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Emission per UID + dividends: Dividends::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Dividends per UID + incentives: Incentive::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Mining incentives per UID + consensus: Consensus::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Consensus per UID + trust: Trust::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Trust per UID + rank: Rank::::get(netuid) + .into_iter() + .map(Compact::from) + .collect(), // Rank per UID + block_at_registration, // Reg block per UID + alpha_stake: alpha_stake_fl + .iter() + .map(|xi| Compact::from(fixed64_to_u64(*xi))) + .collect::>>(), // Alpha staked per UID + tao_stake: tao_stake_fl + .iter() + .map(|xi| Compact::from(fixed64_to_u64(*xi))) + .collect::>>(), // TAO staked per UID + total_stake: total_stake_fl + .iter() + .map(|xi| Compact::from(fixed64_to_u64(*xi))) + .collect::>>(), // Total stake per UID + + // Dividend break down. + tao_dividends_per_hotkey, + alpha_dividends_per_hotkey, + }) + } + pub fn get_all_metagraphs() -> Vec>> { + let netuids: Vec = Self::get_all_subnet_netuids(); + let mut metagraphs = Vec::>>::new(); + for netuid in netuids.clone().iter() { + metagraphs.push(Self::get_metagraph(*netuid)); + } + metagraphs + } +} diff --git a/pallets/subtensor/src/rpc_info/mod.rs b/pallets/subtensor/src/rpc_info/mod.rs index 4c224050e..13b2e9cbb 100644 --- a/pallets/subtensor/src/rpc_info/mod.rs +++ b/pallets/subtensor/src/rpc_info/mod.rs @@ -1,6 +1,7 @@ use super::*; pub mod delegate_info; pub mod dynamic_info; +pub mod metagraph; pub mod neuron_info; pub mod show_subnet; pub mod stake_info; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 128d2b8f3..5c3688a51 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2112,6 +2112,16 @@ impl_runtime_apis! { } } + fn get_metagraph(netuid: u16) -> Vec { + let _result = SubtensorModule::get_metagraph(netuid); + if _result.is_some() { + let result = _result.expect("Could not get Metagraph."); + result.encode() + } else { + vec![] + } + } + fn get_subnet_state(netuid: u16) -> Vec { let _result = SubtensorModule::get_subnet_state(netuid); if _result.is_some() { @@ -2122,6 +2132,11 @@ impl_runtime_apis! { } } + fn get_all_metagraphs() -> Vec { + let result = SubtensorModule::get_all_metagraphs(); + result.encode() + } + fn get_all_dynamic_info() -> Vec { let result = SubtensorModule::get_all_dynamic_info(); result.encode() From af8cd5ec44946ae4e9e6820c89c90162f27cb2cd Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 22 Jan 2025 13:24:05 -0500 Subject: [PATCH 17/25] Fix tests and transfer stake after merging devnet-ready --- pallets/subtensor/src/staking/move_stake.rs | 5 +++- pallets/subtensor/src/tests/move_stake.rs | 31 +++++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index ee9cb49a7..701d8911d 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -181,7 +181,9 @@ impl Pallet { ); // 6. Unstake from the origin coldkey; this returns an amount of TAO. - let origin_tao = Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount); + let fee = DefaultMinStake::::get().saturating_div(2); + let origin_tao = + Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount, fee); // 7. Ensure the returned TAO meets a minimum stake requirement (if required). ensure!( @@ -196,6 +198,7 @@ impl Pallet { &destination_coldkey, destination_netuid, origin_tao, + fee, ); // 9. Emit an event for logging/monitoring. diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 3c1f2b448..a8896eef4 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -813,6 +813,7 @@ fn test_do_transfer_success() { let subnet_owner_coldkey = U256::from(1001); let subnet_owner_hotkey = U256::from(1002); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); // 2. Define the origin coldkey, destination coldkey, and hotkey to be used. let origin_coldkey = U256::from(1); @@ -823,7 +824,7 @@ fn test_do_transfer_success() { // 3. Set up initial stake: (origin_coldkey, hotkey) on netuid. SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); SubtensorModule::create_account_if_non_existent(&destination_coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount, 0); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &origin_coldkey, @@ -855,7 +856,7 @@ fn test_do_transfer_success() { &destination_coldkey, netuid ), - stake_amount, + stake_amount - fee, epsilon = stake_amount / 1000 ); }); @@ -922,7 +923,7 @@ fn test_do_transfer_insufficient_stake() { let stake_amount = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount, 0); let alpha = stake_amount * 2; assert_noop!( @@ -950,11 +951,12 @@ fn test_do_transfer_wrong_origin() { let wrong_coldkey = U256::from(9999); let destination_coldkey = U256::from(2); let hotkey = U256::from(3); - let stake_amount = 100_000; + let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); - SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, 1_000_000_000); - SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, stake_amount + fee); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount, fee); assert_noop!( SubtensorModule::do_transfer_stake( @@ -983,7 +985,7 @@ fn test_do_transfer_minimum_stake_check() { let stake_amount = DefaultMinStake::::get(); SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, netuid, stake_amount, 0); assert_err!( SubtensorModule::do_transfer_stake( @@ -1013,6 +1015,7 @@ fn test_do_transfer_different_subnets() { let destination_coldkey = U256::from(2); let hotkey = U256::from(3); let stake_amount = DefaultMinStake::::get() * 10; + let fee = DefaultMinStake::::get(); // 3. Create accounts if needed. SubtensorModule::create_account_if_non_existent(&origin_coldkey, &hotkey); @@ -1022,7 +1025,13 @@ fn test_do_transfer_different_subnets() { SubtensorModule::add_balance_to_coldkey_account(&origin_coldkey, 1_000_000_000); // 5. Stake into the origin subnet. - SubtensorModule::stake_into_subnet(&hotkey, &origin_coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet( + &hotkey, + &origin_coldkey, + origin_netuid, + stake_amount, + 0, + ); // 6. Transfer entire stake from origin_netuid -> destination_netuid. let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( @@ -1055,6 +1064,10 @@ fn test_do_transfer_different_subnets() { &destination_coldkey, destination_netuid, ); - assert_abs_diff_eq!(dest_stake, stake_amount, epsilon = stake_amount / 1000); + assert_abs_diff_eq!( + dest_stake, + stake_amount - fee, + epsilon = stake_amount / 1000 + ); }); } From 7d43c7e6bbc1d900a8c168967a208991da0dfc56 Mon Sep 17 00:00:00 2001 From: JohnReedV <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:58:30 -0800 Subject: [PATCH 18/25] add swap_stake & tests --- pallets/subtensor/src/macros/dispatches.rs | 44 ++- pallets/subtensor/src/macros/events.rs | 6 + pallets/subtensor/src/staking/move_stake.rs | 97 ++++++ pallets/subtensor/src/tests/move_stake.rs | 360 ++++++++++++++++++++ 4 files changed, 504 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 34712ccb5..7a3e3b240 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1608,9 +1608,6 @@ mod dispatches { /// * `destination_netuid` - The network/subnet ID to move stake to (for cross-subnet transfer). /// * `alpha_amount` - The amount of stake to transfer. /// - /// # Weight - /// Uses a fixed weight of 3_000_000 (plus any DB write overhead). - /// /// # Errors /// Returns an error if: /// * The origin is not signed by the correct coldkey. @@ -1640,5 +1637,46 @@ mod dispatches { alpha_amount, ) } + + /// Swaps a specified amount of stake from one subnet to another, while keeping the same coldkey and hotkey. + /// + /// # Arguments + /// * `origin` - The origin of the transaction, which must be signed by the coldkey that owns the `hotkey`. + /// * `hotkey` - The hotkey whose stake is being swapped. + /// * `origin_netuid` - The network/subnet ID from which stake is removed. + /// * `destination_netuid` - The network/subnet ID to which stake is added. + /// * `alpha_amount` - The amount of stake to swap. + /// + /// # Errors + /// Returns an error if: + /// * The transaction is not signed by the correct coldkey (i.e., `coldkey_owns_hotkey` fails). + /// * Either `origin_netuid` or `destination_netuid` does not exist. + /// * The hotkey does not exist. + /// * There is insufficient stake on `(coldkey, hotkey, origin_netuid)`. + /// * The swap amount is below the minimum stake requirement. + /// + /// # Events + /// May emit a `StakeSwapped` event on success. + #[pallet::call_index(87)] + #[pallet::weight(( + Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No + ))] + pub fn swap_stake( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> DispatchResult { + Self::do_swap_stake( + origin, + hotkey, + origin_netuid, + destination_netuid, + alpha_amount, + ) + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 782f6b792..9083e57f6 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -253,5 +253,11 @@ mod events { /// Parameters: /// (origin_coldkey, destination_coldkey, hotkey, origin_netuid, destination_netuid, amount) StakeTransferred(T::AccountId, T::AccountId, T::AccountId, u16, u16, u64), + + /// Stake has been swapped from one subnet to another for the same coldkey-hotkey pair. + /// + /// Parameters: + /// (coldkey, hotkey, origin_netuid, destination_netuid, amount) + StakeSwapped(T::AccountId, T::AccountId, u16, u16, u64), } } diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 49d823b86..60667e453 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -217,4 +217,101 @@ impl Pallet { // 10. Return success. Ok(()) } + + /// Swaps a specified amount of stake for the same `(coldkey, hotkey)` pair from one subnet + /// (`origin_netuid`) to another (`destination_netuid`). + /// + /// # Arguments + /// * `origin` - The origin of the transaction, which must be signed by the coldkey that owns the hotkey. + /// * `hotkey` - The hotkey whose stake is being swapped. + /// * `origin_netuid` - The subnet ID from which stake is removed. + /// * `destination_netuid` - The subnet ID to which stake is added. + /// * `alpha_amount` - The amount of stake to swap. + /// + /// # Returns + /// * `DispatchResult` - Indicates success or failure. + /// + /// # Errors + /// This function returns an error if: + /// * The origin is not signed by the correct coldkey (i.e., not associated with `hotkey`). + /// * Either the `origin_netuid` or the `destination_netuid` does not exist. + /// * The specified `hotkey` does not exist. + /// * The `(coldkey, hotkey, origin_netuid)` does not have enough stake (`alpha_amount`). + /// * The unstaked amount is below `DefaultMinStake`. + /// + /// # Events + /// Emits a `StakeSwapped` event upon successful completion. + pub fn do_swap_stake( + origin: T::RuntimeOrigin, + hotkey: T::AccountId, + origin_netuid: u16, + destination_netuid: u16, + alpha_amount: u64, + ) -> dispatch::DispatchResult { + // 1. Ensure the extrinsic is signed by the coldkey. + let coldkey = ensure_signed(origin)?; + + // 2. Check that both subnets exist. + ensure!( + Self::if_subnet_exist(origin_netuid), + Error::::SubnetNotExists + ); + ensure!( + Self::if_subnet_exist(destination_netuid), + Error::::SubnetNotExists + ); + + // 3. Check that the hotkey exists. + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + // 4. Ensure this coldkey actually owns the hotkey. + ensure!( + Self::coldkey_owns_hotkey(&coldkey, &hotkey), + Error::::NonAssociatedColdKey + ); + + // 5. Ensure there is enough stake in the origin subnet. + let origin_alpha = + Self::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, origin_netuid); + ensure!( + alpha_amount <= origin_alpha, + Error::::NotEnoughStakeToWithdraw + ); + + // 6. Unstake from the origin subnet, returning TAO (or a 1:1 equivalent). + let tao_unstaked = + Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount); + + // 7. Check that the unstaked amount is above the minimum stake threshold. + ensure!( + tao_unstaked >= DefaultMinStake::::get(), + Error::::AmountTooLow + ); + + // 8. Stake the unstaked amount into the destination subnet, using the same coldkey/hotkey. + Self::stake_into_subnet(&hotkey, &coldkey, destination_netuid, tao_unstaked); + + // 9. Emit an event for logging. + log::info!( + "StakeSwapped(coldkey: {:?}, hotkey: {:?}, origin_netuid: {:?}, destination_netuid: {:?}, amount: {:?})", + coldkey, + hotkey, + origin_netuid, + destination_netuid, + tao_unstaked + ); + Self::deposit_event(Event::StakeSwapped( + coldkey, + hotkey, + origin_netuid, + destination_netuid, + tao_unstaked, + )); + + // 10. Return success. + Ok(()) + } } diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 59afc9b62..b15f49d92 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -1026,3 +1026,363 @@ fn test_do_transfer_different_subnets() { assert_abs_diff_eq!(dest_stake, stake_amount, epsilon = stake_amount / 1000); }); } + +#[test] +fn test_do_swap_success() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount); + let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + ); + + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + alpha_before, + )); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid + ), + 0 + ); + + let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + destination_netuid, + ); + assert_abs_diff_eq!(alpha_after, stake_amount, epsilon = stake_amount / 1000); + }); +} + +#[test] +fn test_do_swap_nonexistent_subnet() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let nonexistent_netuid: u16 = 9999; + let stake_amount = 1_000_000; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + + assert_noop!( + SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + nonexistent_netuid, + nonexistent_netuid, + stake_amount + ), + Error::::SubnetNotExists + ); + }); +} + +#[test] +fn test_do_swap_nonexistent_hotkey() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let nonexistent_hotkey = U256::from(999); + let stake_amount = 10_000; + + assert_noop!( + SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + nonexistent_hotkey, + netuid1, + netuid2, + stake_amount + ), + Error::::HotKeyAccountNotExists + ); + }); +} + +#[test] +fn test_do_swap_insufficient_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 5; + let attempted_swap = stake_amount * 2; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, stake_amount); + + assert_noop!( + SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid1, + netuid2, + attempted_swap + ), + Error::::NotEnoughStakeToWithdraw + ); + }); +} + +#[test] +fn test_do_swap_wrong_origin() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1010); + let subnet_owner_hotkey = U256::from(1011); + let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let real_coldkey = U256::from(1); + let wrong_coldkey = U256::from(9999); + let hotkey = U256::from(3); + let stake_amount = 100_000; + + SubtensorModule::create_account_if_non_existent(&real_coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &real_coldkey, netuid1, stake_amount); + + assert_noop!( + SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(wrong_coldkey), + hotkey, + netuid1, + netuid2, + stake_amount + ), + Error::::NonAssociatedColdKey + ); + }); +} + +#[test] +fn test_do_swap_minimum_stake_check() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(3); + let total_stake = DefaultMinStake::::get(); + let swap_amount = 1; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, total_stake); + + assert_err!( + SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + netuid, + swap_amount + ), + Error::::AmountTooLow + ); + }); +} + +#[test] +fn test_do_swap_same_subnet() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1100); + let subnet_owner_hotkey = U256::from(1101); + let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount); + + let alpha_before = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid, + netuid, + alpha_before + )); + + let alpha_after = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + assert_abs_diff_eq!(alpha_after, alpha_before, epsilon = 5); + }); +} + +#[test] +fn test_do_swap_partial_stake() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1100); + let subnet_owner_hotkey = U256::from(1101); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let total_stake = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, total_stake); + + let swap_amount = total_stake / 2; + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + swap_amount, + )); + + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid + ), + total_stake - swap_amount, + epsilon = total_stake / 1000 + ); + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + destination_netuid + ), + swap_amount, + epsilon = total_stake / 1000 + ); + }); +} + +#[test] +fn test_do_swap_storage_updates() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1300); + let subnet_owner_hotkey = U256::from(1301); + let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let stake_amount = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount); + + let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid, + ); + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + origin_netuid, + destination_netuid, + alpha + )); + + assert_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + origin_netuid + ), + 0 + ); + + assert_abs_diff_eq!( + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, + &coldkey, + destination_netuid + ), + alpha, + epsilon = 5 + ); + }); +} + +#[test] +fn test_do_swap_multiple_times() { + new_test_ext(1).execute_with(|| { + let subnet_owner_coldkey = U256::from(1500); + let subnet_owner_hotkey = U256::from(1501); + let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let initial_stake = DefaultMinStake::::get() * 10; + + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, initial_stake); + + for _ in 0..3 { + let alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid1, + ); + if alpha1 > 0 { + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid1, + netuid2, + alpha1 + )); + } + let alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid2, + ); + if alpha2 > 0 { + assert_ok!(SubtensorModule::do_swap_stake( + RuntimeOrigin::signed(coldkey), + hotkey, + netuid2, + netuid1, + alpha2 + )); + } + } + + let final_stake_netuid1 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid1); + let final_stake_netuid2 = + SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid2); + assert_abs_diff_eq!( + final_stake_netuid1, + initial_stake, + epsilon = initial_stake / 1000 + ); + assert_eq!(final_stake_netuid2, 0); + }); +} From 845cba99e605bde195f3fb8d4d705131d466bbb0 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 22 Jan 2025 13:58:46 -0500 Subject: [PATCH 19/25] Make commitment pallet rate limit configurable via sudo --- pallets/commitments/src/lib.rs | 27 +++++++++++++++++++++++++-- pallets/commitments/src/tests.rs | 2 +- pallets/commitments/src/weights.rs | 12 ++++++++++++ runtime/src/lib.rs | 2 +- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index d5d132034..7e862a593 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -58,7 +58,7 @@ pub mod pallet { /// The rate limit for commitments #[pallet::constant] - type RateLimit: Get>; + type DefaultRateLimit: Get>; } #[pallet::event] @@ -83,6 +83,16 @@ pub mod pallet { CommitmentSetRateLimitExceeded, } + #[pallet::type_value] + /// Default value for commitment rate limit. + pub fn DefaultRateLimit() -> BlockNumberFor { + T::DefaultRateLimit::get() + } + + /// The rate limit for commitments + #[pallet::storage] + pub type RateLimit = StorageValue<_, BlockNumberFor, ValueQuery, DefaultRateLimit>; + /// Identity data by account #[pallet::storage] #[pallet::getter(fn commitment_of)] @@ -137,7 +147,7 @@ pub mod pallet { let cur_block = >::block_number(); if let Some(last_commit) = >::get(netuid, &who) { ensure!( - cur_block >= last_commit.saturating_add(T::RateLimit::get()), + cur_block >= last_commit.saturating_add(RateLimit::::get()), Error::::CommitmentSetRateLimitExceeded ); } @@ -173,6 +183,19 @@ pub mod pallet { Ok(()) } + + /// Sudo-set the commitment rate limit + #[pallet::call_index(1)] + #[pallet::weight(( + T::WeightInfo::set_rate_limit(), + DispatchClass::Operational, + Pays::No + ))] + pub fn set_rate_limit(origin: OriginFor, rate_limit_blocks: u32) -> DispatchResult { + ensure_root(origin)?; + RateLimit::::set(rate_limit_blocks.into()); + Ok(()) + } } } diff --git a/pallets/commitments/src/tests.rs b/pallets/commitments/src/tests.rs index 036cb5d83..15675d8ad 100644 --- a/pallets/commitments/src/tests.rs +++ b/pallets/commitments/src/tests.rs @@ -86,7 +86,7 @@ impl pallet_commitments::Config for Test { type CanCommit = (); type FieldDeposit = frame_support::traits::ConstU64<0>; type InitialDeposit = frame_support::traits::ConstU64<0>; - type RateLimit = frame_support::traits::ConstU64<0>; + type DefaultRateLimit = frame_support::traits::ConstU64<0>; } // // Build genesis storage according to the mock runtime. diff --git a/pallets/commitments/src/weights.rs b/pallets/commitments/src/weights.rs index 5e58b6873..b91017e05 100644 --- a/pallets/commitments/src/weights.rs +++ b/pallets/commitments/src/weights.rs @@ -30,6 +30,7 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_commitments`. pub trait WeightInfo { fn set_commitment() -> Weight; + fn set_rate_limit() -> Weight; } /// Weights for `pallet_commitments` using the Substrate node and recommended hardware. @@ -48,6 +49,11 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Sudo setting rate limit for commitments + fn set_rate_limit() -> Weight { + Weight::from_parts(10_000_000, 2000) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } } // For backwards compatibility and tests. @@ -65,4 +71,10 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + + /// Sudo setting rate limit for commitments + fn set_rate_limit() -> Weight { + Weight::from_parts(10_000_000, 2000) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } } \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5c3688a51..36a879e48 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -974,7 +974,7 @@ impl pallet_commitments::Config for Runtime { type MaxFields = MaxCommitFields; type InitialDeposit = CommitmentInitialDeposit; type FieldDeposit = CommitmentFieldDeposit; - type RateLimit = CommitmentRateLimit; + type DefaultRateLimit = CommitmentRateLimit; } #[cfg(not(feature = "fast-blocks"))] From e797da95521577ddc610f508f31abd4f76a7ce55 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 23 Jan 2025 16:12:08 +0100 Subject: [PATCH 20/25] Fix gas estimation issues --- Cargo.lock | 45 +++++++++++ Cargo.toml | 3 +- node/src/chain_spec/localnet.rs | 13 ++- runtime/Cargo.toml | 2 + runtime/src/precompiles/balance_transfer.rs | 88 +++++++++++---------- runtime/src/precompiles/staking.rs | 42 +++++----- 6 files changed, 130 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 964c583e1..e86ef3d69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1067,6 +1067,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + [[package]] name = "cc" version = "1.1.24" @@ -5658,6 +5664,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", "parity-scale-codec", + "precompile-utils", "rand_chacha", "scale-info", "serde_json", @@ -7010,6 +7017,44 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precompile-utils" +version = "0.1.0" +source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac882333afed827053f31ef56ab739f7a2e" +dependencies = [ + "environmental", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex", + "impl-trait-for-tuples", + "log", + "num_enum", + "pallet-evm", + "parity-scale-codec", + "precompile-utils-macro", + "sp-core", + "sp-io", + "sp-runtime", + "sp-weights", + "staging-xcm", +] + +[[package]] +name = "precompile-utils-macro" +version = "0.1.0" +source = "git+https://github.com/opentensor/frontier?rev=635bdac882#635bdac882333afed827053f31ef56ab739f7a2e" +dependencies = [ + "case", + "num_enum", + "prettyplease 0.2.22", + "proc-macro2", + "quote", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409)", + "syn 1.0.109", +] + [[package]] name = "predicates" version = "2.1.5" diff --git a/Cargo.toml b/Cargo.toml index 3d5adb18a..0e723d6ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,9 +180,10 @@ fc-consensus = { git = "https://github.com/opentensor/frontier", rev = "635bdac8 fp-consensus = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fp-dynamic-fee = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fc-api = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } -fc-rpc = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } +fc-rpc = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false, features = ["rpc-binary-search-estimate"]} fc-rpc-core = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } fc-mapping-sync = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } +precompile-utils = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } # Frontier FRAME pallet-base-fee = { git = "https://github.com/opentensor/frontier", rev = "635bdac882", default-features = false } diff --git a/node/src/chain_spec/localnet.rs b/node/src/chain_spec/localnet.rs index 7f5b3611c..4aa84f8ca 100644 --- a/node/src/chain_spec/localnet.rs +++ b/node/src/chain_spec/localnet.rs @@ -76,6 +76,17 @@ fn localnet_genesis( ( get_account_id_from_seed::("Ferdie"), 2000000000000u128, + ), + // ETH + ( + // Alith - 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac + AccountId::from_str("5Fghzk1AJt88PeFEzuRfXzbPchiBbsVGTTXcdx599VdZzkTA").unwrap(), + 2000000000000u128, + ), + ( + // Baltathar - 0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0 + AccountId::from_str("5GeqNhKWj1KG78VHzbmo3ZjZgUTrCuWeamdgiA114XHGdaEr").unwrap(), + 2000000000000u128, ), ]; @@ -120,7 +131,7 @@ fn localnet_genesis( "senateMembers": { "members": senate_members, }, - "evmChainId": { + "evmChainId": { "chainId": 42, }, }) diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index cdb8fd52f..0d44e0907 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -98,6 +98,7 @@ pallet-commitments = { default-features = false, path = "../pallets/commitments" fp-evm = { workspace = true } fp-rpc = { workspace = true } fp-self-contained = { workspace = true } +precompile-utils = { workspace = true } # Frontier FRAME pallet-base-fee = { workspace = true } @@ -191,6 +192,7 @@ std = [ "fp-evm/std", "fp-rpc/std", "fp-self-contained/std", + "precompile-utils/std", # Frontier FRAME "pallet-base-fee/std", "pallet-dynamic-fee/std", diff --git a/runtime/src/precompiles/balance_transfer.rs b/runtime/src/precompiles/balance_transfer.rs index 99d911b02..7c534212d 100644 --- a/runtime/src/precompiles/balance_transfer.rs +++ b/runtime/src/precompiles/balance_transfer.rs @@ -3,16 +3,24 @@ use pallet_evm::{ BalanceConverter, ExitError, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, }; +use precompile_utils::prelude::RuntimeHelper; use sp_core::U256; use sp_runtime::traits::{Dispatchable, UniqueSaturatedInto}; use sp_std::vec; -use crate::{Runtime, RuntimeCall}; - use crate::precompiles::{bytes_to_account_id, get_method_id, get_slice}; +use crate::{Runtime, RuntimeCall}; pub const BALANCE_TRANSFER_INDEX: u64 = 2048; +// This is a hardcoded hashed address mapping of 0x0000000000000000000000000000000000000800 to an +// ss58 public key i.e., the contract sends funds it received to the destination address from the +// method parameter. +const CONTRACT_ADDRESS_SS58: [u8; 32] = [ + 0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, 0xcd, 0xfc, + 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, 0x62, 0x93, 0x70, 0x5d, +]; + pub struct BalanceTransferPrecompile; impl BalanceTransferPrecompile { @@ -20,54 +28,50 @@ impl BalanceTransferPrecompile { let txdata = handle.input(); // Match method ID: keccak256("transfer(bytes32)") - let method: &[u8] = get_slice(txdata, 0, 4)?; - if get_method_id("transfer(bytes32)") == method { - // Forward all received value to the destination address - let amount: U256 = handle.context().apparent_value; + let method = get_slice(txdata, 0, 4)?; + if get_method_id("transfer(bytes32)") != method { + return Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: vec![], + }); + } - // Use BalanceConverter to convert EVM amount to Substrate balance - let amount_sub = - ::BalanceConverter::into_substrate_balance(amount) - .ok_or(ExitError::OutOfFund)?; + // Forward all received value to the destination address + let amount: U256 = handle.context().apparent_value; - if amount_sub.is_zero() { - return Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output: vec![], - }); - } + // Use BalanceConverter to convert EVM amount to Substrate balance + let amount_sub = + ::BalanceConverter::into_substrate_balance(amount) + .ok_or(ExitError::OutOfFund)?; - // This is a hardcoded hashed address mapping of - // 0x0000000000000000000000000000000000000800 to an ss58 public key - // i.e., the contract sends funds it received to the destination address - // from the method parameter. - const ADDRESS_BYTES_SRC: [u8; 32] = [ - 0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, - 0xcd, 0xfc, 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, - 0x62, 0x93, 0x70, 0x5d, - ]; - let address_bytes_dst: &[u8] = get_slice(txdata, 4, 36)?; - let account_id_src = bytes_to_account_id(&ADDRESS_BYTES_SRC)?; - let account_id_dst = bytes_to_account_id(address_bytes_dst)?; + if amount_sub.is_zero() { + return Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: vec![], + }); + } - let call = - RuntimeCall::Balances(pallet_balances::Call::::transfer_allow_death { - dest: account_id_dst.into(), - value: amount_sub.unique_saturated_into(), - }); + let address_bytes_dst = get_slice(txdata, 4, 36)?; + let account_id_src = bytes_to_account_id(&CONTRACT_ADDRESS_SS58)?; + let account_id_dst = bytes_to_account_id(address_bytes_dst)?; - // Dispatch the call - let result = call.dispatch(RawOrigin::Signed(account_id_src).into()); - if result.is_err() { - return Err(PrecompileFailure::Error { - exit_status: ExitError::OutOfFund, - }); - } - } + let call = RuntimeCall::Balances(pallet_balances::Call::::transfer_allow_death { + dest: account_id_dst.into(), + value: amount_sub.unique_saturated_into(), + }); - Ok(PrecompileOutput { + // Dispatch the call + RuntimeHelper::::try_dispatch( + handle, + RawOrigin::Signed(account_id_src).into(), + call, + ) + .map(|_| PrecompileOutput { exit_status: ExitSucceed::Returned, output: vec![], }) + .map_err(|_| PrecompileFailure::Error { + exit_status: ExitError::OutOfFund, + }) } } diff --git a/runtime/src/precompiles/staking.rs b/runtime/src/precompiles/staking.rs index 86d257a19..e670e3b3b 100644 --- a/runtime/src/precompiles/staking.rs +++ b/runtime/src/precompiles/staking.rs @@ -25,24 +25,24 @@ // - Precompile checks the result of do_remove_stake and, in case of a failure, reverts the transaction. // +use frame_support::dispatch::{GetDispatchInfo, Pays}; use frame_system::RawOrigin; -use pallet_evm::{AddressMapping, BalanceConverter, HashedAddressMapping}; use pallet_evm::{ - ExitError, ExitSucceed, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, + AddressMapping, BalanceConverter, ExitError, ExitSucceed, GasWeightMapping, + HashedAddressMapping, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, }; +use precompile_utils::prelude::RuntimeHelper; use sp_core::crypto::Ss58Codec; use sp_core::U256; -use sp_runtime::traits::Dispatchable; -use sp_runtime::traits::{BlakeTwo256, StaticLookup, UniqueSaturatedInto}; +use sp_runtime::traits::{BlakeTwo256, Dispatchable, StaticLookup, UniqueSaturatedInto}; use sp_runtime::AccountId32; +use sp_std::vec; use crate::{ precompiles::{get_method_id, get_slice}, - ProxyType, + ProxyType, Runtime, RuntimeCall, }; -use sp_std::vec; -use crate::{Runtime, RuntimeCall}; pub const STAKING_PRECOMPILE_INDEX: u64 = 2049; pub struct StakingPrecompile; @@ -209,24 +209,28 @@ impl StakingPrecompile { ); // Transfer the amount back to the caller before executing the staking operation - // let caller = handle.context().caller; let amount = handle.context().apparent_value; if !amount.is_zero() { Self::transfer_back_to_caller(&account_id, amount)?; } - let result = call.dispatch(RawOrigin::Signed(account_id.clone()).into()); - match &result { - Ok(post_info) => log::info!("Dispatch succeeded. Post info: {:?}", post_info), - Err(dispatch_error) => log::error!("Dispatch failed. Error: {:?}", dispatch_error), - } - match result { - Ok(_) => Ok(PrecompileOutput { - exit_status: ExitSucceed::Returned, - output: vec![], - }), - Err(_) => { + match RuntimeHelper::::try_dispatch( + handle, + RawOrigin::Signed(account_id.clone()).into(), + call, + ) { + Ok(post_info) => { + log::info!("Dispatch succeeded. Post info: {:?}", post_info); + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: vec![], + }) + } + + Err(dispatch_error) => { + log::error!("Dispatch failed. Error: {:?}", dispatch_error); log::warn!("Returning error PrecompileFailure::Error"); Err(PrecompileFailure::Error { exit_status: ExitError::Other("Subtensor call failed".into()), From d66e2ed8197bf37120943ab023379b1b7df93d5f Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 23 Jan 2025 16:18:19 +0100 Subject: [PATCH 21/25] Reformat --- node/src/chain_spec/localnet.rs | 4 ++-- runtime/src/precompiles/balance_transfer.rs | 2 +- runtime/src/precompiles/staking.rs | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/node/src/chain_spec/localnet.rs b/node/src/chain_spec/localnet.rs index 4aa84f8ca..3f0743199 100644 --- a/node/src/chain_spec/localnet.rs +++ b/node/src/chain_spec/localnet.rs @@ -77,7 +77,7 @@ fn localnet_genesis( get_account_id_from_seed::("Ferdie"), 2000000000000u128, ), - // ETH + // ETH ( // Alith - 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac AccountId::from_str("5Fghzk1AJt88PeFEzuRfXzbPchiBbsVGTTXcdx599VdZzkTA").unwrap(), @@ -131,7 +131,7 @@ fn localnet_genesis( "senateMembers": { "members": senate_members, }, - "evmChainId": { + "evmChainId": { "chainId": 42, }, }) diff --git a/runtime/src/precompiles/balance_transfer.rs b/runtime/src/precompiles/balance_transfer.rs index 7c534212d..03c4be8a6 100644 --- a/runtime/src/precompiles/balance_transfer.rs +++ b/runtime/src/precompiles/balance_transfer.rs @@ -5,7 +5,7 @@ use pallet_evm::{ }; use precompile_utils::prelude::RuntimeHelper; use sp_core::U256; -use sp_runtime::traits::{Dispatchable, UniqueSaturatedInto}; +use sp_runtime::traits::UniqueSaturatedInto; use sp_std::vec; use crate::precompiles::{bytes_to_account_id, get_method_id, get_slice}; diff --git a/runtime/src/precompiles/staking.rs b/runtime/src/precompiles/staking.rs index e670e3b3b..f8534927b 100644 --- a/runtime/src/precompiles/staking.rs +++ b/runtime/src/precompiles/staking.rs @@ -25,11 +25,10 @@ // - Precompile checks the result of do_remove_stake and, in case of a failure, reverts the transaction. // -use frame_support::dispatch::{GetDispatchInfo, Pays}; use frame_system::RawOrigin; use pallet_evm::{ - AddressMapping, BalanceConverter, ExitError, ExitSucceed, GasWeightMapping, - HashedAddressMapping, PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, + AddressMapping, BalanceConverter, ExitError, ExitSucceed, HashedAddressMapping, + PrecompileFailure, PrecompileHandle, PrecompileOutput, PrecompileResult, }; use precompile_utils::prelude::RuntimeHelper; use sp_core::crypto::Ss58Codec; From 65aad3c04ad64a7ca6eeeb517a81e863c731b534 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 23 Jan 2025 11:05:20 -0500 Subject: [PATCH 22/25] add fee for swap stake --- pallets/subtensor/src/staking/move_stake.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 38a783b25..fff688084 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -288,8 +288,9 @@ impl Pallet { ); // 6. Unstake from the origin subnet, returning TAO (or a 1:1 equivalent). + let fee = DefaultMinStake::::get().saturating_div(2); let tao_unstaked = - Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount); + Self::unstake_from_subnet(&hotkey, &coldkey, origin_netuid, alpha_amount, fee); // 7. Check that the unstaked amount is above the minimum stake threshold. ensure!( @@ -298,7 +299,7 @@ impl Pallet { ); // 8. Stake the unstaked amount into the destination subnet, using the same coldkey/hotkey. - Self::stake_into_subnet(&hotkey, &coldkey, destination_netuid, tao_unstaked); + Self::stake_into_subnet(&hotkey, &coldkey, destination_netuid, tao_unstaked, fee); // 9. Emit an event for logging. log::info!( From 438b67837047f5fe2ecadc100e54497c59b58523 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 23 Jan 2025 11:06:25 -0500 Subject: [PATCH 23/25] add 0-fee to tests --- pallets/subtensor/src/tests/move_stake.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 150fa1201..04f79f3f9 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -1085,7 +1085,7 @@ fn test_do_swap_success() { let stake_amount = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount, 0); let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, @@ -1180,7 +1180,7 @@ fn test_do_swap_insufficient_stake() { let attempted_swap = stake_amount * 2; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, stake_amount, 0); assert_noop!( SubtensorModule::do_swap_stake( @@ -1209,7 +1209,7 @@ fn test_do_swap_wrong_origin() { let stake_amount = 100_000; SubtensorModule::create_account_if_non_existent(&real_coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &real_coldkey, netuid1, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &real_coldkey, netuid1, stake_amount, 0); assert_noop!( SubtensorModule::do_swap_stake( @@ -1237,7 +1237,7 @@ fn test_do_swap_minimum_stake_check() { let swap_amount = 1; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, total_stake); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, total_stake, 0); assert_err!( SubtensorModule::do_swap_stake( @@ -1264,7 +1264,7 @@ fn test_do_swap_same_subnet() { let stake_amount = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid, stake_amount, 0); let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); @@ -1296,7 +1296,7 @@ fn test_do_swap_partial_stake() { let total_stake = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, total_stake); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, total_stake, 0); let swap_amount = total_stake / 2; assert_ok!(SubtensorModule::do_swap_stake( @@ -1341,7 +1341,7 @@ fn test_do_swap_storage_updates() { let stake_amount = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, stake_amount, 0); let alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, @@ -1390,7 +1390,7 @@ fn test_do_swap_multiple_times() { let initial_stake = DefaultMinStake::::get() * 10; SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); - SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, initial_stake); + SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, initial_stake, 0); for _ in 0..3 { let alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( From b42641f93ac9e8eed72067569153caf0648fa91a Mon Sep 17 00:00:00 2001 From: andreea-popescu-reef <160024917+andreea-popescu-reef@users.noreply.github.com> Date: Fri, 24 Jan 2025 00:22:55 +0800 Subject: [PATCH 24/25] Add bonds_penalty hyperparam (#1160) * add bonds_penalty hyperparam * fix safe math * add bonds_penalty admin-utils --- hyperparameters.md | 8 +- pallets/admin-utils/src/benchmarking.rs | 8 + pallets/admin-utils/src/lib.rs | 25 +++ pallets/admin-utils/src/tests/mock.rs | 2 + pallets/admin-utils/src/tests/mod.rs | 33 ++++ pallets/admin-utils/src/weights.rs | 27 +++ pallets/subtensor/src/epoch/math.rs | 84 ++++++++ pallets/subtensor/src/epoch/run_epoch.rs | 53 +++-- pallets/subtensor/src/lib.rs | 9 + pallets/subtensor/src/macros/config.rs | 3 + pallets/subtensor/src/macros/events.rs | 2 + pallets/subtensor/src/tests/epoch.rs | 221 +++++++++++---------- pallets/subtensor/src/tests/math.rs | 234 +++++++++++++++++++++++ pallets/subtensor/src/tests/mock.rs | 2 + pallets/subtensor/src/utils/misc.rs | 8 + runtime/src/lib.rs | 2 + 16 files changed, 604 insertions(+), 117 deletions(-) diff --git a/hyperparameters.md b/hyperparameters.md index 96f955437..c8d2ce110 100644 --- a/hyperparameters.md +++ b/hyperparameters.md @@ -7,7 +7,7 @@ TxRateLimit: u64 = 1; // [1 @ 64,888] ### netuid 1 (text_prompting) ```rust Rho: u16 = 10; -Kappa: u16 = 32_767; // 0.5 = 65535/2 +Kappa: u16 = 32_767; // 0.5 = 65535/2 MaxAllowedUids: u16 = 1024; Issuance: u64 = 0; MinAllowedWeights: u16 = 8; @@ -32,10 +32,11 @@ ActivityCutoff: u16 = 5000; MaxRegistrationsPerBlock: u16 = 1; PruningScore : u16 = u16::MAX; BondsMovingAverage: u64 = 900_000; +BondsPenalty: u16 = 0; WeightsVersionKey: u64 = 1020; MinDifficulty: u64 = 10_000_000; MaxDifficulty: u64 = u64::MAX / 4; -ServingRateLimit: u64 = 10; +ServingRateLimit: u64 = 10; Burn: u64 = 1_000_000_000; // 1 tao MinBurn: u64 = 1_000_000_000; // 1 tao MaxBurn: u64 = 100_000_000_000; // 100 tao @@ -45,7 +46,7 @@ WeightsSetRateLimit: u64 = 100; ### netuid 3 (causallmnext) ```rust Rho: u16 = 10; -Kappa: u16 = 32_767; // 0.5 = 65535/2 +Kappa: u16 = 32_767; // 0.5 = 65535/2 MaxAllowedUids: u16 = 4096; Issuance: u64 = 0; MinAllowedWeights: u16 = 50; @@ -70,6 +71,7 @@ ActivityCutoff: u16 = 5000; // [5000 @ 7,163] MaxRegistrationsPerBlock: u16 = 1; PruningScore : u16 = u16::MAX; BondsMovingAverage: u64 = 900_000; +BondsPenalty: u16 = 0; WeightsVersionKey: u64 = 400; MinDifficulty: u64 = 10_000_000; MaxDifficulty: u64 = u64::MAX / 4; diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index abbcd0f16..af9b68051 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -100,6 +100,14 @@ mod benchmarks { _(RawOrigin::Root, 1u16/*netuid*/, 100u64/*bonds_moving_average*/)/*sudo_set_bonds_moving_average*/; } + #[benchmark] + fn sudo_set_bonds_penalty() { + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16/*netuid*/, 100u16/*bonds_penalty*/)/*sudo_set_bonds_penalty*/; + } + #[benchmark] fn sudo_set_max_allowed_validators() { pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index f6b132148..7de39aa38 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -685,6 +685,31 @@ pub mod pallet { Ok(()) } + /// The extrinsic sets the bonds penalty for a subnet. + /// It is only callable by the root account or subnet owner. + /// The extrinsic will call the Subtensor pallet to set the bonds penalty. + #[pallet::call_index(60)] + #[pallet::weight(::WeightInfo::sudo_set_bonds_penalty())] + pub fn sudo_set_bonds_penalty( + origin: OriginFor, + netuid: u16, + bonds_penalty: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + pallet_subtensor::Pallet::::set_bonds_penalty(netuid, bonds_penalty); + log::debug!( + "BondsPenalty( netuid: {:?} bonds_penalty: {:?} ) ", + netuid, + bonds_penalty + ); + Ok(()) + } + /// The extrinsic sets the maximum registrations per block for a subnet. /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the maximum registrations per block. diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index b2dbb66c0..5517196f1 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -86,6 +86,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; + pub const InitialBondsPenalty: u16 = 0; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. @@ -163,6 +164,7 @@ impl pallet_subtensor::Config for Test { type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; + type InitialBondsPenalty = InitialBondsPenalty; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 01fb985d9..a3b771444 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -741,6 +741,39 @@ fn test_sudo_set_bonds_moving_average() { }); } +#[test] +fn test_sudo_set_bonds_penalty() { + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + let to_be_set: u16 = 10; + add_network(netuid, 10); + let init_value: u16 = SubtensorModule::get_bonds_penalty(netuid); + assert_eq!( + AdminUtils::sudo_set_bonds_penalty( + <::RuntimeOrigin>::signed(U256::from(1)), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin) + ); + assert_eq!( + AdminUtils::sudo_set_bonds_penalty( + <::RuntimeOrigin>::root(), + netuid + 1, + to_be_set + ), + Err(Error::::SubnetDoesNotExist.into()) + ); + assert_eq!(SubtensorModule::get_bonds_penalty(netuid), init_value); + assert_ok!(AdminUtils::sudo_set_bonds_penalty( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_bonds_penalty(netuid), to_be_set); + }); +} + #[test] fn test_sudo_set_rao_recycled() { new_test_ext().execute_with(|| { diff --git a/pallets/admin-utils/src/weights.rs b/pallets/admin-utils/src/weights.rs index cb7017023..6ef952354 100644 --- a/pallets/admin-utils/src/weights.rs +++ b/pallets/admin-utils/src/weights.rs @@ -42,6 +42,7 @@ pub trait WeightInfo { fn sudo_set_weights_set_rate_limit() -> Weight; fn sudo_set_weights_version_key() -> Weight; fn sudo_set_bonds_moving_average() -> Weight; + fn sudo_set_bonds_penalty() -> Weight; fn sudo_set_max_allowed_validators() -> Weight; fn sudo_set_difficulty() -> Weight; fn sudo_set_adjustment_interval() -> Weight; @@ -182,6 +183,19 @@ impl WeightInfo for SubstrateWeight { } /// Storage: SubtensorModule NetworksAdded (r:1 w:0) /// Proof Skipped: SubtensorModule NetworksAdded (max_values: None, max_size: None, mode: Measured) + /// Storage: SubtensorModule BondsPenalty (r:0 w:1) + /// Proof Skipped: SubtensorModule BondsPenalty (max_values: None, max_size: None, mode: Measured) + fn sudo_set_bonds_penalty() -> Weight { + // Proof Size summary in bytes: + // Measured: `1111` + // Estimated: `4697` + // Minimum execution time: 46_099_000 picoseconds. + Weight::from_parts(47_510_000, 4697) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: SubtensorModule NetworksAdded (r:1 w:0) + /// Proof Skipped: SubtensorModule NetworksAdded (max_values: None, max_size: None, mode: Measured) /// Storage: SubtensorModule MaxAllowedUids (r:1 w:0) /// Proof Skipped: SubtensorModule MaxAllowedUids (max_values: None, max_size: None, mode: Measured) /// Storage: SubtensorModule MaxAllowedValidators (r:0 w:1) @@ -559,6 +573,19 @@ impl WeightInfo for () { } /// Storage: SubtensorModule NetworksAdded (r:1 w:0) /// Proof Skipped: SubtensorModule NetworksAdded (max_values: None, max_size: None, mode: Measured) + /// Storage: SubtensorModule BondsPenalty (r:0 w:1) + /// Proof Skipped: SubtensorModule BondsPenalty (max_values: None, max_size: None, mode: Measured) + fn sudo_set_bonds_penalty() -> Weight { + // Proof Size summary in bytes: + // Measured: `1111` + // Estimated: `4697` + // Minimum execution time: 46_099_000 picoseconds. + Weight::from_parts(47_510_000, 4697) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: SubtensorModule NetworksAdded (r:1 w:0) + /// Proof Skipped: SubtensorModule NetworksAdded (max_values: None, max_size: None, mode: Measured) /// Storage: SubtensorModule MaxAllowedUids (r:1 w:0) /// Proof Skipped: SubtensorModule MaxAllowedUids (max_values: None, max_size: None, mode: Measured) /// Storage: SubtensorModule MaxAllowedValidators (r:0 w:1) diff --git a/pallets/subtensor/src/epoch/math.rs b/pallets/subtensor/src/epoch/math.rs index 57d0f6b6f..616a9b78b 100644 --- a/pallets/subtensor/src/epoch/math.rs +++ b/pallets/subtensor/src/epoch/math.rs @@ -1022,6 +1022,90 @@ pub fn weighted_median_col_sparse( median } +// Element-wise interpolation of two matrices: Result = A + ratio * (B - A). +// ratio has intended range [0, 1] +// ratio=0: Result = A +// ratio=1: Result = B +#[allow(dead_code)] +pub fn interpolate(mat1: &[Vec], mat2: &[Vec], ratio: I32F32) -> Vec> { + if ratio == I32F32::from_num(0) { + return mat1.to_owned(); + } + if ratio == I32F32::from_num(1) { + return mat2.to_owned(); + } + assert!(mat1.len() == mat2.len()); + if mat1.is_empty() { + return vec![vec![]; 1]; + } + if mat1.first().unwrap_or(&vec![]).is_empty() { + return vec![vec![]; 1]; + } + let mut result: Vec> = + vec![vec![I32F32::from_num(0); mat1.first().unwrap_or(&vec![]).len()]; mat1.len()]; + for (i, (row1, row2)) in mat1.iter().zip(mat2.iter()).enumerate() { + assert!(row1.len() == row2.len()); + for (j, (&v1, &v2)) in row1.iter().zip(row2.iter()).enumerate() { + if let Some(res) = result.get_mut(i).unwrap_or(&mut vec![]).get_mut(j) { + *res = v1.saturating_add(ratio.saturating_mul(v2.saturating_sub(v1))); + } + } + } + result +} + +// Element-wise interpolation of two sparse matrices: Result = A + ratio * (B - A). +// ratio has intended range [0, 1] +// ratio=0: Result = A +// ratio=1: Result = B +#[allow(dead_code)] +pub fn interpolate_sparse( + mat1: &[Vec<(u16, I32F32)>], + mat2: &[Vec<(u16, I32F32)>], + columns: u16, + ratio: I32F32, +) -> Vec> { + if ratio == I32F32::from_num(0) { + return mat1.to_owned(); + } + if ratio == I32F32::from_num(1) { + return mat2.to_owned(); + } + assert!(mat1.len() == mat2.len()); + let rows = mat1.len(); + let zero: I32F32 = I32F32::from_num(0); + let mut result: Vec> = vec![vec![]; rows]; + for i in 0..rows { + let mut row1: Vec = vec![zero; columns as usize]; + if let Some(row) = mat1.get(i) { + for (j, value) in row { + if let Some(entry) = row1.get_mut(*j as usize) { + *entry = *value; + } + } + } + let mut row2: Vec = vec![zero; columns as usize]; + if let Some(row) = mat2.get(i) { + for (j, value) in row { + if let Some(entry) = row2.get_mut(*j as usize) { + *entry = *value; + } + } + } + for j in 0..columns as usize { + let v1 = row1.get(j).unwrap_or(&zero); + let v2 = row2.get(j).unwrap_or(&zero); + let interp = v1.saturating_add(ratio.saturating_mul(v2.saturating_sub(*v1))); + if zero < interp { + if let Some(res) = result.get_mut(i) { + res.push((j as u16, interp)); + } + } + } + } + result +} + // Element-wise product of two matrices. #[allow(dead_code)] pub fn hadamard(mat1: &[Vec], mat2: &[Vec]) -> Vec> { diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index be06ed59f..e6edd2585 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -137,18 +137,22 @@ impl Pallet { // Compute preranks: r_j = SUM(i) w_ij * s_i let preranks: Vec = matmul(&weights, &active_stake); - // Clip weights at majority consensus - let kappa: I32F32 = Self::get_float_kappa(netuid); // consensus majority ratio, e.g. 51%. + // Consensus majority ratio, e.g. 51%. + let kappa: I32F32 = Self::get_float_kappa(netuid); + // Calculate consensus as stake-weighted median of weights. let consensus: Vec = weighted_median_col(&active_stake, &weights, kappa); - inplace_col_clip(&mut weights, &consensus); - let validator_trust: Vec = row_sum(&weights); + // Clip weights at majority consensus. + let mut clipped_weights: Vec> = weights.clone(); + inplace_col_clip(&mut clipped_weights, &consensus); + // Calculate validator trust as sum of clipped weights set by validator. + let validator_trust: Vec = row_sum(&clipped_weights); // ==================================== // == Ranks, Server Trust, Incentive == // ==================================== // Compute ranks: r_j = SUM(i) w_ij * s_i - let mut ranks: Vec = matmul(&weights, &active_stake); + let mut ranks: Vec = matmul(&clipped_weights, &active_stake); // Compute server trust: ratio of rank after vs. rank before. let trust: Vec = vecdiv(&ranks, &preranks); @@ -161,6 +165,14 @@ impl Pallet { // == Bonds and Dividends == // ========================= + // Get validator bonds penalty in [0, 1]. + let bonds_penalty: I32F32 = Self::get_float_bonds_penalty(netuid); + // Calculate weights for bonds, apply bonds penalty to weights. + // bonds_penalty = 0: weights_for_bonds = weights.clone() + // bonds_penalty = 1: weights_for_bonds = clipped_weights.clone() + let weights_for_bonds: Vec> = + interpolate(&weights, &clipped_weights, bonds_penalty); + // Access network bonds. let mut bonds: Vec> = Self::get_bonds(netuid); inplace_mask_matrix(&outdated, &mut bonds); // mask outdated bonds @@ -168,7 +180,7 @@ impl Pallet { log::trace!("B:\n{:?}\n", &bonds); // Compute bonds delta column normalized. - let mut bonds_delta: Vec> = row_hadamard(&weights, &active_stake); // ΔB = W◦S + let mut bonds_delta: Vec> = row_hadamard(&weights_for_bonds, &active_stake); // ΔB = W◦S inplace_col_normalize(&mut bonds_delta); // sum_i b_ij = 1 log::trace!("ΔB:\n{:?}\n", &bonds_delta); // Compute the Exponential Moving Average (EMA) of bonds. @@ -474,15 +486,18 @@ impl Pallet { let preranks: Vec = matmul_sparse(&weights, &active_stake, n); log::trace!("Ranks (before): {:?}", &preranks); - // Clip weights at majority consensus - let kappa: I32F32 = Self::get_float_kappa(netuid); // consensus majority ratio, e.g. 51%. + // Consensus majority ratio, e.g. 51%. + let kappa: I32F32 = Self::get_float_kappa(netuid); + // Calculate consensus as stake-weighted median of weights. let consensus: Vec = weighted_median_col_sparse(&active_stake, &weights, n, kappa); log::trace!("Consensus: {:?}", &consensus); - weights = col_clip_sparse(&weights, &consensus); - log::trace!("Weights: {:?}", &weights); + // Clip weights at majority consensus. + let clipped_weights: Vec> = col_clip_sparse(&weights, &consensus); + log::trace!("Clipped Weights: {:?}", &clipped_weights); - let validator_trust: Vec = row_sum_sparse(&weights); + // Calculate validator trust as sum of clipped weights set by validator. + let validator_trust: Vec = row_sum_sparse(&clipped_weights); log::trace!("Validator Trust: {:?}", &validator_trust); // ============================= @@ -490,7 +505,7 @@ impl Pallet { // ============================= // Compute ranks: r_j = SUM(i) w_ij * s_i. - let mut ranks: Vec = matmul_sparse(&weights, &active_stake, n); + let mut ranks: Vec = matmul_sparse(&clipped_weights, &active_stake, n); log::trace!("Ranks (after): {:?}", &ranks); // Compute server trust: ratio of rank after vs. rank before. @@ -505,6 +520,14 @@ impl Pallet { // == Bonds and Dividends == // ========================= + // Get validator bonds penalty in [0, 1]. + let bonds_penalty: I32F32 = Self::get_float_bonds_penalty(netuid); + // Calculate weights for bonds, apply bonds penalty to weights. + // bonds_penalty = 0: weights_for_bonds = weights.clone() + // bonds_penalty = 1: weights_for_bonds = clipped_weights.clone() + let weights_for_bonds: Vec> = + interpolate_sparse(&weights, &clipped_weights, n, bonds_penalty); + // Access network bonds. let mut bonds: Vec> = Self::get_bonds_sparse(netuid); log::trace!("B: {:?}", &bonds); @@ -523,7 +546,8 @@ impl Pallet { log::trace!("B (mask+norm): {:?}", &bonds); // Compute bonds delta column normalized. - let mut bonds_delta: Vec> = row_hadamard_sparse(&weights, &active_stake); // ΔB = W◦S (outdated W masked) + let mut bonds_delta: Vec> = + row_hadamard_sparse(&weights_for_bonds, &active_stake); // ΔB = W◦S (outdated W masked) log::trace!("ΔB: {:?}", &bonds_delta); // Normalize bonds delta. @@ -713,6 +737,9 @@ impl Pallet { pub fn get_float_kappa(netuid: u16) -> I32F32 { I32F32::from_num(Self::get_kappa(netuid)).saturating_div(I32F32::from_num(u16::MAX)) } + pub fn get_float_bonds_penalty(netuid: u16) -> I32F32 { + I32F32::from_num(Self::get_bonds_penalty(netuid)).saturating_div(I32F32::from_num(u16::MAX)) + } pub fn get_block_at_registration(netuid: u16) -> Vec { let n = Self::get_subnetwork_n(netuid); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 6ecb69c87..9c4c5d0d4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -572,6 +572,11 @@ pub mod pallet { pub fn DefaultBondsMovingAverage() -> u64 { T::InitialBondsMovingAverage::get() } + /// Default bonds penalty. + #[pallet::type_value] + pub fn DefaultBondsPenalty() -> u16 { + T::InitialBondsPenalty::get() + } #[pallet::type_value] /// Default validator prune length. pub fn DefaultValidatorPruneLen() -> u64 { @@ -1184,6 +1189,10 @@ pub mod pallet { /// --- MAP ( netuid ) --> bonds_moving_average pub type BondsMovingAverage = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; + #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_penalty + pub type BondsPenalty = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultBondsPenalty>; /// --- MAP ( netuid ) --> weights_set_rate_limit #[pallet::storage] pub type WeightsSetRateLimit = diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 6595c8a43..49bb44dc1 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -93,6 +93,9 @@ mod config { /// Initial bonds moving average. #[pallet::constant] type InitialBondsMovingAverage: Get; + /// Initial bonds penalty. + #[pallet::constant] + type InitialBondsPenalty: Get; /// Initial target registrations per interval. #[pallet::constant] type InitialTargetRegistrationsPerInterval: Get; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 9c83b77c7..6dd6ddad2 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -57,6 +57,8 @@ mod events { ImmunityPeriodSet(u16, u16), /// bonds moving average is set for a subnet. BondsMovingAverageSet(u16, u64), + /// bonds penalty is set for a subnet. + BondsPenaltySet(u16, u16), /// setting the max number of allowed validators on a subnet. MaxAllowedValidatorsSet(u16, u16), /// the axon server information is added to the network. diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 43ac6b98c..7973a44e1 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -154,10 +154,14 @@ fn init_run_epochs( random_weights: bool, random_seed: u64, sparse: bool, + bonds_penalty: u16, ) { // === Create the network add_network(netuid, u16::MAX - 1, 0); // set higher tempo to avoid built-in epoch, then manual epoch instead + // === Set bonds penalty + SubtensorModule::set_bonds_penalty(netuid, bonds_penalty); + // === Register uids SubtensorModule::set_max_allowed_uids(netuid, n); for key in 0..n { @@ -403,21 +407,25 @@ fn init_run_epochs( // let epochs: u16 = 1; // let interleave = 2; // log::info!("test_consensus_guarantees ({network_n:?}, {validators_n:?} validators)"); -// for (major_stake, major_weight, minor_weight, weight_stddev) in vec![ -// (0.51, 1., 1., 0.001), -// (0.51, 0.03, 0., 0.001), -// (0.51, 0.51, 0.49, 0.001), -// (0.51, 0.51, 1., 0.001), -// (0.51, 0.61, 0.8, 0.1), -// (0.6, 0.67, 0.65, 0.2), -// (0.6, 0.74, 0.77, 0.4), -// (0.6, 0.76, 0.8, 0.4), -// (0.6, 0.76, 1., 0.4), -// (0.6, 0.92, 1., 0.4), -// (0.6, 0.94, 1., 0.4), -// (0.65, 0.78, 0.85, 0.6), -// (0.7, 0.81, 0.85, 0.8), -// (0.7, 0.83, 0.85, 1.), +// for (major_stake, major_weight, minor_weight, weight_stddev, bonds_penalty) in vec![ +// (0.51, 1., 1., 0.001, u16::MAX), +// (0.51, 0.03, 0., 0.001, u16::MAX), +// (0.51, 0.51, 0.49, 0.001, u16::MAX), +// (0.51, 0.51, 1., 0.001, u16::MAX), +// (0.51, 0.61, 0.8, 0.1, u16::MAX), +// (0.6, 0.67, 0.65, 0.2, u16::MAX), +// (0.6, 0.74, 0.77, 0.4, u16::MAX), +// (0.6, 0.76, 0.8, 0.4, u16::MAX), +// (0.6, 0.73, 1., 0.4, u16::MAX), // bonds_penalty = 100% +// (0.6, 0.74, 1., 0.4, 55800), // bonds_penalty = 85% +// (0.6, 0.76, 1., 0.4, 43690), // bonds_penalty = 66% +// (0.6, 0.78, 1., 0.4, 21845), // bonds_penalty = 33% +// (0.6, 0.79, 1., 0.4, 0), // bonds_penalty = 0% +// (0.6, 0.92, 1., 0.4, u16::MAX), +// (0.6, 0.94, 1., 0.4, u16::MAX), +// (0.65, 0.78, 0.85, 0.6, u16::MAX), +// (0.7, 0.81, 0.85, 0.8, u16::MAX), +// (0.7, 0.83, 0.85, 1., u16::MAX), // ] { // let ( // validators, @@ -455,6 +463,7 @@ fn init_run_epochs( // false, // 0, // false, +// bonds_penalty // ); // let mut major_emission: I64F64 = I64F64::from_num(0); @@ -698,6 +707,7 @@ fn test_512_graph() { false, 0, false, + u16::MAX, ); let bonds = SubtensorModule::get_bonds(netuid); for uid in validators { @@ -744,96 +754,99 @@ fn test_512_graph_random_weights() { let epochs: u16 = 1; log::info!("test_{network_n:?}_graph_random_weights ({validators_n:?} validators)"); for interleave in 0..3 { + // server-self weight off/on for server_self in [false, true] { - // server-self weight off/on - let (validators, servers) = distribute_nodes( - validators_n as usize, - network_n as usize, - interleave as usize, - ); - let server: usize = servers[0] as usize; - let validator: usize = validators[0] as usize; - #[allow(clippy::type_complexity)] - let (mut rank, mut incentive, mut dividend, mut emission, mut bondv, mut bonds): ( - Vec, - Vec, - Vec, - Vec, - Vec, - Vec, - ) = (vec![], vec![], vec![], vec![], vec![], vec![]); - - // Dense epoch - new_test_ext(1).execute_with(|| { - init_run_epochs( - netuid, - network_n, - &validators, - &servers, - epochs, - 1, - server_self, - &[], - false, - &[], - false, - true, - interleave as u64, - false, + for bonds_penalty in [0, u16::MAX / 2, u16::MAX] { + let (validators, servers) = distribute_nodes( + validators_n as usize, + network_n as usize, + interleave as usize, ); + let server: usize = servers[0] as usize; + let validator: usize = validators[0] as usize; + let (mut rank, mut incentive, mut dividend, mut emission, mut bondv, mut bonds): ( + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + ) = (vec![], vec![], vec![], vec![], vec![], vec![]); + + // Dense epoch + new_test_ext(1).execute_with(|| { + init_run_epochs( + netuid, + network_n, + &validators, + &servers, + epochs, + 1, + server_self, + &[], + false, + &[], + false, + true, + interleave as u64, + false, + bonds_penalty, + ); - let bond = SubtensorModule::get_bonds(netuid); - for uid in 0..network_n { - rank.push(SubtensorModule::get_rank_for_uid(netuid, uid)); - incentive.push(SubtensorModule::get_incentive_for_uid(netuid, uid)); - dividend.push(SubtensorModule::get_dividends_for_uid(netuid, uid)); - emission.push(SubtensorModule::get_emission_for_uid(netuid, uid)); - bondv.push(bond[uid as usize][validator]); - bonds.push(bond[uid as usize][server]); - } - }); + let bond = SubtensorModule::get_bonds(netuid); + for uid in 0..network_n { + rank.push(SubtensorModule::get_rank_for_uid(netuid, uid)); + incentive.push(SubtensorModule::get_incentive_for_uid(netuid, uid)); + dividend.push(SubtensorModule::get_dividends_for_uid(netuid, uid)); + emission.push(SubtensorModule::get_emission_for_uid(netuid, uid)); + bondv.push(bond[uid as usize][validator]); + bonds.push(bond[uid as usize][server]); + } + }); - // Sparse epoch (same random seed as dense) - new_test_ext(1).execute_with(|| { - init_run_epochs( - netuid, - network_n, - &validators, - &servers, - epochs, - 1, - server_self, - &[], - false, - &[], - false, - true, - interleave as u64, - true, - ); - // Assert that dense and sparse epoch results are equal - let bond = SubtensorModule::get_bonds(netuid); - for uid in 0..network_n { - assert_eq!( - SubtensorModule::get_rank_for_uid(netuid, uid), - rank[uid as usize] - ); - assert_eq!( - SubtensorModule::get_incentive_for_uid(netuid, uid), - incentive[uid as usize] - ); - assert_eq!( - SubtensorModule::get_dividends_for_uid(netuid, uid), - dividend[uid as usize] - ); - assert_eq!( - SubtensorModule::get_emission_for_uid(netuid, uid), - emission[uid as usize] + // Sparse epoch (same random seed as dense) + new_test_ext(1).execute_with(|| { + init_run_epochs( + netuid, + network_n, + &validators, + &servers, + epochs, + 1, + server_self, + &[], + false, + &[], + false, + true, + interleave as u64, + true, + bonds_penalty, ); - assert_eq!(bond[uid as usize][validator], bondv[uid as usize]); - assert_eq!(bond[uid as usize][server], bonds[uid as usize]); - } - }); + // Assert that dense and sparse epoch results are equal + let bond = SubtensorModule::get_bonds(netuid); + for uid in 0..network_n { + assert_eq!( + SubtensorModule::get_rank_for_uid(netuid, uid), + rank[uid as usize] + ); + assert_eq!( + SubtensorModule::get_incentive_for_uid(netuid, uid), + incentive[uid as usize] + ); + assert_eq!( + SubtensorModule::get_dividends_for_uid(netuid, uid), + dividend[uid as usize] + ); + assert_eq!( + SubtensorModule::get_emission_for_uid(netuid, uid), + emission[uid as usize] + ); + assert_eq!(bond[uid as usize][validator], bondv[uid as usize]); + assert_eq!(bond[uid as usize][server], bonds[uid as usize]); + } + }); + } } } } @@ -873,6 +886,7 @@ fn test_512_graph_random_weights() { // false, // 0, // true, +// u16::MAX, // ); // let (total_stake, _, _) = SubtensorModule::get_stake_weights_for_network(netuid); // assert_eq!(total_stake.iter().map(|s| s.to_num::()).sum::(), 21_000_000_000_000_000); @@ -940,6 +954,7 @@ fn test_512_graph_random_weights() { // false, // 0, // true, +// u16::MAX, // ); // let bonds = SubtensorModule::get_bonds(netuid); // for uid in validators { @@ -995,6 +1010,8 @@ fn test_bonds() { SubtensorModule::set_weights_set_rate_limit( netuid, 0 ); SubtensorModule::set_min_allowed_weights( netuid, 1 ); SubtensorModule::set_max_weight_limit( netuid, u16::MAX ); + SubtensorModule::set_bonds_penalty(netuid, u16::MAX); + // === Register [validator1, validator2, validator3, validator4, server1, server2, server3, server4] for key in 0..n as u64 { @@ -1752,6 +1769,7 @@ fn test_outdated_weights() { SubtensorModule::set_target_registrations_per_interval(netuid, n); SubtensorModule::set_min_allowed_weights(netuid, 0); SubtensorModule::set_max_weight_limit(netuid, u16::MAX); + SubtensorModule::set_bonds_penalty(netuid, u16::MAX); assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 0); // === Register [validator1, validator2, server1, server2] @@ -2817,6 +2835,7 @@ fn test_blocks_since_last_step() { // let epochs: u16 = 1; // let interleave = 0; // let weight_stddev: I32F32 = fixed(0.4); +// let bonds_penalty: u16 = u16::MAX; // println!("["); // for _major_stake in vec![0.51, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99] { // let major_stake: I32F32 = I32F32::from_num(_major_stake); @@ -2846,7 +2865,7 @@ fn test_blocks_since_last_step() { // ); // // new_test_ext(1).execute_with(|| { -// init_run_epochs(netuid, network_n, &validators, &servers, epochs, 1, true, &stake, true, &weights, true, false, 0, true); +// init_run_epochs(netuid, network_n, &validators, &servers, epochs, 1, true, &stake, true, &weights, true, false, 0, true, bonds_penalty); // // let mut major_emission: I64F64 = I64F64::from_num(0); // let mut minor_emission: I64F64 = I64F64::from_num(0); diff --git a/pallets/subtensor/src/tests/math.rs b/pallets/subtensor/src/tests/math.rs index c5aaca84c..84fcc4aac 100644 --- a/pallets/subtensor/src/tests/math.rs +++ b/pallets/subtensor/src/tests/math.rs @@ -1716,6 +1716,240 @@ fn test_math_weighted_median_col_sparse() { ); } +#[test] +fn test_math_interpolate() { + let mat1: Vec> = vec![vec![]]; + let mat2: Vec> = vec![vec![]]; + let target: Vec> = vec![vec![]]; + let ratio = I32F32::from_num(0); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec> = vec![vec![I32F32::from_num(0)]]; + let mat2: Vec> = vec![vec![I32F32::from_num(1)]]; + let target: Vec> = vec![vec![I32F32::from_num(0)]]; + let ratio = I32F32::from_num(0); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec> = vec![vec![I32F32::from_num(1)]]; + let ratio = I32F32::from_num(1); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let ratio = I32F32::from_num(1); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![1., 10., 100., 1000., 10000., 100000.]; + let mat2: Vec = vec![10., 100., 1000., 10000., 100000., 1000000.]; + let target: Vec = vec![1., 10., 100., 1000., 10000., 100000.]; + let mat1 = vec_to_mat_fixed(&mat1, 3, false); + let mat2 = vec_to_mat_fixed(&mat2, 3, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 3, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![9.1, 91., 910., 9100., 91000., 910000.]; + let ratio = I32F32::from_num(0.9); + let target = vec_to_mat_fixed(&target, 3, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0.0001)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + ]; + let ratio = I32F32::from_num(0.000000001); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + ]; + let ratio = I32F32::from_num(0.9999998808); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); +} + +#[test] +fn test_math_interpolate_sparse() { + let mat1: Vec> = vec![vec![]]; + let mat2: Vec> = vec![vec![]]; + let target: Vec> = vec![vec![]]; + let ratio = I32F32::from_num(0); + let result = interpolate_sparse(&mat1, &mat2, 0, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0.]; + let mat2: Vec = vec![1.]; + let target: Vec = vec![0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 1, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 1, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let ratio = I32F32::from_num(1); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![1., 0., 100., 1000., 10000., 100000.]; + let mat2: Vec = vec![10., 100., 1000., 10000., 100000., 0.]; + let target: Vec = vec![1., 0., 100., 1000., 10000., 100000.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 3, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 3, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 3, false); + let result = interpolate_sparse(&mat1, &mat2, 2, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![9.1, 90., 910., 9100., 91000., 10000.]; + let ratio = I32F32::from_num(0.9); + let target = vec_to_sparse_mat_fixed(&target, 3, false); + let result = interpolate_sparse(&mat1, &mat2, 2, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0.0001)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + ]; + let ratio = I32F32::from_num(0.000000001); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + 0.999_999_9, + ]; + let ratio = I32F32::from_num(0.9999998808); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); +} + #[test] fn test_math_hadamard() { let mat2: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.]; diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 9731113e5..9d5f939f6 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -138,6 +138,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; + pub const InitialBondsPenalty:u16 = 0; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production @@ -373,6 +374,7 @@ impl crate::Config for Test { type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; + type InitialBondsPenalty = InitialBondsPenalty; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 25f547c5d..f1e0c7eb4 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -566,6 +566,14 @@ impl Pallet { Self::deposit_event(Event::BondsMovingAverageSet(netuid, bonds_moving_average)); } + pub fn get_bonds_penalty(netuid: u16) -> u16 { + BondsPenalty::::get(netuid) + } + pub fn set_bonds_penalty(netuid: u16, bonds_penalty: u16) { + BondsPenalty::::insert(netuid, bonds_penalty); + Self::deposit_event(Event::BondsPenaltySet(netuid, bonds_penalty)); + } + pub fn get_max_registrations_per_block(netuid: u16) -> u16 { MaxRegistrationsPerBlock::::get(netuid) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 36a879e48..e0ea63987 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1011,6 +1011,7 @@ parameter_types! { pub const SubtensorInitialMaxRegistrationsPerBlock: u16 = 1; pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; + pub const SubtensorInitialBondsPenalty: u16 = 0; 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 @@ -1058,6 +1059,7 @@ impl pallet_subtensor::Config for Runtime { type InitialKappa = SubtensorInitialKappa; type InitialMaxAllowedUids = SubtensorInitialMaxAllowedUids; type InitialBondsMovingAverage = SubtensorInitialBondsMovingAverage; + type InitialBondsPenalty = SubtensorInitialBondsPenalty; type InitialIssuance = SubtensorInitialIssuance; type InitialMinAllowedWeights = SubtensorInitialMinAllowedWeights; type InitialEmissionValue = SubtensorInitialEmissionValue; From 2633e8854606ebad2143179482d2e92d35c3eaf0 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 23 Jan 2025 11:54:43 -0500 Subject: [PATCH 25/25] fix tests to incl fee (#1186) --- pallets/subtensor/src/tests/move_stake.rs | 41 +++++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 04f79f3f9..96d54ed77 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -1080,6 +1080,8 @@ fn test_do_swap_success() { let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); + let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; @@ -1114,7 +1116,11 @@ fn test_do_swap_success() { &coldkey, destination_netuid, ); - assert_abs_diff_eq!(alpha_after, stake_amount, epsilon = stake_amount / 1000); + assert_abs_diff_eq!( + alpha_after, + stake_amount - fee, + epsilon = stake_amount / 1000 + ); }); } @@ -1259,6 +1265,8 @@ fn test_do_swap_same_subnet() { let subnet_owner_hotkey = U256::from(1101); let netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); + let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; @@ -1268,6 +1276,7 @@ fn test_do_swap_same_subnet() { let alpha_before = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); + let fee_as_alpha = SubtensorModule::swap_tao_for_alpha(netuid, fee); assert_ok!(SubtensorModule::do_swap_stake( RuntimeOrigin::signed(coldkey), @@ -1279,7 +1288,8 @@ fn test_do_swap_same_subnet() { let alpha_after = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid); - assert_abs_diff_eq!(alpha_after, alpha_before, epsilon = 5); + assert_abs_diff_eq!(alpha_after, alpha_before - fee_as_alpha, epsilon = 15); + // Some slippage due to fee on same subnet. }); } @@ -1291,6 +1301,8 @@ fn test_do_swap_partial_stake() { let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); + let coldkey = U256::from(1); let hotkey = U256::from(2); let total_stake = DefaultMinStake::::get() * 10; @@ -1298,6 +1310,7 @@ fn test_do_swap_partial_stake() { SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, origin_netuid, total_stake, 0); + let fee_as_alpha2 = SubtensorModule::swap_tao_for_alpha(destination_netuid, fee); let swap_amount = total_stake / 2; assert_ok!(SubtensorModule::do_swap_stake( RuntimeOrigin::signed(coldkey), @@ -1322,7 +1335,7 @@ fn test_do_swap_partial_stake() { &coldkey, destination_netuid ), - swap_amount, + swap_amount - fee_as_alpha2, epsilon = total_stake / 1000 ); }); @@ -1336,6 +1349,8 @@ fn test_do_swap_storage_updates() { let origin_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let destination_netuid = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); + let coldkey = U256::from(1); let hotkey = U256::from(2); let stake_amount = DefaultMinStake::::get() * 10; @@ -1365,13 +1380,15 @@ fn test_do_swap_storage_updates() { 0 ); + let fee_as_alpha = SubtensorModule::swap_tao_for_alpha(destination_netuid, fee); + assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, destination_netuid ), - alpha, + alpha - fee_as_alpha, epsilon = 5 ); }); @@ -1385,6 +1402,8 @@ fn test_do_swap_multiple_times() { let netuid1 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); let netuid2 = add_dynamic_network(&subnet_owner_hotkey, &subnet_owner_coldkey); + let fee = DefaultMinStake::::get(); + let coldkey = U256::from(1); let hotkey = U256::from(2); let initial_stake = DefaultMinStake::::get() * 10; @@ -1392,6 +1411,7 @@ fn test_do_swap_multiple_times() { SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); SubtensorModule::stake_into_subnet(&hotkey, &coldkey, netuid1, initial_stake, 0); + let mut total_alpha1_fee = 0; for _ in 0..3 { let alpha1 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, netuid1, @@ -1404,6 +1424,9 @@ fn test_do_swap_multiple_times() { netuid2, alpha1 )); + + let fee_as_alpha = SubtensorModule::swap_tao_for_alpha(netuid1, fee); + total_alpha1_fee += fee_as_alpha; } let alpha2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &hotkey, &coldkey, netuid2, @@ -1416,17 +1439,21 @@ fn test_do_swap_multiple_times() { netuid1, alpha2 )); + + let fee_as_alpha = SubtensorModule::swap_tao_for_alpha(netuid1, fee); + total_alpha1_fee += fee_as_alpha; } } - let final_stake_netuid1 = + let final_stake_netuid1: u64 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid1); let final_stake_netuid2 = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid2); + let expected_stake = initial_stake - total_alpha1_fee; assert_abs_diff_eq!( final_stake_netuid1, - initial_stake, - epsilon = initial_stake / 1000 + expected_stake, + epsilon = expected_stake / 1000 ); assert_eq!(final_stake_netuid2, 0); });